Skip to content

Commit 3c6f12c

Browse files
addaleaxtargos
authored andcommitted
worker: implement worker.moveMessagePortToContext()
This enables using `MessagePort`s in different `vm.Context`s, aiding with the isolation that the `vm` module seeks to provide. Refs: ayojs/ayo#111 PR-URL: #26497 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 1e669b2 commit 3c6f12c

File tree

6 files changed

+142
-4
lines changed

6 files changed

+142
-4
lines changed

doc/api/worker_threads.md

+27
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,30 @@ if (isMainThread) {
7070
}
7171
```
7272

73+
## worker.moveMessagePortToContext(port, contextifiedSandbox)
74+
<!-- YAML
75+
added: REPLACEME
76+
-->
77+
78+
* `port` {MessagePort} The message port which will be transferred.
79+
* `contextifiedSandbox` {Object} A [contextified][] object as returned by the
80+
`vm.createContext()` method.
81+
82+
* Returns: {MessagePort}
83+
84+
Transfer a `MessagePort` to a different [`vm`][] Context. The original `port`
85+
object will be rendered unusable, and the returned `MessagePort` instance will
86+
take its place.
87+
88+
The returned `MessagePort` will be an object in the target context, and will
89+
inherit from its global `Object` class. Objects passed to the
90+
[`port.onmessage()`][] listener will also be created in the target context
91+
and inherit from its global `Object` class.
92+
93+
However, the created `MessagePort` will no longer inherit from
94+
[`EventEmitter`][], and only [`port.onmessage()`][] can be used to receive
95+
events using it.
96+
7397
## worker.parentPort
7498
<!-- YAML
7599
added: v10.5.0
@@ -583,6 +607,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
583607
[`Worker`]: #worker_threads_class_worker
584608
[`cluster` module]: cluster.html
585609
[`port.on('message')`]: #worker_threads_event_message
610+
[`port.onmessage()`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/onmessage
586611
[`port.postMessage()`]: #worker_threads_port_postmessage_value_transferlist
587612
[`process.abort()`]: process.html#process_process_abort
588613
[`process.chdir()`]: process.html#process_process_chdir_directory
@@ -600,6 +625,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
600625
[`require('worker_threads').threadId`]: #worker_threads_worker_threadid
601626
[`require('worker_threads').workerData`]: #worker_threads_worker_workerdata
602627
[`trace_events`]: tracing.html
628+
[`vm`]: vm.html
603629
[`worker.on('message')`]: #worker_threads_event_message_1
604630
[`worker.postMessage()`]: #worker_threads_worker_postmessage_value_transferlist
605631
[`worker.terminate()`]: #worker_threads_worker_terminate_callback
@@ -610,4 +636,5 @@ active handle in the event system. If the worker is already `unref()`ed calling
610636
[Web Workers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
611637
[browser `MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
612638
[child processes]: child_process.html
639+
[contextified]: vm.html#vm_what_does_it_mean_to_contextify_an_object
613640
[v8.serdes]: v8.html#v8_serialization_api

