go version)?$ go version go version go1.15 darwin/amd64
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"
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>
Function running succesfully
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
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.
Thanks @agnivade for clarification.
@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?
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.
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 :smile:), but am wondering if you can offer any advice to someone new to Go. Thanks.
You can try wrapping repo.Clone() call in a seperate go routine.
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?
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.
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
}
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.
Most helpful comment
This is documented here: https://golang.org/pkg/syscall/js/#FuncOf
You would need to perform the
http.Getin a separate goroutine.