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

syscall/js: "goroutines are asleep" error only in Node.js #32764

Closed
agnivade opened this issue Jun 25, 2019 · 9 comments
Closed

syscall/js: "goroutines are asleep" error only in Node.js #32764

agnivade opened this issue Jun 25, 2019 · 9 comments
Labels
arch-wasm WebAssembly issues FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.

Comments

@agnivade
Copy link
Contributor

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

$ go version
go version go1.12.4 linux/amd64

Does this issue reproduce with the latest release?

Yes

What did you do?

package main

import (
	"fmt"
	"syscall/js"
)

func main() {
	var cb js.Func
	cb = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		fmt.Println("cb called")
		return nil
	})
	js.Global().Set("callme", cb)
	done := make(chan struct{})
	fmt.Println("waiting for callback")
	<-done
}

I ran this in both browser and Node.js.

What did you expect to see?

I expect in both cases, for the program to wait indefinitely.

What did you see instead?

In works fine in browser:

$#go_js_wasm_exec is set to wasmbrowsertest
$GOOS=js GOARCH=wasm go run foo.go 
waiting for callback
^Csignal: interrupt

But fails in Node.js

$PATH=/usr/local/go/misc/wasm:$PATH GOOS=js GOARCH=wasm go run foo.go 
waiting for callback
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	/home/agniva/play/go/src/foo.go:24 +0xb
exit status 2

Is this expected ?

My node version is 10.2.1

/cc @neelance

I haven't tried with 1.11 because the function semantics have changed since.

@agnivade agnivade added arch-wasm WebAssembly issues NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Jun 25, 2019
@johanbrandhorst
Copy link
Member

Thinking out loud, but presumably this incompatibility is lurking somewhere in wasm_exec.js? If it's working in the browser, I assume that means the generated WASM is fine, and that it's the translation layer that's doing something wrong.

@neelance
Copy link
Member

neelance commented Oct 4, 2019

This is expected. The following code is only used by Node.js:

go/misc/wasm/wasm_exec.js

Lines 525 to 531 in 5f4aa5d

process.on("exit", (code) => { // Node.js exits if no event handler is pending
if (code === 0 && !go.exited) {
// deadlock, make Go print error and stack traces
go._pendingEvent = { id: 0 };
go._resume();
}
});

The reasoning is that if all goroutines are asleep AND there is no Node.js event handler pending, then the program can never continue, which is a "deadlock" situation. In fact, the default behavior of Node.js is to just quit with exit code 0, because there is no code to run any more 😒. I opted for throwing the error instead.

@agnivade
Copy link
Contributor Author

agnivade commented Oct 5, 2019

I see. Then does this mean in Node.js, we can never attach a global callback without hooking into the event loop somehow ?

@neelance
Copy link
Member

neelance commented Oct 6, 2019

The issue is not the callback, but that there is no code at all running any more, thus the application would hang forever. Explicitly saying "this is a deadlock" is a feature, not a bug.

What are you trying to do?

@agnivade
Copy link
Contributor Author

agnivade commented Oct 6, 2019

I was trying to debug this - #26277. Then realized that the code gives this deadlock error, which obviously did not give at that time. Reduced it to this test case.

Hence, I was trying to get to the root of it.

Full code here

package main

import (
"fmt"
"syscall/js"
)

var headers js.Value

func init() {
headers := js.Global().Get("Object").New()
headers.Set("Content-Type", "text/plain")
}

func main() {
require := js.Global().Get("require")
http := require.Invoke("http")
app := http.Get("Server").New()
app.Call("on", "request", js.FuncOf(request))
app.Call("listen", 3000, js.FuncOf(listen))
wait()
}

func request(this js.Value, args []js.Value) interface{} {
res := args[1]
res.Call("writeHead", 200, headers)
res.Call("write", "Hello World")
res.Call("end", "\n")
return nil
}

func listen(this js.Value, args []js.Value) interface{} {
fmt.Println("listening on port 3000")
return nil
}

func wait() {
done := make(chan bool)
js.Global().Get("process").Call("on", "SIGTERM", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
done <- true
return nil
}))
<-done
}

@neelance
Copy link
Member

neelance commented Oct 6, 2019

The problem with this code is that you are doing a blocking operation (fmt.Println) in a callback (listen). This causes listen to never return, thus the HTTP server never starts. This means that there is no event that can ever cause the program to continue, which is a deadlock.

If you replace fmt.Println with go fmt.Println or println, then it works as expected.

However, the stack of the hanging listen function does not show up when printing the deadlock. This is bad, I'll look into it.

@agnivade
Copy link
Contributor Author

agnivade commented Oct 6, 2019

Aha ! Missed that one. Sorry I went down a different rabbit hole. Thanks for looking into it.

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/199537 mentions this issue: runtime: do not omit stack trace of goroutine that handles async events

gopherbot pushed a commit that referenced this issue Oct 7, 2019
On wasm there is a special goroutine that handles asynchronous events.
Blocking this goroutine often causes a deadlock. However, the stack
trace of this goroutine was omitted when printing the deadlock error.

This change adds an exception so the goroutine is not considered as
an internal system goroutine and the stack trace gets printed, which
helps with debugging the deadlock.

Updates #32764

Change-Id: Icc8f5ba3ca5a485d557b7bdd76bf2f1ffb92eb3e
Reviewed-on: https://go-review.googlesource.com/c/go/+/199537
Run-TryBot: Richard Musiol <neelance@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
@agnivade
Copy link
Contributor Author

agnivade commented Dec 6, 2019

This is resolved. Closing.

@agnivade agnivade closed this as completed Dec 6, 2019
@golang golang locked and limited conversation to collaborators Dec 5, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
arch-wasm WebAssembly issues FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants