mirror of
https://github.com/golang/go
synced 2024-11-23 05:30:07 -07:00
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: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
ca642bb326
commit
fec97c0aa7
@ -333,14 +333,10 @@
|
|||||||
false,
|
false,
|
||||||
global,
|
global,
|
||||||
this._inst.exports.mem,
|
this._inst.exports.mem,
|
||||||
() => { // resolveCallbackPromise
|
this,
|
||||||
if (this.exited) {
|
|
||||||
throw new Error("bad callback: Go program has already exited");
|
|
||||||
}
|
|
||||||
setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
this._refs = new Map();
|
this._refs = new Map();
|
||||||
|
this._callbackShutdown = false;
|
||||||
this.exited = false;
|
this.exited = false;
|
||||||
|
|
||||||
const mem = new DataView(this._inst.exports.mem.buffer)
|
const mem = new DataView(this._inst.exports.mem.buffer)
|
||||||
@ -377,7 +373,12 @@
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const callbackPromise = new Promise((resolve) => {
|
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);
|
this._inst.exports.run(argc, argv);
|
||||||
if (this.exited) {
|
if (this.exited) {
|
||||||
@ -399,17 +400,16 @@
|
|||||||
go.env = process.env;
|
go.env = process.env;
|
||||||
go.exit = process.exit;
|
go.exit = process.exit;
|
||||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
||||||
process.on("exit", () => { // Node.js exits if no callback is pending
|
process.on("exit", (code) => { // Node.js exits if no callback is pending
|
||||||
if (!go.exited) {
|
if (code === 0 && !go.exited) {
|
||||||
console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!");
|
// deadlock, make Go print error and stack traces
|
||||||
process.exit(1);
|
go._callbackShutdown = true;
|
||||||
|
go._inst.exports.run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return go.run(result.instance);
|
return go.run(result.instance);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err);
|
throw err;
|
||||||
go.exited = true;
|
|
||||||
process.exit(1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -11,10 +11,10 @@ import "sync"
|
|||||||
var pendingCallbacks = Global().Get("Array").New()
|
var pendingCallbacks = Global().Get("Array").New()
|
||||||
|
|
||||||
var makeCallbackHelper = Global().Call("eval", `
|
var makeCallbackHelper = Global().Call("eval", `
|
||||||
(function(id, pendingCallbacks, resolveCallbackPromise) {
|
(function(id, pendingCallbacks, go) {
|
||||||
return function() {
|
return function() {
|
||||||
pendingCallbacks.push({ id: id, args: arguments });
|
pendingCallbacks.push({ id: id, args: arguments });
|
||||||
resolveCallbackPromise();
|
go._resolveCallbackPromise();
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
`)
|
`)
|
||||||
@ -71,7 +71,7 @@ func NewCallback(fn func(args []Value)) Callback {
|
|||||||
callbacks[id] = fn
|
callbacks[id] = fn
|
||||||
callbacksMu.Unlock()
|
callbacksMu.Unlock()
|
||||||
return Callback{
|
return Callback{
|
||||||
Value: makeCallbackHelper.Invoke(id, pendingCallbacks, resolveCallbackPromise),
|
Value: makeCallbackHelper.Invoke(id, pendingCallbacks, jsGo),
|
||||||
id: id,
|
id: id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@ func (c Callback) Release() {
|
|||||||
var callbackLoopOnce sync.Once
|
var callbackLoopOnce sync.Once
|
||||||
|
|
||||||
func callbackLoop() {
|
func callbackLoop() {
|
||||||
for {
|
for !jsGo.Get("_callbackShutdown").Bool() {
|
||||||
sleepUntilCallback()
|
sleepUntilCallback()
|
||||||
for {
|
for {
|
||||||
cb := pendingCallbacks.Call("shift")
|
cb := pendingCallbacks.Call("shift")
|
||||||
|
@ -56,14 +56,14 @@ func (e Error) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
valueNaN = predefValue(0)
|
valueNaN = predefValue(0)
|
||||||
valueUndefined = predefValue(1)
|
valueUndefined = predefValue(1)
|
||||||
valueNull = predefValue(2)
|
valueNull = predefValue(2)
|
||||||
valueTrue = predefValue(3)
|
valueTrue = predefValue(3)
|
||||||
valueFalse = predefValue(4)
|
valueFalse = predefValue(4)
|
||||||
valueGlobal = predefValue(5)
|
valueGlobal = predefValue(5)
|
||||||
memory = predefValue(6) // WebAssembly linear memory
|
memory = predefValue(6) // WebAssembly linear memory
|
||||||
resolveCallbackPromise = predefValue(7) // function that the callback helper uses to resume the execution of Go's WebAssembly code
|
jsGo = predefValue(7) // instance of the Go class in JavaScript
|
||||||
)
|
)
|
||||||
|
|
||||||
// Undefined returns the JavaScript value "undefined".
|
// Undefined returns the JavaScript value "undefined".
|
||||||
|
Loading…
Reference in New Issue
Block a user