Skip to content

Commit fec97c0

Browse files
neelancebradfitz
authored andcommitted
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 golang#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>
1 parent ca642bb commit fec97c0

File tree

3 files changed

+26
-26
lines changed

3 files changed

+26
-26
lines changed

misc/wasm/wasm_exec.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -333,14 +333,10 @@
333333
false,
334334
global,
335335
this._inst.exports.mem,
336-
() => { // resolveCallbackPromise
337-
if (this.exited) {
338-
throw new Error("bad callback: Go program has already exited");
339-
}
340-
setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous
341-
},
336+
this,
342337
];
343338
this._refs = new Map();
339+
this._callbackShutdown = false;
344340
this.exited = false;
345341

346342
const mem = new DataView(this._inst.exports.mem.buffer)
@@ -377,7 +373,12 @@
377373

378374
while (true) {
379375
const callbackPromise = new Promise((resolve) => {
380-
this._resolveCallbackPromise = resolve;
376+
this._resolveCallbackPromise = () => {
377+
if (this.exited) {
378+
throw new Error("bad callback: Go program has already exited");
379+
}
380+
setTimeout(resolve, 0); // make sure it is asynchronous
381+
};
381382
});
382383
this._inst.exports.run(argc, argv);
383384
if (this.exited) {
@@ -399,17 +400,16 @@
399400
go.env = process.env;
400401
go.exit = process.exit;
401402
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
402-
process.on("exit", () => { // Node.js exits if no callback is pending
403-
if (!go.exited) {
404-
console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!");
405-
process.exit(1);
403+
process.on("exit", (code) => { // Node.js exits if no callback is pending
404+
if (code === 0 && !go.exited) {
405+
// deadlock, make Go print error and stack traces
406+
go._callbackShutdown = true;
407+
go._inst.exports.run();
406408
}
407409
});
408410
return go.run(result.instance);
409411
}).catch((err) => {
410-
console.error(err);
411-
go.exited = true;
412-
process.exit(1);
412+
throw err;
413413
});
414414
}
415415
})();

src/syscall/js/callback.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import "sync"
1111
var pendingCallbacks = Global().Get("Array").New()
1212

1313
var makeCallbackHelper = Global().Call("eval", `
14-
(function(id, pendingCallbacks, resolveCallbackPromise) {
14+
(function(id, pendingCallbacks, go) {
1515
return function() {
1616
pendingCallbacks.push({ id: id, args: arguments });
17-
resolveCallbackPromise();
17+
go._resolveCallbackPromise();
1818
};
1919
})
2020
`)
@@ -71,7 +71,7 @@ func NewCallback(fn func(args []Value)) Callback {
7171
callbacks[id] = fn
7272
callbacksMu.Unlock()
7373
return Callback{
74-
Value: makeCallbackHelper.Invoke(id, pendingCallbacks, resolveCallbackPromise),
74+
Value: makeCallbackHelper.Invoke(id, pendingCallbacks, jsGo),
7575
id: id,
7676
}
7777
}
@@ -116,7 +116,7 @@ func (c Callback) Release() {
116116
var callbackLoopOnce sync.Once
117117

118118
func callbackLoop() {
119-
for {
119+
for !jsGo.Get("_callbackShutdown").Bool() {
120120
sleepUntilCallback()
121121
for {
122122
cb := pendingCallbacks.Call("shift")

src/syscall/js/js.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ func (e Error) Error() string {
5656
}
5757

5858
var (
59-
valueNaN = predefValue(0)
60-
valueUndefined = predefValue(1)
61-
valueNull = predefValue(2)
62-
valueTrue = predefValue(3)
63-
valueFalse = predefValue(4)
64-
valueGlobal = predefValue(5)
65-
memory = predefValue(6) // WebAssembly linear memory
66-
resolveCallbackPromise = predefValue(7) // function that the callback helper uses to resume the execution of Go's WebAssembly code
59+
valueNaN = predefValue(0)
60+
valueUndefined = predefValue(1)
61+
valueNull = predefValue(2)
62+
valueTrue = predefValue(3)
63+
valueFalse = predefValue(4)
64+
valueGlobal = predefValue(5)
65+
memory = predefValue(6) // WebAssembly linear memory
66+
jsGo = predefValue(7) // instance of the Go class in JavaScript
6767
)
6868

6969
// Undefined returns the JavaScript value "undefined".

0 commit comments

Comments
 (0)