Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
messaging: fix edge cases with transferring ports
Currently, transferring the port on which postMessage is called causes a segmentation fault, and transferring the target port causes a subsequent port.onmessage setting to throw, or a deadlock if onmessage is set before the postMessage. Fix both of these behaviors and align the methods more closely with the normative definitions in the HTML Standard. Also, per spec postMessage must not throw just because the ports are disentangled. Implement that behavior. PR-URL: #21540 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
- Loading branch information
Showing
with
213 additions
and 28 deletions.
@@ -0,0 +1,54 @@ | ||
// Flags: --experimental-worker | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const { MessageChannel } = require('worker_threads'); | ||
|
||
// This tests various behaviors around transferring MessagePorts with closing | ||
// or closed handles. | ||
|
||
const { port1, port2 } = new MessageChannel(); | ||
|
||
const arrayBuf = new ArrayBuffer(10); | ||
port1.onmessage = common.mustNotCall(); | ||
port2.onmessage = common.mustNotCall(); | ||
|
||
function testSingle(closedPort, potentiallyOpenPort) { | ||
assert.throws(common.mustCall(() => { | ||
potentiallyOpenPort.postMessage(null, [arrayBuf, closedPort]); | ||
}), common.mustCall((err) => { | ||
assert.strictEqual(err.name, 'DataCloneError'); | ||
assert.strictEqual(err.message, | ||
'MessagePort in transfer list is already detached'); | ||
assert.strictEqual(err.code, 25); | ||
assert.ok(err instanceof Error); | ||
|
||
const DOMException = err.constructor; | ||
assert.ok(err instanceof DOMException); | ||
assert.strictEqual(DOMException.name, 'DOMException'); | ||
|
||
return true; | ||
})); | ||
|
||
// arrayBuf must not be transferred, even though it is present earlier in the | ||
// transfer list than the closedPort. | ||
assert.strictEqual(arrayBuf.byteLength, 10); | ||
} | ||
|
||
function testBothClosed() { | ||
testSingle(port1, port2); | ||
testSingle(port2, port1); | ||
} | ||
|
||
// Even though the port handles may not be completely closed in C++ land, the | ||
// observable behavior must be that the closing/detachment is synchronous and | ||
// instant. | ||
|
||
port1.close(common.mustCall(testBothClosed)); | ||
testSingle(port1, port2); | ||
port2.close(common.mustCall(testBothClosed)); | ||
testBothClosed(); | ||
|
||
setTimeout(common.mustNotCall('The communication channel is still open'), | ||
common.platformTimeout(1000)).unref(); |
@@ -0,0 +1,33 @@ | ||
// Flags: --experimental-worker | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const { MessageChannel } = require('worker_threads'); | ||
|
||
const { port1, port2 } = new MessageChannel(); | ||
|
||
assert.throws(common.mustCall(() => { | ||
port1.postMessage(null, [port1]); | ||
}), common.mustCall((err) => { | ||
assert.strictEqual(err.name, 'DataCloneError'); | ||
assert.strictEqual(err.message, 'Transfer list contains source port'); | ||
assert.strictEqual(err.code, 25); | ||
assert.ok(err instanceof Error); | ||
|
||
const DOMException = err.constructor; | ||
assert.ok(err instanceof DOMException); | ||
assert.strictEqual(DOMException.name, 'DOMException'); | ||
|
||
return true; | ||
})); | ||
|
||
// The failed transfer should not affect the ports in anyway. | ||
port2.onmessage = common.mustCall((message) => { | ||
assert.strictEqual(message, 2); | ||
port1.close(); | ||
|
||
setTimeout(common.mustNotCall('The communication channel is still open'), | ||
common.platformTimeout(1000)).unref(); | ||
}); | ||
port1.postMessage(2); |
@@ -0,0 +1,24 @@ | ||
// Flags: --experimental-worker | ||
'use strict'; | ||
|
||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const { MessageChannel } = require('worker_threads'); | ||
|
||
const { port1, port2 } = new MessageChannel(); | ||
|
||
const arrayBuf = new ArrayBuffer(10); | ||
|
||
common.expectWarning('Warning', | ||
'The target port was posted to itself, and the ' + | ||
'communication channel was lost', | ||
common.noWarnCode); | ||
port2.onmessage = common.mustNotCall(); | ||
port2.postMessage(null, [port1, arrayBuf]); | ||
|
||
// arrayBuf must be transferred, despite the fact that port2 never received the | ||
// message. | ||
assert.strictEqual(arrayBuf.byteLength, 0); | ||
|
||
setTimeout(common.mustNotCall('The communication channel is still open'), | ||
common.platformTimeout(1000)).unref(); |