-
Notifications
You must be signed in to change notification settings - Fork 569
gopherjs deadlock with mqtt/websocket library port #1106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks for the detailed report, I was able to reproduce it. I'll see if I can find the root cause. |
@Bluebugs I can confirm this is a GopherJS compiler bug. Your intuition was spot on — the bug happens when Let's look at what this function compiled into: func (i *impl) Do() {
select {
default:
}
} impl.ptr.prototype.Do = function() {
var _selection, i, $r;
// Restore function context if the goroutine is being resumed:
/* */ var $f, $c = false; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; _selection = $f._selection; i = $f.i; $r = $f.$r; }
i = this;
_selection = $select([[]]);
if (_selection[0] === 0) {
}
// Checkpoint function context if the goroutine is blocked. But we actually do it unconditionally?
/* */ if ($f === undefined) { $f = { $blk: impl.ptr.prototype.Do }; } $f._selection = _selection; $f.i = i; $f.$r = $r; return $f;
};
}; Note the lines at the beginning and the end of the compiled function. These are typical prologue and epilogue of a GopherJS function that may get blocked and needs to checkpoint/restore its state. JavaScript runtime doesn't allow blocking functions, but Go very much does, so GopherJS has to emulate it by detecting when the function has blocked, saving local variable state and returning early. Once blocking condition passed, the function will be called again, restore its context and continue execution. What seems suspicious here is that function Besides, depending on how we call the function (directly or via an interface) causes the bug to happen or not. Let's look at the call site. Called directly as a method of ii.Do(); Called via an interface: $r = ii.Do(); /* */ $s = 1; case 1: if($c) { $c = false; $r = $r.$blk(); } if ($r && $r.$blk !== undefined) { break s; } This is actually interesting. When So what we observe here is that the I'll need more time to investigate why the compiler inserts checkpointing logic where it shouldn't, but luckily there is an easy workaround: adding an explicit func (i *impl) Do() {
select {
default:
}
return
} In this case generated code looks like so: impl.ptr.prototype.Do = function() {
var _selection, i, $r;
/* */ var $f, $c = false; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; _selection = $f._selection; i = $f.i; $r = $f.$r; }
i = this;
_selection = $select([[]]);
if (_selection[0] === 0) {
}
return;
// The line below is never executed, bug avoided.
/* */ if ($f === undefined) { $f = { $blk: impl.ptr.prototype.Do }; } $f._selection = _selection; $f.i = i; $f.$r = $r; return $f;
}; |
In almost every place compiler checks whether a function is blocking by checking `len(c.Blocking) > 0`, so assigning false to the map confuses the check, causing unnecessary checkpointing prologue and epilogue to be added to the function. Fixes gopherjs#1106.
Actually, never mind, the fix turned out to be simpler than I though: #1108. |
In almost every place compiler checks whether a function is blocking by checking `len(c.Blocking) > 0`, so assigning false to the map confuses the check, causing unnecessary checkpointing prologue and epilogue to be added to the function. Fixes gopherjs#1106.
Thanks for the quick fix and the detailed explanation of how gopherjs work, very much appreciated! |
Trying gopherjs with mqtt/websocket in both Chrome and Firefox as in this branch: https://github.com/Bluebugs/paho.mqtt.golang/tree/bugs/gopherjs-deadlock lead to a deadlock in the following function: https://github.com/Bluebugs/paho.mqtt.golang/blob/bugs/gopherjs-deadlock/token.go#L101 when called from https://github.com/Bluebugs/paho.mqtt.golang/blob/bugs/gopherjs-deadlock/net.go#L216 .
I tried to write a test that would trigger the problem, but couldn't figure out a way for gopherjs test to execute it as it seems some syscall are missing in that case when doing websocket related traffic.
Instead I added a small example in cmd/websocket that work fine with go run or inside a browser with go wasm target, but fail with gopherjs serve and endup in a dead lock. It use mosquitto public mqtt server which provide websocket interface.
A working output would look like:
While a non working one will look like :
As you can see in the non working case, it never go into
[net] logic waiting for msg on ibound
after[net] startIncomingComms: granted qoss [1]
.The text was updated successfully, but these errors were encountered: