Commit fec97c0a authored by Richard Musiol's avatar Richard Musiol Committed by Brad Fitzpatrick

syscall/js: show goroutine stack traces on deadlock

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: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent ca642bb3
......@@ -333,14 +333,10 @@
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)
......@@ -377,7 +373,12 @@
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) {
......@@ -399,17 +400,16 @@
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;
});
}
})();
......@@ -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")
......
......@@ -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".
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment