fix(node): fix worker_threads issues blocking Angular support #26024
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #22995. Fixes #23000.
There were a handful of bugs here causing the hang (each with a corresponding minimized test):
We were canceling recv futures when
receiveMessageOnPort
was called, but this caused the "receive loop" in the message port to exit. This was due to the fact thatCancelHandle
s are never reset (i.e., once youcancel
aCancelHandle
, it remains cancelled). That meant that afterreceieveMessageOnPort
was called, the subsequent calls toop_message_port_recv_message
would throwInterrupted
exceptions, and we would exit the loop.The cancellation, however, isn't actually necessary.
op_message_port_recv_message
only borrows the underlying port for long enough to poll the receiver, so the borrow there could never overlap withop_message_port_recv_message_sync
.Calling
MessagePort.unref()
caused the "receive loop" in the message port to exit. This was because we were settingmessageEventListenerCount
to 0 on unref. Not only does that break the counter when multipleMessagePort
s are present in the same thread, but we also exited the "receive loop" whenever the listener count was 0. I assume this was to prevent the recv promise from keeping the event loop open.Instead of this, I chose to just unref the recv promise as needed to control the event loop.
The last bug causing the hang (which was a doozy to debug) ended up being an unfortunate interaction between how we implement our messageport "receive loop" and a pattern found in
npm:piscina
(which angular uses). The gist of it is that piscina uses an atomic wait loop along withreceiveMessageOnPort
in its worker threads, and as the worker is getting started, the following incredibly convoluted series of events occurs:p
to workerm
to the portp
Atomics.notify
that a new message is availablep
MessagePort.start()
onp
m
, but then hits an await point and yields (before dispatching the "message" event)m
withreceiveMessageOnPort
, but this returnsundefined
because the receive loop already took the message in 6Atomic.wait
Atomic.wait
blocks the worker thread, which prevents the receive loop from continuing and dispatching the "message" event for the received messageThe fix I've chosen here (which I don't particularly love, but it works) is to just delay the
MessagePort.start
call until the end of the event loop turn, so that the atomic wait loop receives the message first. This prevents the hang.Those were the main issues causing the hang. There ended up being a few other small bugs as well, namely
exit
being emitted multiple times, and not patching up the message port when it's received byreceiveMessageOnPort
.