Skip to content

Commit 2bbcfa0

Browse files
mcollinaaduh95
authored andcommitted
worker: preload streams into startup snapshot
Worker stdio loads stream after restoring the main-thread context, initializing its constructors and prototype methods on every startup. Preload stream during snapshot construction so workers can restore that initialized state instead. Worker-specific stdio instances and message ports are still created at runtime. Verify snapshot membership, stdio methods, and isolation from parent-thread stream mutations. Assisted-by: pi Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #66311 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com>
1 parent 9d86291 commit 2bbcfa0

11 files changed

Lines changed: 171 additions & 102 deletions

File tree

‎lib/internal/bootstrap/switches/is_main_thread.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ if (isBuildingSnapshot()) {
312312
require('internal/modules/typescript');
313313
require('internal/blob');
314314
require('internal/dns/utils');
315+
// Workers restore this context before loading internal/worker/io, which
316+
// needs the initialized stream constructors for its stdio subclasses.
317+
require('stream');
315318
}
316319

317320
// Needed to refresh the time origin.

‎lib/internal/streams/duplex.js‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,14 @@ const {
4646
const destroyImpl = require('internal/streams/destroy');
4747
const { kOnConstructed } = require('internal/streams/utils');
4848

49-
ObjectSetPrototypeOf(Duplex.prototype, Readable.prototype);
50-
ObjectSetPrototypeOf(Duplex, Readable);
51-
5249
{
5350
const keys = ObjectKeys(Writable.prototype);
5451
// Allow the keys array to be GC'ed.
5552
for (let i = 0; i < keys.length; i++) {
5653
const method = keys[i];
57-
Duplex.prototype[method] ||= Writable.prototype[method];
54+
if (!Readable.prototype[method]) {
55+
Duplex.prototype[method] = Writable.prototype[method];
56+
}
5857
}
5958
}
6059

@@ -201,3 +200,8 @@ Duplex.from = function(body) {
201200
duplexify ??= require('internal/streams/duplexify');
202201
return duplexify(body, 'body');
203202
};
203+
204+
// Define own methods before inheriting from constructors or prototypes that
205+
// user code may have frozen before this module was loaded.
206+
ObjectSetPrototypeOf(Duplex.prototype, Readable.prototype);
207+
ObjectSetPrototypeOf(Duplex, Readable);

‎lib/internal/streams/operators.js‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ const {
66
MathFloor,
77
Number,
88
NumberIsNaN,
9+
ObjectDefineProperty,
10+
ObjectKeys,
911
Promise,
1012
PromisePrototypeThen,
1113
PromiseReject,
1214
PromiseResolve,
15+
ReflectApply,
1316
Symbol,
1417
} = primordials;
1518

@@ -18,6 +21,7 @@ const { AbortController, AbortSignal } = require('internal/abort_controller');
1821
const {
1922
AbortError,
2023
codes: {
24+
ERR_ILLEGAL_CONSTRUCTOR,
2125
ERR_MISSING_ARGS,
2226
ERR_OUT_OF_RANGE,
2327
},
@@ -31,6 +35,7 @@ const {
3135
const { kWeakHandler, kResistStopPropagation } = require('internal/event_target');
3236
const destroyImpl = require('internal/streams/destroy');
3337
const { finished } = require('internal/streams/end-of-stream');
38+
const Stream = require('stream');
3439

3540
const kEmpty = Symbol('kEmpty');
3641
const kEof = Symbol('kEof');
@@ -373,19 +378,49 @@ function take(number, options = undefined) {
373378
}.call(this);
374379
}
375380

376-
module.exports.streamReturningOperators = {
381+
const streamReturningOperators = {
377382
drop,
378383
filter,
379384
flatMap,
380385
map,
381386
take,
382387
};
383388

384-
module.exports.promiseReturningOperators = {
389+
const promiseReturningOperators = {
385390
every,
386391
forEach,
387392
reduce,
388393
toArray,
389394
some,
390395
find,
391396
};
397+
398+
const streamKeys = ObjectKeys(streamReturningOperators);
399+
for (let i = 0; i < streamKeys.length; i++) {
400+
const key = streamKeys[i];
401+
const op = streamReturningOperators[key];
402+
function fn(...args) {
403+
if (new.target) {
404+
throw new ERR_ILLEGAL_CONSTRUCTOR();
405+
}
406+
return Stream.Readable.from(ReflectApply(op, this, args));
407+
}
408+
ObjectDefineProperty(fn, 'name', { __proto__: null, value: op.name });
409+
ObjectDefineProperty(fn, 'length', { __proto__: null, value: op.length });
410+
module.exports[key] = fn;
411+
}
412+
413+
const promiseKeys = ObjectKeys(promiseReturningOperators);
414+
for (let i = 0; i < promiseKeys.length; i++) {
415+
const key = promiseKeys[i];
416+
const op = promiseReturningOperators[key];
417+
function fn(...args) {
418+
if (new.target) {
419+
throw new ERR_ILLEGAL_CONSTRUCTOR();
420+
}
421+
return ReflectApply(op, this, args);
422+
}
423+
ObjectDefineProperty(fn, 'name', { __proto__: null, value: op.name });
424+
ObjectDefineProperty(fn, 'length', { __proto__: null, value: op.length });
425+
module.exports[key] = fn;
426+
}

‎lib/internal/streams/passthrough.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ const {
3232
module.exports = PassThrough;
3333

3434
const Transform = require('internal/streams/transform');
35-
ObjectSetPrototypeOf(PassThrough.prototype, Transform.prototype);
36-
ObjectSetPrototypeOf(PassThrough, Transform);
3735

3836
function PassThrough(options) {
3937
if (!(this instanceof PassThrough))
@@ -45,3 +43,7 @@ function PassThrough(options) {
4543
PassThrough.prototype._transform = function(chunk, encoding, cb) {
4644
cb(null, chunk);
4745
};
46+
47+
// Install the override before inheriting a potentially frozen prototype.
48+
ObjectSetPrototypeOf(PassThrough.prototype, Transform.prototype);
49+
ObjectSetPrototypeOf(PassThrough, Transform);

‎lib/internal/streams/pipeline.js‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55

66
const {
77
ArrayIsArray,
8+
ObjectDefineProperty,
89
Promise,
910
SymbolAsyncIterator,
1011
SymbolDispose,
1112
} = primordials;
1213

1314
const { eos } = require('internal/streams/end-of-stream');
14-
const { once } = require('internal/util');
15+
const {
16+
once,
17+
promisify: { custom: customPromisify },
18+
} = require('internal/util');
1519
const destroyImpl = require('internal/streams/destroy');
1620
const Duplex = require('internal/streams/duplex');
1721
const {
@@ -469,4 +473,12 @@ function pipe(src, dst, finish, finishOnlyHandleError, { end }) {
469473
return eos(dst, { readable: false, writable: true }, finish);
470474
}
471475

476+
ObjectDefineProperty(pipeline, customPromisify, {
477+
__proto__: null,
478+
enumerable: true,
479+
get() {
480+
return require('stream/promises').pipeline;
481+
},
482+
});
483+
472484
module.exports = { pipelineImpl, pipeline };

‎lib/internal/streams/transform.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ const {
7474
} = require('internal/errors').codes;
7575
const Duplex = require('internal/streams/duplex');
7676
const { getHighWaterMark } = require('internal/streams/state');
77-
ObjectSetPrototypeOf(Transform.prototype, Duplex.prototype);
78-
ObjectSetPrototypeOf(Transform, Duplex);
7977

8078
const kCallback = Symbol('kCallback');
8179

@@ -202,3 +200,7 @@ Transform.prototype._read = function() {
202200
callback();
203201
}
204202
};
203+
204+
// Install overrides before inheriting potentially frozen prototype methods.
205+
ObjectSetPrototypeOf(Transform.prototype, Duplex.prototype);
206+
ObjectSetPrototypeOf(Transform, Duplex);

‎lib/stream.js‎

Lines changed: 23 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,20 @@
2323

2424
const {
2525
ObjectDefineProperty,
26-
ObjectKeys,
27-
ReflectApply,
26+
ObjectSetPrototypeOf,
2827
} = primordials;
2928

3029
const {
30+
defineLazyProperties,
3131
promisify: { custom: customPromisify },
3232
} = require('internal/util');
3333

34-
const {
35-
streamReturningOperators,
36-
promiseReturningOperators,
37-
} = require('internal/streams/operators');
38-
39-
const {
40-
codes: {
41-
ERR_ILLEGAL_CONSTRUCTOR,
42-
},
43-
} = require('internal/errors');
44-
const compose = require('internal/streams/compose');
4534
const { setDefaultHighWaterMark, getDefaultHighWaterMark } = require('internal/streams/state');
46-
const { pipeline } = require('internal/streams/pipeline');
4735
const { destroyer } = require('internal/streams/destroy');
4836
const { eos } = require('internal/streams/end-of-stream');
4937
const internalBuffer = require('internal/buffer');
5038

51-
const promises = require('stream/promises');
39+
let promises;
5240
const utils = require('internal/streams/utils');
5341
const { isArrayBufferView, isUint8Array } = require('internal/util/types');
5442

@@ -61,57 +49,29 @@ Stream.isReadable = utils.isReadable;
6149
Stream.isWritable = utils.isWritable;
6250

6351
Stream.Readable = require('internal/streams/readable');
64-
const streamKeys = ObjectKeys(streamReturningOperators);
65-
for (let i = 0; i < streamKeys.length; i++) {
66-
const key = streamKeys[i];
67-
const op = streamReturningOperators[key];
68-
function fn(...args) {
69-
if (new.target) {
70-
throw new ERR_ILLEGAL_CONSTRUCTOR();
71-
}
72-
return Stream.Readable.from(ReflectApply(op, this, args));
73-
}
74-
ObjectDefineProperty(fn, 'name', { __proto__: null, value: op.name });
75-
ObjectDefineProperty(fn, 'length', { __proto__: null, value: op.length });
76-
ObjectDefineProperty(Stream.Readable.prototype, key, {
77-
__proto__: null,
78-
value: fn,
79-
enumerable: false,
80-
configurable: true,
81-
writable: true,
82-
});
83-
}
84-
const promiseKeys = ObjectKeys(promiseReturningOperators);
85-
for (let i = 0; i < promiseKeys.length; i++) {
86-
const key = promiseKeys[i];
87-
const op = promiseReturningOperators[key];
88-
function fn(...args) {
89-
if (new.target) {
90-
throw new ERR_ILLEGAL_CONSTRUCTOR();
91-
}
92-
return ReflectApply(op, this, args);
93-
}
94-
ObjectDefineProperty(fn, 'name', { __proto__: null, value: op.name });
95-
ObjectDefineProperty(fn, 'length', { __proto__: null, value: op.length });
96-
ObjectDefineProperty(Stream.Readable.prototype, key, {
97-
__proto__: null,
98-
value: fn,
99-
enumerable: false,
100-
configurable: true,
101-
writable: true,
102-
});
103-
}
52+
defineLazyProperties(Stream.Readable.prototype, 'internal/streams/operators', [
53+
'drop', 'filter', 'flatMap', 'map', 'take',
54+
'every', 'forEach', 'reduce', 'toArray', 'some', 'find',
55+
], false);
10456
Stream.Writable = require('internal/streams/writable');
105-
Stream.Duplex = require('internal/streams/duplex');
106-
Stream.Transform = require('internal/streams/transform');
107-
Stream.PassThrough = require('internal/streams/passthrough');
108-
Stream.duplexPair = require('internal/streams/duplexpair');
109-
Stream.pipeline = pipeline;
57+
// These modules export a single function rather than named properties.
58+
// Set the prototype after creation to retain fast properties.
59+
const lazy = ObjectSetPrototypeOf({
60+
Duplex: 'internal/streams/duplex',
61+
Transform: 'internal/streams/transform',
62+
PassThrough: 'internal/streams/passthrough',
63+
duplexPair: 'internal/streams/duplexpair',
64+
compose: 'internal/streams/compose',
65+
}, null);
66+
defineLazyProperties(Stream, lazy, [
67+
'Duplex', 'Transform', 'PassThrough', 'duplexPair',
68+
]);
69+
defineLazyProperties(Stream, 'internal/streams/pipeline', ['pipeline']);
11070
const { addAbortSignal } = require('internal/streams/add-abort-signal');
11171
Stream.addAbortSignal = addAbortSignal;
11272
Stream.finished = eos;
11373
Stream.destroy = destroyer;
114-
Stream.compose = compose;
74+
defineLazyProperties(Stream, lazy, ['compose']);
11575
Stream.setDefaultHighWaterMark = setDefaultHighWaterMark;
11676
Stream.getDefaultHighWaterMark = getDefaultHighWaterMark;
11777

@@ -120,22 +80,15 @@ ObjectDefineProperty(Stream, 'promises', {
12080
configurable: true,
12181
enumerable: true,
12282
get() {
123-
return promises;
124-
},
125-
});
126-
127-
ObjectDefineProperty(pipeline, customPromisify, {
128-
__proto__: null,
129-
enumerable: true,
130-
get() {
131-
return promises.pipeline;
83+
return promises ??= require('stream/promises');
13284
},
13385
});
13486

13587
ObjectDefineProperty(eos, customPromisify, {
13688
__proto__: null,
13789
enumerable: true,
13890
get() {
91+
promises ??= require('stream/promises');
13992
return promises.finished;
14093
},
14194
});

‎lib/stream/promises.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ const {
1212
isWebStream,
1313
} = require('internal/streams/utils');
1414

15-
const { pipelineImpl: pl } = require('internal/streams/pipeline');
15+
let pl;
1616
const { finished } = require('internal/streams/end-of-stream');
1717

1818
require('stream');
1919

2020
function pipeline(...streams) {
21+
pl ??= require('internal/streams/pipeline').pipelineImpl;
2122
return new Promise((resolve, reject) => {
2223
let signal;
2324
let end;

0 commit comments

Comments
 (0)