Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4c317ce

Browse files
addaleaxtargos
authored andcommittedJan 13, 2020
child_process,cluster: allow using V8 serialization API
Add an `serialization` option that allows child process IPC to use the (typically more powerful) V8 serialization API. Fixes: #10965 PR-URL: #30162 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Gus Caplan <[email protected]> Reviewed-By: David Carlier <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent 56188fe commit 4c317ce

13 files changed

+304
-39
lines changed
 

‎benchmark/cluster/echo.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,25 @@ if (cluster.isMaster) {
77
workers: [1],
88
payload: ['string', 'object'],
99
sendsPerBroadcast: [1, 10],
10+
serialization: ['json', 'advanced'],
1011
n: [1e5]
1112
});
1213

13-
function main({ n, workers, sendsPerBroadcast, payload }) {
14+
function main({
15+
n,
16+
workers,
17+
sendsPerBroadcast,
18+
payload,
19+
serialization
20+
}) {
1421
const expectedPerBroadcast = sendsPerBroadcast * workers;
1522
var readies = 0;
1623
var broadcasts = 0;
1724
var msgCount = 0;
1825
var data;
1926

27+
cluster.settings.serialization = serialization;
28+
2029
switch (payload) {
2130
case 'string':
2231
data = 'hello world!';

‎doc/api/child_process.md

+39
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ arbitrary command execution.**
321321
<!-- YAML
322322
added: v0.5.0
323323
changes:
324+
- version: REPLACEME
325+
pr-url: https://github.com/nodejs/node/pull/30162
326+
description: The `serialization` option is supported now.
324327
- version: v8.0.0
325328
pr-url: https://github.com/nodejs/node/pull/10866
326329
description: The `stdio` option can now be a string.
@@ -340,6 +343,9 @@ changes:
340343
* `execPath` {string} Executable used to create the child process.
341344
* `execArgv` {string[]} List of string arguments passed to the executable.
342345
**Default:** `process.execArgv`.
346+
* `serialization` {string} Specify the kind of serialization used for sending
347+
messages between processes. Possible values are `'json'` and `'advanced'`.
348+
See [Advanced Serialization][] for more details. **Default:** `'json'`.
343349
* `silent` {boolean} If `true`, stdin, stdout, and stderr of the child will be
344350
piped to the parent, otherwise they will be inherited from the parent, see
345351
the `'pipe'` and `'inherit'` options for [`child_process.spawn()`][]'s
@@ -386,6 +392,9 @@ The `shell` option available in [`child_process.spawn()`][] is not supported by
386392
<!-- YAML
387393
added: v0.1.90
388394
changes:
395+
- version: REPLACEME
396+
pr-url: https://github.com/nodejs/node/pull/30162
397+
description: The `serialization` option is supported now.
389398
- version: v8.8.0
390399
pr-url: https://github.com/nodejs/node/pull/15380
391400
description: The `windowsHide` option is supported now.
@@ -411,6 +420,9 @@ changes:
411420
[`options.detached`][]).
412421
* `uid` {number} Sets the user identity of the process (see setuid(2)).
413422
* `gid` {number} Sets the group identity of the process (see setgid(2)).
423+
* `serialization` {string} Specify the kind of serialization used for sending
424+
messages between processes. Possible values are `'json'` and `'advanced'`.
425+
See [Advanced Serialization][] for more details. **Default:** `'json'`.
414426
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
415427
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
416428
shell can be specified as a string. See [Shell Requirements][] and
@@ -998,6 +1010,11 @@ The `'message'` event is triggered when a child process uses
9981010
The message goes through serialization and parsing. The resulting
9991011
message might not be the same as what is originally sent.
10001012

1013+
If the `serialization` option was set to `'advanced'` used when spawning the
1014+
child process, the `message` argument can contain data that JSON is not able
1015+
to represent.
1016+
See [Advanced Serialization][] for more details.
1017+
10011018
### `subprocess.channel`
10021019
<!-- YAML
10031020
added: v7.1.0
@@ -1474,6 +1491,26 @@ the same requirement. Thus, in `child_process` functions where a shell can be
14741491
spawned, `'cmd.exe'` is used as a fallback if `process.env.ComSpec` is
14751492
unavailable.
14761493

1494+
## Advanced Serialization
1495+
<!-- YAML
1496+
added: REPLACEME
1497+
-->
1498+
1499+
Child processes support a serialization mechanism for IPC that is based on the
1500+
[serialization API of the `v8` module][v8.serdes], based on the
1501+
[HTML structured clone algorithm][]. This is generally more powerful and
1502+
supports more built-in JavaScript object types, such as `BigInt`, `Map`
1503+
and `Set`, `ArrayBuffer` and `TypedArray`, `Buffer`, `Error`, `RegExp` etc.
1504+
1505+
However, this format is not a full superset of JSON, and e.g. properties set on
1506+
objects of such built-in types will not be passed on through the serialization
1507+
step. Additionally, performance may not be equivalent to that of JSON, depending
1508+
on the structure of the passed data.
1509+
Therefore, this feature requires opting in by setting the
1510+
`serialization` option to `'advanced'` when calling [`child_process.spawn()`][]
1511+
or [`child_process.fork()`][].
1512+
1513+
[Advanced Serialization]: #child_process_advanced_serialization
14771514
[`'disconnect'`]: process.html#process_event_disconnect
14781515
[`'error'`]: #child_process_event_error
14791516
[`'exit'`]: #child_process_event_exit
@@ -1507,5 +1544,7 @@ unavailable.
15071544
[`subprocess.stdout`]: #child_process_subprocess_stdout
15081545
[`util.promisify()`]: util.html#util_util_promisify_original
15091546
[Default Windows Shell]: #child_process_default_windows_shell
1547+
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
15101548
[Shell Requirements]: #child_process_shell_requirements
15111549
[synchronous counterparts]: #child_process_synchronous_process_creation
1550+
[v8.serdes]: v8.html#v8_serialization_api

‎doc/api/cluster.md

+8
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,9 @@ values are `'rr'` and `'none'`.
724724
<!-- YAML
725725
added: v0.7.1
726726
changes:
727+
- version: REPLACEME
728+
pr-url: https://github.com/nodejs/node/pull/30162
729+
description: The `serialization` option is supported now.
727730
- version: v9.5.0
728731
pr-url: https://github.com/nodejs/node/pull/18399
729732
description: The `cwd` option is supported now.
@@ -746,6 +749,10 @@ changes:
746749
**Default:** `process.argv.slice(2)`.
747750
* `cwd` {string} Current working directory of the worker process. **Default:**
748751
`undefined` (inherits from parent process).
752+
* `serialization` {string} Specify the kind of serialization used for sending
753+
messages between processes. Possible values are `'json'` and `'advanced'`.
754+
See [Advanced Serialization for `child_process`][] for more details.
755+
**Default:** `false`.
749756
* `silent` {boolean} Whether or not to send output to parent's stdio.
750757
**Default:** `false`.
751758
* `stdio` {Array} Configures the stdio of forked processes. Because the
@@ -874,4 +881,5 @@ socket.on('data', (id) => {
874881
[`process` event: `'message'`]: process.html#process_event_message
875882
[`server.close()`]: net.html#net_event_close
876883
[`worker.exitedAfterDisconnect`]: #cluster_worker_exitedafterdisconnect
884+
[Advanced Serialization for `child_process`]: child_process.html#child_process_advanced_serialization
877885
[Child Process module]: child_process.html#child_process_child_process_fork_modulepath_args_options

‎doc/api/process.md

+6
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ the child process.
119119
The message goes through serialization and parsing. The resulting message might
120120
not be the same as what is originally sent.
121121

122+
If the `serialization` option was set to `advanced` used when spawning the
123+
process, the `message` argument can contain data that JSON is not able
124+
to represent.
125+
See [Advanced Serialization for `child_process`][] for more details.
126+
122127
### Event: `'multipleResolves'`
123128
<!-- YAML
124129
added: v10.12.0
@@ -2457,6 +2462,7 @@ cases:
24572462
[`require.resolve()`]: modules.html#modules_require_resolve_request_options
24582463
[`subprocess.kill()`]: child_process.html#child_process_subprocess_kill_signal
24592464
[`v8.setFlagsFromString()`]: v8.html#v8_v8_setflagsfromstring_flags
2465+
[Advanced Serialization for `child_process`]: child_process.html#child_process_advanced_serialization
24602466
[Android building]: https://github.com/nodejs/node/blob/master/BUILDING.md#androidandroid-based-devices-eg-firefox-os
24612467
[Child Process]: child_process.html
24622468
[Cluster]: cluster.html

‎lib/child_process.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ function fork(modulePath /* , args, options */) {
108108
return spawn(options.execPath, args, options);
109109
}
110110

111-
function _forkChild(fd) {
111+
function _forkChild(fd, serializationMode) {
112112
// set process.send()
113113
const p = new Pipe(PipeConstants.IPC);
114114
p.open(fd);
115115
p.unref();
116-
const control = setupChannel(process, p);
116+
const control = setupChannel(process, p, serializationMode);
117117
process.on('newListener', function onNewListener(name) {
118118
if (name === 'message' || name === 'disconnect') control.ref();
119119
});
@@ -547,7 +547,8 @@ function spawn(file, args, options) {
547547
envPairs: opts.envPairs,
548548
stdio: options.stdio,
549549
uid: options.uid,
550-
gid: options.gid
550+
gid: options.gid,
551+
serialization: options.serialization,
551552
});
552553

553554
return child;

‎lib/internal/bootstrap/pre_execution.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,11 @@ function setupChildProcessIpcChannel() {
326326
// Make sure it's not accidentally inherited by child processes.
327327
delete process.env.NODE_CHANNEL_FD;
328328

329-
require('child_process')._forkChild(fd);
329+
const serializationMode =
330+
process.env.NODE_CHANNEL_SERIALIZATION_MODE || 'json';
331+
delete process.env.NODE_CHANNEL_SERIALIZATION_MODE;
332+
333+
require('child_process')._forkChild(fd, serializationMode);
330334
assert(process.send);
331335
}
332336
}

‎lib/internal/child_process.js

+27-32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { JSON, Object } = primordials;
3+
const { Object } = primordials;
44

55
const {
66
errnoException,
@@ -55,8 +55,6 @@ const {
5555

5656
const { SocketListSend, SocketListReceive } = SocketList;
5757

58-
// Lazy loaded for startup performance.
59-
let StringDecoder;
6058
// Lazy loaded for startup performance and to allow monkey patching of
6159
// internalBinding('http_parser').HTTPParser.
6260
let freeParser;
@@ -343,6 +341,15 @@ ChildProcess.prototype.spawn = function(options) {
343341
const ipcFd = stdio.ipcFd;
344342
stdio = options.stdio = stdio.stdio;
345343

344+
if (options.serialization !== undefined &&
345+
options.serialization !== 'json' &&
346+
options.serialization !== 'advanced') {
347+
throw new ERR_INVALID_OPT_VALUE('options.serialization',
348+
options.serialization);
349+
}
350+
351+
const serialization = options.serialization || 'json';
352+
346353
if (ipc !== undefined) {
347354
// Let child process know about opened IPC channel
348355
if (options.envPairs === undefined)
@@ -353,7 +360,8 @@ ChildProcess.prototype.spawn = function(options) {
353360
options.envPairs);
354361
}
355362

356-
options.envPairs.push('NODE_CHANNEL_FD=' + ipcFd);
363+
options.envPairs.push(`NODE_CHANNEL_FD=${ipcFd}`);
364+
options.envPairs.push(`NODE_CHANNEL_SERIALIZATION_MODE=${serialization}`);
357365
}
358366

359367
validateString(options.file, 'options.file');
@@ -446,7 +454,7 @@ ChildProcess.prototype.spawn = function(options) {
446454
this.stdio.push(stdio[i].socket === undefined ? null : stdio[i].socket);
447455

448456
// Add .send() method and start listening for IPC data
449-
if (ipc !== undefined) setupChannel(this, ipc);
457+
if (ipc !== undefined) setupChannel(this, ipc, serialization);
450458

451459
return err;
452460
};
@@ -513,7 +521,8 @@ class Control extends EventEmitter {
513521
}
514522
}
515523

516-
function setupChannel(target, channel) {
524+
let serialization;
525+
function setupChannel(target, channel, serializationMode) {
517526
target.channel = channel;
518527

519528
// _channel can be deprecated in version 8
@@ -528,12 +537,16 @@ function setupChannel(target, channel) {
528537

529538
const control = new Control(channel);
530539

531-
if (StringDecoder === undefined)
532-
StringDecoder = require('string_decoder').StringDecoder;
533-
const decoder = new StringDecoder('utf8');
534-
var jsonBuffer = '';
535-
var pendingHandle = null;
536-
channel.buffering = false;
540+
if (serialization === undefined)
541+
serialization = require('internal/child_process/serialization');
542+
const {
543+
initMessageChannel,
544+
parseChannelMessages,
545+
writeChannelMessage
546+
} = serialization[serializationMode];
547+
548+
let pendingHandle = null;
549+
initMessageChannel(channel);
537550
channel.pendingHandle = null;
538551
channel.onread = function(arrayBuffer) {
539552
const recvHandle = channel.pendingHandle;
@@ -545,21 +558,7 @@ function setupChannel(target, channel) {
545558
if (recvHandle)
546559
pendingHandle = recvHandle;
547560

548-
// Linebreak is used as a message end sign
549-
var chunks = decoder.write(pool).split('\n');
550-
var numCompleteChunks = chunks.length - 1;
551-
// Last line does not have trailing linebreak
552-
var incompleteChunk = chunks[numCompleteChunks];
553-
if (numCompleteChunks === 0) {
554-
jsonBuffer += incompleteChunk;
555-
this.buffering = jsonBuffer.length !== 0;
556-
return;
557-
}
558-
chunks[0] = jsonBuffer + chunks[0];
559-
560-
for (var i = 0; i < numCompleteChunks; i++) {
561-
var message = JSON.parse(chunks[i]);
562-
561+
for (const message of parseChannelMessages(channel, pool)) {
563562
// There will be at most one NODE_HANDLE message in every chunk we
564563
// read because SCM_RIGHTS messages don't get coalesced. Make sure
565564
// that we deliver the handle with the right message however.
@@ -574,9 +573,6 @@ function setupChannel(target, channel) {
574573
handleMessage(message, undefined, false);
575574
}
576575
}
577-
jsonBuffer = incompleteChunk;
578-
this.buffering = jsonBuffer.length !== 0;
579-
580576
} else {
581577
this.buffering = false;
582578
target.disconnect();
@@ -775,8 +771,7 @@ function setupChannel(target, channel) {
775771

776772
const req = new WriteWrap();
777773

778-
const string = JSON.stringify(message) + '\n';
779-
const err = channel.writeUtf8String(req, string, handle);
774+
const err = writeChannelMessage(channel, req, message, handle);
780775
const wasAsyncWrite = streamBaseState[kLastWriteWasAsync];
781776

782777
if (err === 0) {
+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
'use strict';
2+
3+
const { JSON } = primordials;
4+
const { Buffer } = require('buffer');
5+
const { StringDecoder } = require('string_decoder');
6+
const v8 = require('v8');
7+
const { isArrayBufferView } = require('internal/util/types');
8+
const assert = require('internal/assert');
9+
10+
const kMessageBuffer = Symbol('kMessageBuffer');
11+
const kJSONBuffer = Symbol('kJSONBuffer');
12+
const kStringDecoder = Symbol('kStringDecoder');
13+
14+
// Extend V8's serializer APIs to give more JSON-like behaviour in
15+
// some cases; in particular, for native objects this serializes them the same
16+
// way that JSON does rather than throwing an exception.
17+
const kArrayBufferViewTag = 0;
18+
const kNotArrayBufferViewTag = 1;
19+
class ChildProcessSerializer extends v8.DefaultSerializer {
20+
_writeHostObject(object) {
21+
if (isArrayBufferView(object)) {
22+
this.writeUint32(kArrayBufferViewTag);
23+
return super._writeHostObject(object);
24+
} else {
25+
this.writeUint32(kNotArrayBufferViewTag);
26+
this.writeValue({ ...object });
27+
}
28+
}
29+
}
30+
31+
class ChildProcessDeserializer extends v8.DefaultDeserializer {
32+
_readHostObject() {
33+
const tag = this.readUint32();
34+
if (tag === kArrayBufferViewTag)
35+
return super._readHostObject();
36+
37+
assert(tag === kNotArrayBufferViewTag);
38+
return this.readValue();
39+
}
40+
}
41+
42+
// Messages are parsed in either of the following formats:
43+
// - Newline-delimited JSON, or
44+
// - V8-serialized buffers, prefixed with their length as a big endian uint32
45+
// (aka 'advanced')
46+
const advanced = {
47+
initMessageChannel(channel) {
48+
channel[kMessageBuffer] = Buffer.alloc(0);
49+
channel.buffering = false;
50+
},
51+
52+
*parseChannelMessages(channel, readData) {
53+
if (readData.length === 0) return;
54+
55+
let messageBuffer = Buffer.concat([channel[kMessageBuffer], readData]);
56+
while (messageBuffer.length > 4) {
57+
const size = messageBuffer.readUInt32BE();
58+
if (messageBuffer.length < 4 + size) {
59+
break;
60+
}
61+
62+
const deserializer = new ChildProcessDeserializer(
63+
messageBuffer.subarray(4, 4 + size));
64+
messageBuffer = messageBuffer.subarray(4 + size);
65+
66+
deserializer.readHeader();
67+
yield deserializer.readValue();
68+
}
69+
channel[kMessageBuffer] = messageBuffer;
70+
channel.buffering = messageBuffer.length > 0;
71+
},
72+
73+
writeChannelMessage(channel, req, message, handle) {
74+
const ser = new ChildProcessSerializer();
75+
ser.writeHeader();
76+
ser.writeValue(message);
77+
const serializedMessage = ser.releaseBuffer();
78+
const sizeBuffer = Buffer.allocUnsafe(4);
79+
sizeBuffer.writeUInt32BE(serializedMessage.length);
80+
return channel.writeBuffer(req, Buffer.concat([
81+
sizeBuffer,
82+
serializedMessage
83+
]), handle);
84+
},
85+
};
86+
87+
const json = {
88+
initMessageChannel(channel) {
89+
channel[kJSONBuffer] = '';
90+
channel[kStringDecoder] = undefined;
91+
},
92+
93+
*parseChannelMessages(channel, readData) {
94+
if (readData.length === 0) return;
95+
96+
if (channel[kStringDecoder] === undefined)
97+
channel[kStringDecoder] = new StringDecoder('utf8');
98+
const chunks = channel[kStringDecoder].write(readData).split('\n');
99+
const numCompleteChunks = chunks.length - 1;
100+
// Last line does not have trailing linebreak
101+
const incompleteChunk = chunks[numCompleteChunks];
102+
if (numCompleteChunks === 0) {
103+
channel[kJSONBuffer] += incompleteChunk;
104+
} else {
105+
chunks[0] = channel[kJSONBuffer] + chunks[0];
106+
for (let i = 0; i < numCompleteChunks; i++)
107+
yield JSON.parse(chunks[i]);
108+
channel[kJSONBuffer] = incompleteChunk;
109+
}
110+
channel.buffering = channel[kJSONBuffer].length !== 0;
111+
},
112+
113+
writeChannelMessage(channel, req, message, handle) {
114+
const string = JSON.stringify(message) + '\n';
115+
return channel.writeUtf8String(req, string, handle);
116+
},
117+
};
118+
119+
module.exports = { advanced, json };

‎lib/internal/cluster/master.js

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ function createWorkerProcess(id, env) {
130130
return fork(cluster.settings.exec, cluster.settings.args, {
131131
cwd: cluster.settings.cwd,
132132
env: workerEnv,
133+
serialization: cluster.settings.serialization,
133134
silent: cluster.settings.silent,
134135
windowsHide: cluster.settings.windowsHide,
135136
execArgv: execArgv,

‎node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
'lib/internal/buffer.js',
9292
'lib/internal/cli_table.js',
9393
'lib/internal/child_process.js',
94+
'lib/internal/child_process/serialization.js',
9495
'lib/internal/cluster/child.js',
9596
'lib/internal/cluster/master.js',
9697
'lib/internal/cluster/round_robin_handle.js',

‎src/stream_base.cc

+16-2
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,26 @@ int StreamBase::WriteBuffer(const FunctionCallbackInfo<Value>& args) {
180180
}
181181

182182
Local<Object> req_wrap_obj = args[0].As<Object>();
183-
184183
uv_buf_t buf;
185184
buf.base = Buffer::Data(args[1]);
186185
buf.len = Buffer::Length(args[1]);
187186

188-
StreamWriteResult res = Write(&buf, 1, nullptr, req_wrap_obj);
187+
uv_stream_t* send_handle = nullptr;
188+
189+
if (args[2]->IsObject() && IsIPCPipe()) {
190+
Local<Object> send_handle_obj = args[2].As<Object>();
191+
192+
HandleWrap* wrap;
193+
ASSIGN_OR_RETURN_UNWRAP(&wrap, send_handle_obj, UV_EINVAL);
194+
send_handle = reinterpret_cast<uv_stream_t*>(wrap->GetHandle());
195+
// Reference LibuvStreamWrap instance to prevent it from being garbage
196+
// collected before `AfterWrite` is called.
197+
req_wrap_obj->Set(env->context(),
198+
env->handle_string(),
199+
send_handle_obj).Check();
200+
}
201+
202+
StreamWriteResult res = Write(&buf, 1, send_handle, req_wrap_obj);
189203
SetWriteResult(res);
190204

191205
return res.err;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const child_process = require('child_process');
5+
const { once } = require('events');
6+
7+
if (process.argv[2] !== 'child') {
8+
for (const value of [null, 42, Infinity, 'foo']) {
9+
common.expectsError(() => {
10+
child_process.spawn(process.execPath, [], { serialization: value });
11+
}, {
12+
code: 'ERR_INVALID_OPT_VALUE',
13+
message: `The value "${value}" is invalid ` +
14+
'for option "options.serialization"'
15+
});
16+
}
17+
18+
(async () => {
19+
const cp = child_process.spawn(process.execPath, [__filename, 'child'],
20+
{
21+
stdio: ['ipc', 'inherit', 'inherit'],
22+
serialization: 'advanced'
23+
});
24+
25+
const circular = {};
26+
circular.circular = circular;
27+
for await (const message of [
28+
{ uint8: new Uint8Array(4) },
29+
{ float64: new Float64Array([ Math.PI ]) },
30+
{ buffer: Buffer.from('Hello!') },
31+
{ map: new Map([{ a: 1 }, { b: 2 }]) },
32+
{ bigInt: 1337n },
33+
circular,
34+
new Error('Something went wrong'),
35+
new RangeError('Something range-y went wrong'),
36+
]) {
37+
cp.send(message);
38+
const [ received ] = await once(cp, 'message');
39+
assert.deepStrictEqual(received, message);
40+
}
41+
42+
cp.disconnect();
43+
})().then(common.mustCall());
44+
} else {
45+
process.on('message', (msg) => process.send(msg));
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cluster = require('cluster');
5+
6+
if (cluster.isMaster) {
7+
cluster.settings.serialization = 'advanced';
8+
const worker = cluster.fork();
9+
const circular = {};
10+
circular.circular = circular;
11+
12+
worker.on('online', common.mustCall(() => {
13+
worker.send(circular);
14+
15+
worker.on('message', common.mustCall((msg) => {
16+
assert.deepStrictEqual(msg, circular);
17+
worker.kill();
18+
}));
19+
}));
20+
} else {
21+
process.on('message', (msg) => process.send(msg));
22+
}

0 commit comments

Comments
 (0)
Please sign in to comment.