@@ -5,6 +5,7 @@ const EventEmitter = require('events');
5
5
const assert = require ( 'assert' ) ;
6
6
const path = require ( 'path' ) ;
7
7
const util = require ( 'util' ) ;
8
+ const { Readable, Writable } = require ( 'stream' ) ;
8
9
const {
9
10
ERR_INVALID_ARG_TYPE ,
10
11
ERR_WORKER_NEED_ABSOLUTE_PATH ,
@@ -29,13 +30,20 @@ const isMainThread = threadId === 0;
29
30
30
31
const kOnMessageListener = Symbol ( 'kOnMessageListener' ) ;
31
32
const kHandle = Symbol ( 'kHandle' ) ;
33
+ const kName = Symbol ( 'kName' ) ;
32
34
const kPort = Symbol ( 'kPort' ) ;
33
35
const kPublicPort = Symbol ( 'kPublicPort' ) ;
34
36
const kDispose = Symbol ( 'kDispose' ) ;
35
37
const kOnExit = Symbol ( 'kOnExit' ) ;
36
38
const kOnMessage = Symbol ( 'kOnMessage' ) ;
37
39
const kOnCouldNotSerializeErr = Symbol ( 'kOnCouldNotSerializeErr' ) ;
38
40
const kOnErrorMessage = Symbol ( 'kOnErrorMessage' ) ;
41
+ const kParentSideStdio = Symbol ( 'kParentSideStdio' ) ;
42
+ const kWritableCallbacks = Symbol ( 'kWritableCallbacks' ) ;
43
+ const kStdioWantsMoreDataCallback = Symbol ( 'kStdioWantsMoreDataCallback' ) ;
44
+ const kStartedReading = Symbol ( 'kStartedReading' ) ;
45
+ const kWaitingStreams = Symbol ( 'kWaitingStreams' ) ;
46
+ const kIncrementsPortRef = Symbol ( 'kIncrementsPortRef' ) ;
39
47
40
48
const debug = util . debuglog ( 'worker' ) ;
41
49
@@ -129,6 +137,72 @@ function setupPortReferencing(port, eventEmitter, eventName) {
129
137
}
130
138
131
139
140
+ class ReadableWorkerStdio extends Readable {
141
+ constructor ( port , name ) {
142
+ super ( ) ;
143
+ this [ kPort ] = port ;
144
+ this [ kName ] = name ;
145
+ this [ kIncrementsPortRef ] = true ;
146
+ this [ kStartedReading ] = false ;
147
+ this . on ( 'end' , ( ) => {
148
+ if ( this [ kIncrementsPortRef ] && -- this [ kPort ] [ kWaitingStreams ] === 0 )
149
+ this [ kPort ] . unref ( ) ;
150
+ } ) ;
151
+ }
152
+
153
+ _read ( ) {
154
+ if ( ! this [ kStartedReading ] && this [ kIncrementsPortRef ] ) {
155
+ this [ kStartedReading ] = true ;
156
+ if ( this [ kPort ] [ kWaitingStreams ] ++ === 0 )
157
+ this [ kPort ] . ref ( ) ;
158
+ }
159
+
160
+ this [ kPort ] . postMessage ( {
161
+ type : 'stdioWantsMoreData' ,
162
+ stream : this [ kName ]
163
+ } ) ;
164
+ }
165
+ }
166
+
167
+ class WritableWorkerStdio extends Writable {
168
+ constructor ( port , name ) {
169
+ super ( { decodeStrings : false } ) ;
170
+ this [ kPort ] = port ;
171
+ this [ kName ] = name ;
172
+ this [ kWritableCallbacks ] = [ ] ;
173
+ }
174
+
175
+ _write ( chunk , encoding , cb ) {
176
+ this [ kPort ] . postMessage ( {
177
+ type : 'stdioPayload' ,
178
+ stream : this [ kName ] ,
179
+ chunk,
180
+ encoding
181
+ } ) ;
182
+ this [ kWritableCallbacks ] . push ( cb ) ;
183
+ if ( this [ kPort ] [ kWaitingStreams ] ++ === 0 )
184
+ this [ kPort ] . ref ( ) ;
185
+ }
186
+
187
+ _final ( cb ) {
188
+ this [ kPort ] . postMessage ( {
189
+ type : 'stdioPayload' ,
190
+ stream : this [ kName ] ,
191
+ chunk : null
192
+ } ) ;
193
+ cb ( ) ;
194
+ }
195
+
196
+ [ kStdioWantsMoreDataCallback ] ( ) {
197
+ const cbs = this [ kWritableCallbacks ] ;
198
+ this [ kWritableCallbacks ] = [ ] ;
199
+ for ( const cb of cbs )
200
+ cb ( ) ;
201
+ if ( ( this [ kPort ] [ kWaitingStreams ] -= cbs . length ) === 0 )
202
+ this [ kPort ] . unref ( ) ;
203
+ }
204
+ }
205
+
132
206
class Worker extends EventEmitter {
133
207
constructor ( filename , options = { } ) {
134
208
super ( ) ;
@@ -154,8 +228,25 @@ class Worker extends EventEmitter {
154
228
this [ kPort ] . on ( 'message' , ( data ) => this [ kOnMessage ] ( data ) ) ;
155
229
this [ kPort ] . start ( ) ;
156
230
this [ kPort ] . unref ( ) ;
231
+ this [ kPort ] [ kWaitingStreams ] = 0 ;
157
232
debug ( `[${ threadId } ] created Worker with ID ${ this . threadId } ` ) ;
158
233
234
+ let stdin = null ;
235
+ if ( options . stdin )
236
+ stdin = new WritableWorkerStdio ( this [ kPort ] , 'stdin' ) ;
237
+ const stdout = new ReadableWorkerStdio ( this [ kPort ] , 'stdout' ) ;
238
+ if ( ! options . stdout ) {
239
+ stdout [ kIncrementsPortRef ] = false ;
240
+ pipeWithoutWarning ( stdout , process . stdout ) ;
241
+ }
242
+ const stderr = new ReadableWorkerStdio ( this [ kPort ] , 'stderr' ) ;
243
+ if ( ! options . stderr ) {
244
+ stderr [ kIncrementsPortRef ] = false ;
245
+ pipeWithoutWarning ( stderr , process . stderr ) ;
246
+ }
247
+
248
+ this [ kParentSideStdio ] = { stdin, stdout, stderr } ;
249
+
159
250
const { port1, port2 } = new MessageChannel ( ) ;
160
251
this [ kPublicPort ] = port1 ;
161
252
this [ kPublicPort ] . on ( 'message' , ( message ) => this . emit ( 'message' , message ) ) ;
@@ -165,7 +256,8 @@ class Worker extends EventEmitter {
165
256
filename,
166
257
doEval : ! ! options . eval ,
167
258
workerData : options . workerData ,
168
- publicPort : port2
259
+ publicPort : port2 ,
260
+ hasStdin : ! ! options . stdin
169
261
} , [ port2 ] ) ;
170
262
// Actually start the new thread now that everything is in place.
171
263
this [ kHandle ] . startThread ( ) ;
@@ -197,6 +289,16 @@ class Worker extends EventEmitter {
197
289
return this [ kOnCouldNotSerializeErr ] ( ) ;
198
290
case 'errorMessage' :
199
291
return this [ kOnErrorMessage ] ( message . error ) ;
292
+ case 'stdioPayload' :
293
+ {
294
+ const { stream, chunk, encoding } = message ;
295
+ return this [ kParentSideStdio ] [ stream ] . push ( chunk , encoding ) ;
296
+ }
297
+ case 'stdioWantsMoreData' :
298
+ {
299
+ const { stream } = message ;
300
+ return this [ kParentSideStdio ] [ stream ] [ kStdioWantsMoreDataCallback ] ( ) ;
301
+ }
200
302
}
201
303
202
304
assert . fail ( `Unknown worker message type ${ message . type } ` ) ;
@@ -207,6 +309,18 @@ class Worker extends EventEmitter {
207
309
this [ kHandle ] = null ;
208
310
this [ kPort ] = null ;
209
311
this [ kPublicPort ] = null ;
312
+
313
+ const { stdout, stderr } = this [ kParentSideStdio ] ;
314
+ this [ kParentSideStdio ] = null ;
315
+
316
+ if ( ! stdout . _readableState . ended ) {
317
+ debug ( `[${ threadId } ] explicitly closes stdout for ${ this . threadId } ` ) ;
318
+ stdout . push ( null ) ;
319
+ }
320
+ if ( ! stderr . _readableState . ended ) {
321
+ debug ( `[${ threadId } ] explicitly closes stderr for ${ this . threadId } ` ) ;
322
+ stderr . push ( null ) ;
323
+ }
210
324
}
211
325
212
326
postMessage ( ...args ) {
@@ -243,6 +357,27 @@ class Worker extends EventEmitter {
243
357
244
358
return this [ kHandle ] . threadId ;
245
359
}
360
+
361
+ get stdin ( ) {
362
+ return this [ kParentSideStdio ] . stdin ;
363
+ }
364
+
365
+ get stdout ( ) {
366
+ return this [ kParentSideStdio ] . stdout ;
367
+ }
368
+
369
+ get stderr ( ) {
370
+ return this [ kParentSideStdio ] . stderr ;
371
+ }
372
+ }
373
+
374
+ const workerStdio = { } ;
375
+ if ( ! isMainThread ) {
376
+ const port = getEnvMessagePort ( ) ;
377
+ port [ kWaitingStreams ] = 0 ;
378
+ workerStdio . stdin = new ReadableWorkerStdio ( port , 'stdin' ) ;
379
+ workerStdio . stdout = new WritableWorkerStdio ( port , 'stdout' ) ;
380
+ workerStdio . stderr = new WritableWorkerStdio ( port , 'stderr' ) ;
246
381
}
247
382
248
383
let originalFatalException ;
@@ -256,10 +391,14 @@ function setupChild(evalScript) {
256
391
257
392
port . on ( 'message' , ( message ) => {
258
393
if ( message . type === 'loadScript' ) {
259
- const { filename, doEval, workerData, publicPort } = message ;
394
+ const { filename, doEval, workerData, publicPort, hasStdin } = message ;
260
395
publicWorker . parentPort = publicPort ;
261
396
setupPortReferencing ( publicPort , publicPort , 'message' ) ;
262
397
publicWorker . workerData = workerData ;
398
+
399
+ if ( ! hasStdin )
400
+ workerStdio . stdin . push ( null ) ;
401
+
263
402
debug ( `[${ threadId } ] starts worker script ${ filename } ` +
264
403
`(eval = ${ eval } ) at cwd = ${ process . cwd ( ) } ` ) ;
265
404
port . unref ( ) ;
@@ -271,6 +410,14 @@ function setupChild(evalScript) {
271
410
require ( 'module' ) . runMain ( ) ;
272
411
}
273
412
return ;
413
+ } else if ( message . type === 'stdioPayload' ) {
414
+ const { stream, chunk, encoding } = message ;
415
+ workerStdio [ stream ] . push ( chunk , encoding ) ;
416
+ return ;
417
+ } else if ( message . type === 'stdioWantsMoreData' ) {
418
+ const { stream } = message ;
419
+ workerStdio [ stream ] [ kStdioWantsMoreDataCallback ] ( ) ;
420
+ return ;
274
421
}
275
422
276
423
assert . fail ( `Unknown worker message type ${ message . type } ` ) ;
@@ -317,11 +464,24 @@ function deserializeError(error) {
317
464
error . byteLength ) . toString ( 'utf8' ) ;
318
465
}
319
466
467
+ function pipeWithoutWarning ( source , dest ) {
468
+ const sourceMaxListeners = source . _maxListeners ;
469
+ const destMaxListeners = dest . _maxListeners ;
470
+ source . setMaxListeners ( Infinity ) ;
471
+ dest . setMaxListeners ( Infinity ) ;
472
+
473
+ source . pipe ( dest ) ;
474
+
475
+ source . _maxListeners = sourceMaxListeners ;
476
+ dest . _maxListeners = destMaxListeners ;
477
+ }
478
+
320
479
module . exports = {
321
480
MessagePort,
322
481
MessageChannel,
323
482
threadId,
324
483
Worker,
325
484
setupChild,
326
- isMainThread
485
+ isMainThread,
486
+ workerStdio
327
487
} ;
0 commit comments