Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: wasm: fatal error: all goroutines are asleep - deadlock! #41310

Closed
darkLord19 opened this issue Sep 10, 2020 · 10 comments
Closed

runtime: wasm: fatal error: all goroutines are asleep - deadlock! #41310

darkLord19 opened this issue Sep 10, 2020 · 10 comments

Comments

@darkLord19
Copy link
Contributor

darkLord19 commented Sep 10, 2020

What version of Go are you using (go version)?

$ go version
go version go1.15 darwin/amd64

Does this issue reproduce with the latest release?

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/uparmar/Library/Caches/go-build"
GOENV="/Users/uparmar/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/uparmar/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/uparmar/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.15/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.15/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/l7/x5hcvngd5m99k62kxv7srrdr0000gn/T/go-build448055941=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Call Go function from js(from chrome dev tools console)

// +build js, wasm

package main

import (
	"net/http"
	"syscall/js"
)

func test(this js.Value, args []js.Value) interface{} {
	println("test called")
	resp, err := http.Get("https://hacker-news.firebaseio.com/v0/item/24429478.json")
	if err != nil {
		println(err.Error())
		return nil
	}
	defer resp.Body.Close()
	var tmp []byte
	resp.Body.Read(tmp)
	println("response is", tmp)
	return nil
}

func registerFuncs() {
	js.Global().Set("test", js.FuncOf(test))
}

func main() {
	c := make(chan struct{})
	println("WASM Go Initialized")
	registerFuncs()
	<-c
}

<!doctype html>
<html>

<head>
	<meta charset="utf-8">
	<title>Test</title>
</head>

<body>
	<h1> TEST </h1>
</body>

<footer>
	<script type="text/javascript" src="./wasm_exec.js"></script>
	<script>
		if (!WebAssembly.instantiateStreaming) { // polyfill
			WebAssembly.instantiateStreaming = async (resp, importObject) => {
				const source = await (await resp).arrayBuffer();
				return await WebAssembly.instantiate(source, importObject);
			};
		}

		const go = new Go();
		let mod, inst;
		WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then(
		async result => {
			mod = result.module;
			inst = result.instance;
                        go.run(inst)
		}).catch((err) => {
			console.error(err);
		});
	</script>

</footer>

</html>

What did you expect to see?

Function running succesfully

What did you see instead?

test()
wasm_exec.js:50 test called
wasm_exec.js:50 fatal error: all goroutines are asleep - deadlock!
wasm_exec.js:50 
wasm_exec.js:50 goroutine 1 [chan receive]:
wasm_exec.js:50 main.main()
wasm_exec.js:50 	/Users/uparmar/Development/test_wasm/test.go:33 +0x7
wasm_exec.js:50 
wasm_exec.js:50 goroutine 6 [select]:
wasm_exec.js:50 net/http.(*Transport).RoundTrip(0x33c860, 0x486000, 0x33c860, 0x0, 0x0)
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/net/http/roundtrip_js.go:168 +0x55
wasm_exec.js:50 net/http.send(0x486000, 0xc8260, 0x33c860, 0x0, 0x0, 0x0, 0x0, 0x40c040, 0x424ab0, 0x1)
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/net/http/client.go:252 +0x5b
wasm_exec.js:50 net/http.(*Client).send(0x3490c0, 0x486000, 0x0, 0x0, 0x0, 0x40c040, 0x0, 0x1, 0xf8)
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/net/http/client.go:176 +0x13
wasm_exec.js:50 net/http.(*Client).do(0x3490c0, 0x486000, 0x0, 0x0, 0x0)
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/net/http/client.go:718 +0x38
wasm_exec.js:50 net/http.(*Client).Do(...)
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/net/http/client.go:586
wasm_exec.js:50 net/http.(*Client).Get(0x3490c0, 0x8c00d, 0x38, 0x41a138, 0x14a30008, 0x14a80007)
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/net/http/client.go:475 +0xe
wasm_exec.js:50 net/http.Get(...)
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/net/http/client.go:447
wasm_exec.js:50 main.test(0x0, 0x0, 0x368070, 0x0, 0x0, 0x0, 0x0)
wasm_exec.js:50 	/Users/uparmar/Development/test_wasm/test.go:12 +0x7
wasm_exec.js:50 syscall/js.handleEvent()
wasm_exec.js:50 	/usr/local/Cellar/go/1.15/libexec/src/syscall/js/func.go:96 +0x24
wasm_exec.js:141 exit code: 2
@agnivade
Copy link
Contributor

This is documented here: https://golang.org/pkg/syscall/js/#FuncOf