lib/internal/worker/io.js

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
MessagePort,
99
MessageChannel,
1010
drainMessagePort,
11+
moveMessagePortToContext,
1112
stopMessagePort
1213
} = internalBinding('messaging');
1314
const { threadId } = internalBinding('worker');
@@ -233,6 +234,7 @@ module.exports = {
233234
kIncrementsPortRef,
234235
kWaitingStreams,
235236
kStdioWantsMoreDataCallback,
237+
moveMessagePortToContext,
236238
MessagePort,
237239
MessageChannel,
238240
setupPortReferencing,

lib/worker_threads.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ const {
88

99
const {
1010
MessagePort,
11-
MessageChannel
11+
MessageChannel,
12+
moveMessagePortToContext,
1213
} = require('internal/worker/io');
1314

1415
module.exports = {
1516
isMainThread,
1617
MessagePort,
1718
MessageChannel,
19+
moveMessagePortToContext,
1820
threadId,
1921
Worker,
2022
parentPort: null,

src/node_messaging.cc

+36-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
#include "async_wrap-inl.h"
44
#include "debug_utils.h"
5+
#include "node_contextify.h"
56
#include "node_buffer.h"
67
#include "node_errors.h"
78
#include "node_process.h"
89
#include "util.h"
910

11+
using node::contextify::ContextifyContext;
1012
using v8::Array;
1113
using v8::ArrayBuffer;
1214
using v8::ArrayBufferCreationMode;
@@ -760,6 +762,35 @@ void MessagePort::Drain(const FunctionCallbackInfo<Value>& args) {
760762
port->OnMessage();
761763
}
762764

765+
void MessagePort::MoveToContext(const FunctionCallbackInfo<Value>& args) {
766+
Environment* env = Environment::GetCurrent(args);
767+
if (!args[0]->IsObject() ||
768+
!env->message_port_constructor_template()->HasInstance(args[0])) {
769+
return THROW_ERR_INVALID_ARG_TYPE(env,
770+
"First argument needs to be a MessagePort instance");
771+
}
772+
MessagePort* port = Unwrap<MessagePort>(args[0].As<Object>());
773+
CHECK_NOT_NULL(port);
774+
775+
Local<Value> context_arg = args[1];
776+
ContextifyContext* context_wrapper;
777+
if (!context_arg->IsObject() ||
778+
(context_wrapper = ContextifyContext::ContextFromContextifiedSandbox(
779+
env, context_arg.As<Object>())) == nullptr) {
780+
return THROW_ERR_INVALID_ARG_TYPE(env, "Invalid context argument");
781+
}
782+
783+
std::unique_ptr<MessagePortData> data;
784+
if (!port->IsDetached())
785+
data = port->Detach();
786+
787+
Context::Scope context_scope(context_wrapper->context());
788+
MessagePort* target =
789+
MessagePort::New(env, context_wrapper->context(), std::move(data));
790+
if (target != nullptr)
791+
args.GetReturnValue().Set(target->object());
792+
}
793+
763794
void MessagePort::Entangle(MessagePort* a, MessagePort* b) {
764795
Entangle(a, b->data_.get());
765796
}
@@ -816,9 +847,9 @@ static void MessageChannel(const FunctionCallbackInfo<Value>& args) {
816847
MessagePort* port2 = MessagePort::New(env, context);
817848
MessagePort::Entangle(port1, port2);
818849

819-
args.This()->Set(env->context(), env->port1_string(), port1->object())
850+
args.This()->Set(context, env->port1_string(), port1->object())
820851
.FromJust();
821-
args.This()->Set(env->context(), env->port2_string(), port2->object())
852+
args.This()->Set(context, env->port2_string(), port2->object())
822853
.FromJust();
823854
}
824855

@@ -833,7 +864,7 @@ static void InitMessaging(Local<Object> target,
833864
FIXED_ONE_BYTE_STRING(env->isolate(), "MessageChannel");
834865
Local<FunctionTemplate> templ = env->NewFunctionTemplate(MessageChannel);
835866
templ->SetClassName(message_channel_string);
836-
target->Set(env->context(),
867+
target->Set(context,
837868
message_channel_string,
838869
templ->GetFunction(context).ToLocalChecked()).FromJust();
839870
}
@@ -847,6 +878,8 @@ static void InitMessaging(Local<Object> target,
847878
// the browser equivalents do not provide them.
848879
env->SetMethod(target, "stopMessagePort", MessagePort::Stop);
849880
env->SetMethod(target, "drainMessagePort", MessagePort::Drain);
881+
env->SetMethod(target, "moveMessagePortToContext",
882+
MessagePort::MoveToContext);
850883
}
851884

852885
} // anonymous namespace

src/node_messaging.h

+5
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,17 @@ class MessagePort : public HandleWrap {
158158
// Stop processing messages on this port as a receiving end.
159159
void Stop();
160160

161+
/* constructor */
161162
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
163+
/* prototype methods */
162164
static void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args);
163165
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
164166
static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args);
165167
static void Drain(const v8::FunctionCallbackInfo<v8::Value>& args);
166168

169+
/* static */
170+
static void MoveToContext(const v8::FunctionCallbackInfo<v8::Value>& args);
171+
167172
// Turns `a` and `b` into siblings, i.e. connects the sending side of one
168173
// to the receiving side of the other. This is not thread-safe.
169174
static void Entangle(MessagePort* a, MessagePort* b);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* global port */
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const vm = require('vm');
6+
const {
7+
MessagePort, MessageChannel, moveMessagePortToContext
8+
} = require('worker_threads');
9+
10+
const context = vm.createContext();
11+
const { port1, port2 } = new MessageChannel();
12+
context.port = moveMessagePortToContext(port1, context);
13+
context.global = context;
14+
Object.assign(context, {
15+
global: context,
16+
assert,
17+
MessagePort,
18+
MessageChannel
19+
});
20+
21+
vm.runInContext('(' + function() {
22+
{
23+
assert(port.postMessage instanceof Function);
24+
assert(port.constructor instanceof Function);
25+
for (let obj = port; obj !== null; obj = Object.getPrototypeOf(obj)) {
26+
for (const key of Object.getOwnPropertyNames(obj)) {
27+
if (typeof obj[key] === 'object' && obj[key] !== null) {
28+
assert(obj[key] instanceof Object);
29+
} else if (typeof obj[key] === 'function') {
30+
assert(obj[key] instanceof Function);
31+
}
32+
}
33+
}
34+
35+
assert(!(port instanceof MessagePort));
36+
assert.strictEqual(port.onmessage, undefined);
37+
port.onmessage = function({ data }) {
38+
assert(data instanceof Object);
39+
port.postMessage(data);
40+
};
41+
port.start();
42+
}
43+
44+
{
45+
let threw = false;
46+
try {
47+
port.postMessage(global);
48+
} catch (e) {
49+
assert.strictEqual(e.constructor.name, 'DOMException');
50+
assert(e instanceof Object);
51+
assert(e instanceof Error);
52+
threw = true;
53+
}
54+
assert(threw);
55+
}
56+
57+
{
58+
const newDummyPort = new (port.constructor)();
59+
assert(!(newDummyPort instanceof MessagePort));
60+
assert(newDummyPort.close instanceof Function);
61+
newDummyPort.close();
62+
}
63+
} + ')()', context);
64+
65+
port2.on('message', common.mustCall((msg) => {
66+
assert(msg instanceof Object);
67+
port2.close();
68+
}));
69+
port2.postMessage({});

0 commit comments

Comments
 (0)