Version: go1.13 windows/amd64
Does this issue reproduce with the latest release? Yes
go env Output
set GO111MODULE=
set GOARCH=wasm
set GOBIN=
set GOCACHE=C:\Users\danau\AppData\Local\go-build
set GOENV=C:\Users\danau\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GONOPROXY=
set GONOSUMDB=
set GOOS=js
set GOPATH=C:\projects\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=c:\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=c:\go\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\danau\AppData\Local\Temp\go-build158127038=/tmp/go-build -gno-record-gcc-switches
package main
import (
"fmt"
"syscall/js"
)
func doStuffSynchronously() {
done := make(chan bool)
f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fmt.Println("DOING STUFF")
done <- true
return nil
})
defer f.Release()
js.Global().Call("setTimeout", f, 1000)
<-done
fmt.Println("DONE DOING STUFF")
}
func main() {
doStuffSynchronously() // <- WORKS HERE
cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fmt.Println("CB CALLED")
doStuffSynchronously() // <- CRASHES WITH "fatal error: all goroutines are asleep - deadlock!"
return nil
})
defer cb.Release()
js.Global().Call("setTimeout", cb, 1000)
// Prevent app from finishing while we wait for callback to execute
neverDone := make(chan bool)
<-neverDone
}
I expected doStuffSynchronously() to work inside a callback just like it works outside one.
fatal error: all goroutines are asleep - deadlock!
As https://golang.org/pkg/syscall/js/#FuncOf says:
Blocking operations in the wrapped function will block the event loop. As a consequence, if one wrapped function blocks, other wrapped funcs will not be processed. A blocking function should therefore explicitly start a new goroutine.
currently, blocking operation, such as channel send, doesn't work in callbacks.
@cherrymui Thanks for the fast reply! If I start a new goroutine go doStuffSynchronously() is it guaranteed that it will be executed sequentially (i.e. the go syntax is only necessary due to limitations in the WASM implementation)?
I think it is not guaranteed to be executed sequentially, just like the go statement in general.
@cherrymui Yeah, just tried it and it's async - is there any workaround?
There is none at the moment. If you are making any call to JS land from inside a wrapped function, it's a deadlock. You have to change your programming model accordingly.
@cherrymui, @neelance: should we keep this issue open to track a future improvement to relax this restriction, or is it going to remain this way for the foreseeable future?
@bcmills I don't see any way to relax this restriction. setTimeout can only fire its callback if the event loop continues to run. The documentation may get improved by https://github.com/golang/go/issues/34324#issuecomment-534272747. Closing this issue.
@danaugrs Btw: You can use select {} instead of neverDone.
Most helpful comment
@bcmills I don't see any way to relax this restriction.
setTimeoutcan only fire its callback if the event loop continues to run. The documentation may get improved by https://github.com/golang/go/issues/34324#issuecomment-534272747. Closing this issue.@danaugrs Btw: You can use
select {}instead ofneverDone.