workers icon indicating copy to clipboard operation
workers copied to clipboard

Frequent "Promise will never complete" and "call to released function" errors in the fetch example

Open HouzuoGuo opened this issue 2 years ago • 5 comments

Hey. I'm running the fetch example (_examples/fetch/main.go) under a free worker plan. When making about 2-3 requests per second the example works quite well, however, when the request rate increases to about 5 request per second the example starts to run into these errors:

...
GET https://first.hzgl.workers.dev/fetchexample - Ok @ 5/20/2023, 6:03:26 PM
GET https://first.hzgl.workers.dev/fetchexample - Exception Thrown @ 5/20/2023, 6:03:26 PM
✘ [ERROR]   Error: The script will never generate a response.
GET https://first.hzgl.workers.dev/fetchexample - Exception Thrown @ 5/20/2023, 6:03:26 PM
  (error) call to released function
✘ [ERROR]   Error: Promise will never complete.
GET https://first.hzgl.workers.dev/fetchexample - Ok @ 5/20/2023, 6:03:26 PM
...

HouzuoGuo avatar May 20 '23 18:05 HouzuoGuo

I'll check this later, thanks!

syumai avatar May 21 '23 23:05 syumai

I have a similar thing going on -- below is relevant data thus far:

I set wrangler to local mode in order to see the errors.

[mf:inf] Worker reloaded! (1.06MiB)
[mf:wrn] Worker's uncompressed size exceeds the 1MiB limit! Note that your worker will be compressed during upload so you may still be able to deploy it.
[mf:inf] Listening on 0.0.0.0:8787
[mf:inf] - http://127.0.0.1:8787
[mf:inf] Updated `Request.cf` object cache!
panic: unimplemented: (reflect.Type).NumMethod()
[mf:err] Unhandled Promise Rejection: RuntimeError: unreachable
    at runtime._panic (wasm://wasm/00429132:1:7424)
    at (reflect.rawType).NumMethod (wasm://wasm/00429132:1:16538)
    at encoding/json.indirect (wasm://wasm/00429132:1:247803)
    at (*encoding/json.decodeState).value (wasm://wasm/00429132:1:240398)
    at main.main$1 (wasm://wasm/00429132:1:357508)
    at github.com/syumai/workers.handleRequest$1 (wasm://wasm/00429132:1:343023)
    at github.com/syumai/workers.handleRequest$1$gowrapper (wasm://wasm/00429132:1:342583)
    at tinygo_rewind (wasm://wasm/00429132:1:1903)
    at (*internal/task.Task).Resume (wasm://wasm/00429132:1:38562)
    at runtime.scheduler (wasm://wasm/00429132:1:133061)

Here is my code(It's hacked together from multiple of your examples @syumai) I'm unsure if I'm using the right cloudflare tools for the job I'm doing but I'm just pasting the information in case it is helfpul :)


import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"

	"github.com/syumai/tinyutil/httputil"
	"github.com/syumai/workers"
	"github.com/syumai/workers/cloudflare"
)

type TokenResponse struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
}

const kvNamespace = "twitch"
const tokenAccessKey = "accessToken"
const tokenExpiresKey = "tokenExpires"

func main() {
	workers.Serve(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		clientID := cloudflare.Getenv(req.Context(), "client_id")
		clientSecret := cloudflare.Getenv(req.Context(), "client_secret")

		data := url.Values{}
		data.Set("client_id", clientID)
		data.Set("client_secret", clientSecret)
		data.Set("grant_type", "client_credentials")

		// initialize KV namespace instance
		kv, err := cloudflare.NewKVNamespace(req.Context(), kvNamespace)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("failed to init KV: " + err.Error()))
			return
		}

		res, err := httputil.PostForm("https://id.twitch.tv/oauth2/token", data)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("failed to make request: " + err.Error()))
			return
		}

		defer res.Body.Close()
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("failed to read response: " + err.Error()))
			return
		}

		var tokenResponse TokenResponse
		err = json.Unmarshal(body, &tokenResponse)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("failed to unmarshal response: " + err.Error()))
			return
		}

		kv.PutString(tokenAccessKey, tokenResponse.AccessToken, nil)
		kv.PutString(tokenExpiresKey, strconv.Itoa(tokenResponse.ExpiresIn), nil)
	}))
}

allisoneer avatar May 25 '23 17:05 allisoneer

I think it's likely my use of encoding/json and tinygo not having reflect fully implemented yet(although it looks like we're closer to having it) --- https://github.com/tinygo-org/tinygo/issues/2660

I'm unsure if it's similar for your fetch example. I attempted to unmarshal into a map instead of a struct and I still get the reflection error. I'm still digging currently though.

allisoneer avatar May 25 '23 18:05 allisoneer

Yep, that ended up being exactly what it was. I attempted to use json.Decode instead of unmarshal but I think it still uses reflect under the hood somewhere. I removed encoding/json and implemented my own json parser for the specific section I was in and it ran well right away locally. I then published my code and was able to get 15 requests per second handled without any errors in production workers.

Here was the final sizing of my uploaded package - Total Upload: 844.29 KiB / gzip: 272.16 KiB

allisoneer avatar May 25 '23 18:05 allisoneer

@AdjectiveAllison In the upcoming release of TinyGo (v0.28), which is expected to be released soon, many features of the reflect package are expected to be supported. It appears that a significant number of functionalities in the encoding/json package, which are currently not working, will also start working. I believe that your sample code will also start working. I have been keeping an eye on the progress of TinyGo, and if encoding/json starts working, I plan to update the example.

syumai avatar May 26 '23 00:05 syumai