]> Cypherpunks repositories - gostls13.git/commitdiff
syscall/js: show goroutine stack traces on deadlock
authorRichard Musiol <mail@richard-musiol.de>
Sat, 14 Jul 2018 10:19:36 +0000 (12:19 +0200)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 19 Jul 2018 19:00:52 +0000 (19:00 +0000)
When using callbacks, it is not necessarily a deadlock if there is no
runnable goroutine, since a callback might still be pending. If there
is no callback pending, Node.js simply exits with exit code zero,
which is not desired if the Go program is still considered running.
This is why an explicit check on exit is used to trigger the "deadlock"
error. This CL makes it so this is Go's normal "deadlock" error, which
includes the stack traces of all goroutines.

Updates #26382

Change-Id: If88486684d0517a64f570009a5ea0ad082679a54
Reviewed-on: https://go-review.googlesource.com/123936
Run-TryBot: Richard Musiol <neelance@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
misc/wasm/wasm_exec.js
src/syscall/js/callback.go
src/syscall/js/js.go

index 233c5aa1877a6b28916ba767527b160f3c60d161..02a753c823036047e5474f427d4a34181bed7f02 100644 (file)
                                false,
                                global,
                                this._inst.exports.mem,
-                               () => { // resolveCallbackPromise
-                                       if (this.exited) {
-                                               throw new Error("bad callback: Go program has already exited");
-                                       }
-                                       setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous
-                               },
+                               this,
                        ];
                        this._refs = new Map();
+                       this._callbackShutdown = false;
                        this.exited = false;
 
                        const mem = new DataView(this._inst.exports.mem.buffer)
 
                        while (true) {
                                const callbackPromise = new Promise((resolve) => {
-                                       this._resolveCallbackPromise = resolve;
+                                       this._resolveCallbackPromise = () => {
+                                               if (this.exited) {
+                                                       throw new Error("bad callback: Go program has already exited");
+                                               }
+                                               setTimeout(resolve, 0); // make sure it is asynchronous
+                                       };
                                });
                                this._inst.exports.run(argc, argv);
                                if (this.exited) {
                go.env = process.env;
                go.exit = process.exit;
                WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
-                       process.on("exit", () => { // Node.js exits if no callback is pending
-                               if (!go.exited) {
-                                       console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!");
-                                       process.exit(1);
+                       process.on("exit", (code) => { // Node.js exits if no callback is pending
+                               if (code === 0 && !go.exited) {
+                                       // deadlock, make Go print error and stack traces
+                                       go._callbackShutdown = true;
+                                       go._inst.exports.run();
                                }
                        });
                        return go.run(result.instance);
                }).catch((err) => {
-                       console.error(err);
-                       go.exited = true;
-                       process.exit(1);
+                       throw err;
                });
        }
 })();
index fa8a03ab0c3997210d9c1ecad1a867b179c3b2bf..de9da888fd9e03219131f9454478846d2f67f8ae 100644 (file)
@@ -11,10 +11,10 @@ import "sync"
 var pendingCallbacks = Global().Get("Array").New()
 
 var makeCallbackHelper = Global().Call("eval", `
-       (function(id, pendingCallbacks, resolveCallbackPromise) {
+       (function(id, pendingCallbacks, go) {
                return function() {
                        pendingCallbacks.push({ id: id, args: arguments });
-                       resolveCallbackPromise();
+                       go._resolveCallbackPromise();
                };
        })
 `)
@@ -71,7 +71,7 @@ func NewCallback(fn func(args []Value)) Callback {
        callbacks[id] = fn
        callbacksMu.Unlock()
        return Callback{
-               Value: makeCallbackHelper.Invoke(id, pendingCallbacks, resolveCallbackPromise),
+               Value: makeCallbackHelper.Invoke(id, pendingCallbacks, jsGo),
                id:    id,
        }
 }
@@ -116,7 +116,7 @@ func (c Callback) Release() {
 var callbackLoopOnce sync.Once
 
 func callbackLoop() {
-       for {
+       for !jsGo.Get("_callbackShutdown").Bool() {
                sleepUntilCallback()
                for {
                        cb := pendingCallbacks.Call("shift")
index 0cc98bd52c631bd6803c1b798c24eba200d6adb0..4b55193c4126c277f32d9363db4c233315040817 100644 (file)
@@ -56,14 +56,14 @@ func (e Error) Error() string {
 }
 
 var (
-       valueNaN               = predefValue(0)
-       valueUndefined         = predefValue(1)
-       valueNull              = predefValue(2)
-       valueTrue              = predefValue(3)
-       valueFalse             = predefValue(4)
-       valueGlobal            = predefValue(5)
-       memory                 = predefValue(6) // WebAssembly linear memory
-       resolveCallbackPromise = predefValue(7) // function that the callback helper uses to resume the execution of Go's WebAssembly code
+       valueNaN       = predefValue(0)
+       valueUndefined = predefValue(1)
+       valueNull      = predefValue(2)
+       valueTrue      = predefValue(3)
+       valueFalse     = predefValue(4)
+       valueGlobal    = predefValue(5)
+       memory         = predefValue(6) // WebAssembly linear memory
+       jsGo           = predefValue(7) // instance of the Go class in JavaScript
 )
 
 // Undefined returns the JavaScript value "undefined".