Repository: denoland/deno
Pull request: fix(node/worker_threads): support `port.once()`

Support `MessagePort.once` in Node mode and enable relevant `worker_threads` test. Noticed that another Node test was passing as well, so I enabled that too.

Changed files:
filename: ext/node/polyfills/worker_threads.ts
patch: @@ -536,6 +536,13 @@ function webMessagePortToNodeMessagePort(port: MessagePort) {
   port.ref = () => {
     port[refMessagePort](true);
   };
+  port.once = (name: string | symbol, listener) => {
+    const fn = (event) => {
+      port.off(name, fn);
+      return listener(event);
+    };
+    port.on(name, fn);
+  };
   return port;
 }
 

filename: tests/node_compat/config.jsonc
patch: @@ -684,6 +684,8 @@
       "test-whatwg-url-custom-tostringtag.js",
       "test-whatwg-url-override-hostname.js",
       "test-whatwg-url-properties.js",
+      "test-worker-message-port-infinite-message-loop.js",
+      "test-worker-message-port-multiple-sharedarraybuffers.js",
       "test-worker-message-port-receive-message.js",
       "test-zlib-close-after-error.js",
       "test-zlib-close-after-write.js",

filename: tests/node_compat/runner/TODO.md
patch: @@ -2829,13 +2829,11 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
 - [parallel/test-worker-message-port-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-close.js)
 - [parallel/test-worker-message-port-constructor.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-constructor.js)
 - [parallel/test-worker-message-port-drain.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-drain.js)
-- [parallel/test-worker-message-port-infinite-message-loop.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-infinite-message-loop.js)
 - [parallel/test-worker-message-port-inspect-during-init-hook.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-inspect-during-init-hook.js)
 - [parallel/test-worker-message-port-jstransferable-nested-untransferable.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-jstransferable-nested-untransferable.js)
 - [parallel/test-worker-message-port-message-before-close.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-message-before-close.js)
 - [parallel/test-worker-message-port-message-port-transferring.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-message-port-transferring.js)
 - [parallel/test-worker-message-port-move.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-move.js)
-- [parallel/test-worker-message-port-multiple-sharedarraybuffers.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-multiple-sharedarraybuffers.js)
 - [parallel/test-worker-message-port-terminate-transfer-list.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-terminate-transfer-list.js)
 - [parallel/test-worker-message-port-transfer-closed.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-transfer-closed.js)
 - [parallel/test-worker-message-port-transfer-duplicate.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-worker-message-port-transfer-duplicate.js)

filename: tests/node_compat/test/parallel/test-worker-message-port-infinite-message-loop.js
patch: @@ -0,0 +1,36 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
+
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+
+const { MessageChannel } = require('worker_threads');
+
+// Make sure that an infinite asynchronous .on('message')/postMessage loop
+// does not lead to a stack overflow and does not starve the event loop.
+// We schedule timeouts both from before the .on('message') handler and
+// inside of it, which both should run.
+
+const { port1, port2 } = new MessageChannel();
+let count = 0;
+port1.on('message', () => {
+  if (count === 0) {
+    setTimeout(common.mustCall(() => {
+      port1.close();
+    }), 0);
+  }
+
+  port2.postMessage(0);
+  assert(count++ < 10000, `hit ${count} loop iterations`);
+});
+
+port2.postMessage(0);
+
+// This is part of the test -- the event loop should be available and not stall
+// out due to the recursive .postMessage() calls.
+setTimeout(common.mustCall(), 0);

filename: tests/node_compat/test/parallel/test-worker-message-port-multiple-sharedarraybuffers.js
patch: @@ -0,0 +1,24 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
+
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+const { MessageChannel } = require('worker_threads');
+
+// Regression test for https://github.com/nodejs/node/issues/28559
+
+const obj = [
+  [ new SharedArrayBuffer(0), new SharedArrayBuffer(1) ],
+  [ new SharedArrayBuffer(2), new SharedArrayBuffer(3) ],
+];
+
+const { port1, port2 } = new MessageChannel();
+port1.once('message', common.mustCall((message) => {
+  assert.deepStrictEqual(message, obj);
+}));
+port2.postMessage(obj);