-
Notifications
You must be signed in to change notification settings - Fork 29.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
worker: use special message as MessagePort close command
When a `MessagePort` connected to another `MessagePort` closes, the latter `MessagePort` will be closed as well. Until now, this is done by testing whether the ports are still entangled after processing messages. This leaves open a race condition window in which messages sent just before the closure can be lost when timing is unfortunate. (A description of the timing is in the test file.) This can be addressed by using a special message instead, which is the last message received by a `MessagePort`. This way, all previously sent messages are processed first. Fixes: #22762 PR-URL: #27705 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
- Loading branch information
Showing
3 changed files
with
71 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
test/parallel/test-worker-message-port-message-before-close.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const { once } = require('events'); | ||
const { Worker, MessageChannel } = require('worker_threads'); | ||
|
||
// This is a regression test for the race condition underlying | ||
// https://github.com/nodejs/node/issues/22762. | ||
// It ensures that all messages send before a MessagePort#close() call are | ||
// received. Previously, what could happen was a race condition like this: | ||
// - Thread 1 sends message A | ||
// - Thread 2 begins receiving/emitting message A | ||
// - Thread 1 sends message B | ||
// - Thread 1 closes its side of the channel | ||
// - Thread 2 finishes receiving/emitting message A | ||
// - Thread 2 sees that the port should be closed | ||
// - Thread 2 closes the port, discarding message B in the process. | ||
|
||
async function test() { | ||
const worker = new Worker(` | ||
require('worker_threads').parentPort.on('message', ({ port }) => { | ||
port.postMessage('firstMessage'); | ||
port.postMessage('lastMessage'); | ||
port.close(); | ||
}); | ||
`, { eval: true }); | ||
|
||
for (let i = 0; i < 10000; i++) { | ||
const { port1, port2 } = new MessageChannel(); | ||
worker.postMessage({ port: port2 }, [ port2 ]); | ||
await once(port1, 'message'); // 'complexObject' | ||
assert.deepStrictEqual(await once(port1, 'message'), ['lastMessage']); | ||
} | ||
|
||
worker.terminate(); | ||
} | ||
|
||
test().then(common.mustCall()); |