As a consequence, if one wrapped function blocks, JavaScript's event loop is blocked until that function returns. Hence, calling any async JavaScript API, which requires the event loop, like fetch (http.Client), will cause an immediate deadlock. Therefore a blocking function should explicitly start a new goroutine.

You would need to perform the http.Get in a separate goroutine.

@darkLord19
Copy link
Contributor Author

Thanks @agnivade for clarification.

@darkLord19
Copy link
Contributor Author

darkLord19 commented Sep 12, 2020

@agnivade I am doing http.Get in seperate routine but still getting error. I am creating a channel before invoking go routine to perform http.Get and writing on it from inside the go routine and reading from channel later down the function. Is this also a limitation?

@agnivade
Copy link
Contributor

Yes, that does not change anything if you read from the channel in the same function. It will still cause the deadlock. You have to create a separate event handler loop or some other mechanism to make http requests and read responses.

@happybeing
Copy link

I'm new to Go so would appreciate any thoughts on my scenario. I'm not doing the GET directly, but have compiled go-git to wasm and am calling its repo.Clone() in the browser, which causes the same error as in the OP. The http requests and responses are there and look ok, but Go has died with the error fatal error: all goroutines are asleep - deadlock! wasm_exec.js.

So my question is about ways to solve this when not doing the fetches directly, but when they are happening in another package (which would not expect to be used in the browser). Can I solve this without re-writing the relevant parts of go-git?

If not I guess I'll look into modifying go-git to use my own "fetch" package ("in another event loop" whatever that means 😄), but am wondering if you can offer any advice to someone new to Go. Thanks.

@darkLord19
Copy link
Contributor Author

You can try wrapping repo.Clone() call in a seperate go routine.

@happybeing
Copy link

Thanks. I'm trying the following, but maybe it can only work if Clone() only does a single fetch(), because I'm seeing the Clone() get further (more console output) but still fail with the same error.

This is in a funciton called via the wasm go bridge so I can't put it in a playground:

	url = "https://gitlab.com/weblate/libvirt"
	fs := memfs.New()
	storage := memory.NewStorage()
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		_, err := git.Clone(storage, fs, &git.CloneOptions{ URL: url })
		if err != nil {
			println("git.Clone() failed: ", err.Error())
		}

		wg.Done()
	}()
	wg.Wait()

Or might the problem remain because this is (I think) a nested goroutine?

@happybeing
Copy link

I think I have solved this, the issue remained because I was waiting on the result so if I remove the WaitGroup altogether and just create the goroutine as it, it doesn't exit as in:

	url = "https://gitlab.com/weblate/libvirt"
	fs := memfs.New()
	storage := memory.NewStorage()
	go func() {
		_, err := git.Clone(storage, fs, &git.CloneOptions{URL: url})
		if err != nil {
			println("git.Clone() failed: ", err.Error())
		}
	}()

It doesn't clone the repo, but that's a different issue. Thanks for your help.

@darkLord19
Copy link
Contributor Author

darkLord19 commented Nov 12, 2020

You're cloning in memory but not using it's return value so that might be issue. Try doing

repo, err := repo.Clone(storage, fs,...)

And this repo variable will contain cloned repo.

And again you won't be able to wait for go routine
You can try using js promises and see if it works

func Clone() js.Func {
	return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		// Handler for the Promise: this is a JS function
		// It receives two arguments, which are JS functions themselves: resolve and reject
		handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
			resolve := args[0]
			// Commented out because this Promise never fails
			//reject := args[1]

			// Now that we have a way to return the response to JS, spawn a goroutine
			// This way, we don't block the event loop and avoid a deadlock
			go func() {
				r, err := repo.Clone()
				// Resolve the Promise, passing anything back to JavaScript
				// This is done by invoking the "resolve" function passed to the handler
				resolve.Invoke(r)
			}()

			// The handler of a Promise doesn't return any value
			return nil
		})

		// Create and return the Promise object
		promiseConstructor := js.Global().Get("Promise")
		return promiseConstructor.New(handler)
	})
}

To invoke from JavaScript

async function MyFunc() {
    // Get the Promise from Go
    const p = Clone()
    // Await for the Promise to resolve
    const repo = await p
    // Use repo response
}

@happybeing
Copy link

I'm pretty sure the remaining issue is because I'm doing this in the browser, so the server issues a 404 (after initially responding as desired), so it isn't a concern for this thread. It's also not vital for my use case, more for testing at least for now so I'm happy!

And thanks for your help. Go has a great community and I've had excellent help whenever I get stuck.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants