Skip to content

Commit bf9240a

Browse files
RaisinTentargos
authored andcommitted
worker: add hasRef() to MessagePort
Since we were removing the hasRef() method before exposing the MessagePort object, the only way of knowing if the handle was keeping the event loop active was to parse the string returned by util.inspect(port), which is inconvenient and inconsistent with most of the other async resources. So this change stops removing hasRef() from the MessagePort prototype. The reason why this is also being documented is that while reporting active resources, async_hooks returns the same MessagePort object as the one that is accessible by users. Refs: #42091 (comment) Signed-off-by: Darshan Sen <raisinten@gmail.com> PR-URL: #42849 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 1e7479d commit bf9240a

File tree

5 files changed

+120
-3
lines changed

5 files changed

+120
-3
lines changed

doc/api/worker_threads.md

+12
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,18 @@ port2.postMessage(new URL('https://example.org'));
763763
// Prints: { }
764764
```
765765

766+
### `port.hasRef()`
767+
768+
<!-- YAML
769+
added: REPLACEME
770+
-->
771+
772+
> Stability: 1 - Experimental
773+
774+
* Returns: {boolean}
775+
776+
If true, the `MessagePort` object will keep the Node.js event loop active.
777+
766778
### `port.ref()`
767779

768780
<!-- YAML

lib/internal/worker/io.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const messageTypes = {
9191
// We have to mess with the MessagePort prototype a bit, so that a) we can make
9292
// it inherit from NodeEventTarget, even though it is a C++ class, and b) we do
9393
// not provide methods that are not present in the Browser and not documented
94-
// on our side (e.g. hasRef).
94+
// on our side (e.g. stopMessagePort).
9595
// Save a copy of the original set of methods as a shallow clone.
9696
const MessagePortPrototype = ObjectCreate(
9797
ObjectGetPrototypeOf(MessagePort.prototype),
@@ -103,6 +103,9 @@ ObjectSetPrototypeOf(MessagePort.prototype, NodeEventTarget.prototype);
103103
// changing the prototype of MessagePort.prototype implicitly removed them.
104104
MessagePort.prototype.ref = MessagePortPrototype.ref;
105105
MessagePort.prototype.unref = MessagePortPrototype.unref;
106+
MessagePort.prototype.hasRef = function() {
107+
return !!FunctionPrototypeCall(MessagePortPrototype.hasRef, this);
108+
};
106109

107110
function validateMessagePort(port, name) {
108111
if (!checkMessagePort(port))
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
const { MessageChannel } = require('worker_threads');
5+
const { createHook } = require('async_hooks');
6+
const { strictEqual } = require('assert');
7+
8+
const handles = [];
9+
10+
createHook({
11+
init(asyncId, type, triggerAsyncId, resource) {
12+
if (type === 'MESSAGEPORT') {
13+
handles.push(resource);
14+
}
15+
}
16+
}).enable();
17+
18+
const { port1, port2 } = new MessageChannel();
19+
strictEqual(handles[0], port1);
20+
strictEqual(handles[1], port2);
21+
22+
strictEqual(handles[0].hasRef(), false);
23+
strictEqual(handles[1].hasRef(), false);
24+
25+
port1.unref();
26+
strictEqual(handles[0].hasRef(), false);
27+
28+
port1.ref();
29+
strictEqual(handles[0].hasRef(), true);
30+
31+
port1.unref();
32+
strictEqual(handles[0].hasRef(), false);
33+
34+
port1.on('message', () => {});
35+
strictEqual(handles[0].hasRef(), true);
36+
37+
port2.unref();
38+
strictEqual(handles[1].hasRef(), false);
39+
40+
port2.ref();
41+
strictEqual(handles[1].hasRef(), true);
42+
43+
port2.unref();
44+
strictEqual(handles[1].hasRef(), false);
45+
46+
port2.on('message', () => {});
47+
strictEqual(handles[0].hasRef(), true);
48+
49+
port1.on('close', common.mustCall(() => {
50+
strictEqual(handles[0].hasRef(), false);
51+
strictEqual(handles[1].hasRef(), false);
52+
}));
53+
54+
port2.close();
55+
56+
strictEqual(handles[0].hasRef(), true);
57+
strictEqual(handles[1].hasRef(), true);

test/parallel/test-worker-message-port.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ const { MessageChannel, MessagePort } = require('worker_threads');
179179
assert.deepStrictEqual(
180180
Object.getOwnPropertyNames(MessagePort.prototype).sort(),
181181
[
182-
'close', 'constructor', 'onmessage', 'onmessageerror', 'postMessage',
183-
'ref', 'start', 'unref',
182+
'close', 'constructor', 'hasRef', 'onmessage', 'onmessageerror',
183+
'postMessage', 'ref', 'start', 'unref',
184184
]);
185185
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
const { Worker } = require('worker_threads');
5+
const { createHook } = require('async_hooks');
6+
const { deepStrictEqual, strictEqual } = require('assert');
7+
8+
const m = new Map();
9+
createHook({
10+
init(asyncId, type, triggerAsyncId, resource) {
11+
if (['WORKER', 'MESSAGEPORT'].includes(type)) {
12+
m.set(asyncId, { type, resource });
13+
}
14+
},
15+
destroy(asyncId) {
16+
m.delete(asyncId);
17+
}
18+
}).enable();
19+
20+
function getActiveWorkerAndMessagePortTypes() {
21+
const activeWorkerAndMessagePortTypes = [];
22+
for (const asyncId of m.keys()) {
23+
const { type, resource } = m.get(asyncId);
24+
// Same logic as https://github.com/mafintosh/why-is-node-running/blob/24fb4c878753390a05d00959e6173d0d3c31fddd/index.js#L31-L32.
25+
if (typeof resource.hasRef !== 'function' || resource.hasRef() === true) {
26+
activeWorkerAndMessagePortTypes.push(type);
27+
}
28+
}
29+
return activeWorkerAndMessagePortTypes;
30+
}
31+
32+
const w = new Worker('', { eval: true });
33+
deepStrictEqual(getActiveWorkerAndMessagePortTypes(), ['WORKER']);
34+
w.unref();
35+
deepStrictEqual(getActiveWorkerAndMessagePortTypes(), []);
36+
w.ref();
37+
deepStrictEqual(getActiveWorkerAndMessagePortTypes(), ['WORKER', 'MESSAGEPORT']);
38+
39+
w.on('exit', common.mustCall((exitCode) => {
40+
strictEqual(exitCode, 0);
41+
deepStrictEqual(getActiveWorkerAndMessagePortTypes(), ['WORKER']);
42+
setTimeout(common.mustCall(() => {
43+
deepStrictEqual(getActiveWorkerAndMessagePortTypes(), []);
44+
}), 0);
45+
}));

0 commit comments

Comments
 (0)