@parcel/workers 2.0.0-nightly.130 → 2.0.0-nightly.1303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/WorkerFarm.js CHANGED
@@ -20,20 +20,20 @@ import {
20
20
  restoreDeserializedObject,
21
21
  serialize,
22
22
  } from '@parcel/core';
23
- import ThrowableDiagnostic, {anyToDiagnostic} from '@parcel/diagnostic';
23
+ import ThrowableDiagnostic, {anyToDiagnostic, md} from '@parcel/diagnostic';
24
24
  import Worker, {type WorkerCall} from './Worker';
25
25
  import cpuCount from './cpuCount';
26
26
  import Handle from './Handle';
27
27
  import {child} from './childState';
28
28
  import {detectBackend} from './backend';
29
- import Profiler from './Profiler';
30
- import Trace from './Trace';
29
+ import {SamplingProfiler, Trace} from '@parcel/profiler';
31
30
  import fs from 'fs';
32
31
  import logger from '@parcel/logger';
33
32
 
34
- let profileId = 1;
35
33
  let referenceId = 1;
36
34
 
35
+ export opaque type SharedReference = number;
36
+
37
37
  export type FarmOptions = {|
38
38
  maxConcurrentWorkers: number,
39
39
  maxConcurrentCallsPerWorker: number,
@@ -42,7 +42,8 @@ export type FarmOptions = {|
42
42
  warmWorkers: boolean,
43
43
  workerPath?: FilePath,
44
44
  backend: BackendType,
45
- patchConsole?: boolean,
45
+ shouldPatchConsole?: boolean,
46
+ shouldTrace?: boolean,
46
47
  |};
47
48
 
48
49
  type WorkerModule = {|
@@ -52,8 +53,8 @@ type WorkerModule = {|
52
53
  export type WorkerApi = {|
53
54
  callMaster(CallRequest, ?boolean): Promise<mixed>,
54
55
  createReverseHandle(fn: HandleFunction): Handle,
55
- getSharedReference(ref: number): mixed,
56
- resolveSharedReference(value: mixed): ?number,
56
+ getSharedReference(ref: SharedReference): mixed,
57
+ resolveSharedReference(value: mixed): ?SharedReference,
57
58
  callChild?: (childId: number, request: HandleCallRequest) => Promise<mixed>,
58
59
  |};
59
60
 
@@ -67,14 +68,16 @@ export default class WorkerFarm extends EventEmitter {
67
68
  callQueue: Array<WorkerCall> = [];
68
69
  ending: boolean = false;
69
70
  localWorker: WorkerModule;
71
+ localWorkerInit: ?Promise<void>;
70
72
  options: FarmOptions;
71
73
  run: HandleFunction;
72
74
  warmWorkers: number = 0;
73
75
  workers: Map<number, Worker> = new Map();
74
76
  handles: Map<number, Handle> = new Map();
75
- sharedReferences: Map<number, mixed> = new Map();
76
- sharedReferencesByValue: Map<mixed, number> = new Map();
77
- profiler: ?Profiler;
77
+ sharedReferences: Map<SharedReference, mixed> = new Map();
78
+ sharedReferencesByValue: Map<mixed, SharedReference> = new Map();
79
+ serializedSharedReferences: Map<SharedReference, ?ArrayBuffer> = new Map();
80
+ profiler: ?SamplingProfiler;
78
81
 
79
82
  constructor(farmOptions: $Shape<FarmOptions> = {}) {
80
83
  super();
@@ -94,12 +97,39 @@ export default class WorkerFarm extends EventEmitter {
94
97
 
95
98
  // $FlowFixMe this must be dynamic
96
99
  this.localWorker = require(this.options.workerPath);
100
+ this.localWorkerInit =
101
+ this.localWorker.childInit != null ? this.localWorker.childInit() : null;
97
102
  this.run = this.createHandle('run');
98
103
 
104
+ // Worker thread stdout is by default piped into the process stdout, if there are enough worker
105
+ // threads to exceed the default listener limit, then anything else piping into stdout will trigger
106
+ // the `MaxListenersExceededWarning`, so we should ensure the max listeners is at least equal to the
107
+ // number of workers + 1 for the main thread.
108
+ //
109
+ // Note this can't be fixed easily where other things pipe into stdout - even after starting > 10 worker
110
+ // threads `process.stdout.getMaxListeners()` will still return 10, however adding another pipe into `stdout`
111
+ // will give the warning with `<worker count + 1>` as the number of listeners.
112
+ process.stdout.setMaxListeners(
113
+ Math.max(
114
+ process.stdout.getMaxListeners(),
115
+ WorkerFarm.getNumWorkers() + 1,
116
+ ),
117
+ );
118
+
99
119
  this.startMaxWorkers();
100
120
  }
101
121
 
102
- workerApi = {
122
+ workerApi: {|
123
+ callChild: (childId: number, request: HandleCallRequest) => Promise<mixed>,
124
+ callMaster: (
125
+ request: CallRequest,
126
+ awaitResponse?: ?boolean,
127
+ ) => Promise<mixed>,
128
+ createReverseHandle: (fn: HandleFunction) => Handle,
129
+ getSharedReference: (ref: SharedReference) => mixed,
130
+ resolveSharedReference: (value: mixed) => void | SharedReference,
131
+ runHandle: (handle: Handle, args: Array<any>) => Promise<mixed>,
132
+ |} = {
103
133
  callMaster: async (
104
134
  request: CallRequest,
105
135
  awaitResponse: ?boolean = true,
@@ -122,7 +152,13 @@ export default class WorkerFarm extends EventEmitter {
122
152
  retries: 0,
123
153
  });
124
154
  }),
125
- getSharedReference: (ref: number) => this.sharedReferences.get(ref),
155
+ runHandle: (handle: Handle, args: Array<any>): Promise<mixed> =>
156
+ this.workerApi.callChild(nullthrows(handle.childId), {
157
+ handle: handle.id,
158
+ args,
159
+ }),
160
+ getSharedReference: (ref: SharedReference) =>
161
+ this.sharedReferences.get(ref),
126
162
  resolveSharedReference: (value: mixed) =>
127
163
  this.sharedReferencesByValue.get(value),
128
164
  };
@@ -155,30 +191,46 @@ export default class WorkerFarm extends EventEmitter {
155
191
  );
156
192
  }
157
193
 
158
- createHandle(method: string): HandleFunction {
159
- return (...args) => {
194
+ createHandle(method: string, useMainThread: boolean = false): HandleFunction {
195
+ if (!this.options.useLocalWorker) {
196
+ useMainThread = false;
197
+ }
198
+
199
+ return async (...args) => {
160
200
  // Child process workers are slow to start (~600ms).
161
201
  // While we're waiting, just run on the main thread.
162
202
  // This significantly speeds up startup time.
163
- if (this.shouldUseRemoteWorkers()) {
203
+ if (this.shouldUseRemoteWorkers() && !useMainThread) {
164
204
  return this.addCall(method, [...args, false]);
165
205
  } else {
166
206
  if (this.options.warmWorkers && this.shouldStartRemoteWorkers()) {
167
207
  this.warmupWorker(method, args);
168
208
  }
169
209
 
170
- let processedArgs = restoreDeserializedObject(
171
- prepareForSerialization([...args, false]),
172
- );
210
+ let processedArgs;
211
+ if (!useMainThread) {
212
+ processedArgs = restoreDeserializedObject(
213
+ prepareForSerialization([...args, false]),
214
+ );
215
+ } else {
216
+ processedArgs = args;
217
+ }
218
+
219
+ if (this.localWorkerInit != null) {
220
+ await this.localWorkerInit;
221
+ this.localWorkerInit = null;
222
+ }
173
223
  return this.localWorker[method](this.workerApi, ...processedArgs);
174
224
  }
175
225
  };
176
226
  }
177
227
 
178
- onError(error: ErrorWithCode, worker: Worker) {
228
+ onError(error: ErrorWithCode, worker: Worker): void | Promise<void> {
179
229
  // Handle ipc errors
180
230
  if (error.code === 'ERR_IPC_CHANNEL_CLOSED') {
181
231
  return this.stopWorker(worker);
232
+ } else {
233
+ logger.error(error, '@parcel/workers');
182
234
  }
183
235
  }
184
236
 
@@ -186,7 +238,9 @@ export default class WorkerFarm extends EventEmitter {
186
238
  let worker = new Worker({
187
239
  forcedKillTime: this.options.forcedKillTime,
188
240
  backend: this.options.backend,
189
- patchConsole: this.options.patchConsole,
241
+ shouldPatchConsole: this.options.shouldPatchConsole,
242
+ shouldTrace: this.options.shouldTrace,
243
+ sharedReferences: this.sharedReferences,
190
244
  });
191
245
 
192
246
  worker.fork(nullthrows(this.options.workerPath));
@@ -231,7 +285,11 @@ export default class WorkerFarm extends EventEmitter {
231
285
  this.startChild();
232
286
  }
233
287
 
234
- for (let worker of this.workers.values()) {
288
+ let workers = [...this.workers.values()].sort(
289
+ (a, b) => a.calls.size - b.calls.size,
290
+ );
291
+
292
+ for (let worker of workers) {
235
293
  if (!this.callQueue.length) {
236
294
  break;
237
295
  }
@@ -241,11 +299,24 @@ export default class WorkerFarm extends EventEmitter {
241
299
  }
242
300
 
243
301
  if (worker.calls.size < this.options.maxConcurrentCallsPerWorker) {
244
- worker.call(this.callQueue.shift());
302
+ this.callWorker(worker, this.callQueue.shift());
245
303
  }
246
304
  }
247
305
  }
248
306
 
307
+ async callWorker(worker: Worker, call: WorkerCall): Promise<void> {
308
+ for (let ref of this.sharedReferences.keys()) {
309
+ if (!worker.sentSharedReferences.has(ref)) {
310
+ await worker.sendSharedReference(
311
+ ref,
312
+ this.getSerializedSharedReference(ref),
313
+ );
314
+ }
315
+ }
316
+
317
+ worker.call(call);
318
+ }
319
+
249
320
  async processRequest(
250
321
  data: {|
251
322
  location: FilePath,
@@ -255,7 +326,7 @@ export default class WorkerFarm extends EventEmitter {
255
326
  let {method, args, location, awaitResponse, idx, handle: handleId} = data;
256
327
  let mod;
257
328
  if (handleId != null) {
258
- mod = nullthrows(this.handles.get(handleId)).fn;
329
+ mod = nullthrows(this.handles.get(handleId)?.fn);
259
330
  } else if (location) {
260
331
  // $FlowFixMe this must be dynamic
261
332
  mod = require(location);
@@ -286,7 +357,6 @@ export default class WorkerFarm extends EventEmitter {
286
357
  }
287
358
  } else {
288
359
  // ESModule default interop
289
- // $FlowFixMe
290
360
  if (mod.__esModule && !mod[method] && mod.default) {
291
361
  mod = mod.default;
292
362
  }
@@ -331,6 +401,10 @@ export default class WorkerFarm extends EventEmitter {
331
401
  async end(): Promise<void> {
332
402
  this.ending = true;
333
403
 
404
+ await Promise.all(
405
+ Array.from(this.workers.values()).map(worker => this.stopWorker(worker)),
406
+ );
407
+
334
408
  for (let handle of this.handles.values()) {
335
409
  handle.dispose();
336
410
  }
@@ -338,9 +412,6 @@ export default class WorkerFarm extends EventEmitter {
338
412
  this.sharedReferences = new Map();
339
413
  this.sharedReferencesByValue = new Map();
340
414
 
341
- await Promise.all(
342
- Array.from(this.workers.values()).map(worker => this.stopWorker(worker)),
343
- );
344
415
  this.ending = false;
345
416
  }
346
417
 
@@ -362,40 +433,37 @@ export default class WorkerFarm extends EventEmitter {
362
433
  );
363
434
  }
364
435
 
365
- createReverseHandle(fn: HandleFunction) {
366
- let handle = new Handle({fn, workerApi: this.workerApi});
436
+ createReverseHandle(fn: HandleFunction): Handle {
437
+ let handle = new Handle({fn});
367
438
  this.handles.set(handle.id, handle);
368
439
  return handle;
369
440
  }
370
441
 
371
- async createSharedReference(value: mixed) {
442
+ createSharedReference(
443
+ value: mixed,
444
+ isCacheable: boolean = true,
445
+ ): {|ref: SharedReference, dispose(): Promise<mixed>|} {
372
446
  let ref = referenceId++;
373
447
  this.sharedReferences.set(ref, value);
374
448
  this.sharedReferencesByValue.set(value, ref);
375
- let promises = [];
376
- for (let worker of this.workers.values()) {
377
- promises.push(
378
- new Promise((resolve, reject) => {
379
- worker.call({
380
- method: 'createSharedReference',
381
- args: [ref, value],
382
- resolve,
383
- reject,
384
- retries: 0,
385
- });
386
- }),
387
- );
449
+ if (!isCacheable) {
450
+ this.serializedSharedReferences.set(ref, null);
388
451
  }
389
452
 
390
- await Promise.all(promises);
391
-
392
453
  return {
393
454
  ref,
394
455
  dispose: () => {
395
456
  this.sharedReferences.delete(ref);
396
457
  this.sharedReferencesByValue.delete(value);
458
+ this.serializedSharedReferences.delete(ref);
459
+
397
460
  let promises = [];
398
461
  for (let worker of this.workers.values()) {
462
+ if (!worker.sentSharedReferences.has(ref)) {
463
+ continue;
464
+ }
465
+
466
+ worker.sentSharedReferences.delete(ref);
399
467
  promises.push(
400
468
  new Promise((resolve, reject) => {
401
469
  worker.call({
@@ -403,6 +471,7 @@ export default class WorkerFarm extends EventEmitter {
403
471
  args: [ref],
404
472
  resolve,
405
473
  reject,
474
+ skipReadyCheck: true,
406
475
  retries: 0,
407
476
  });
408
477
  }),
@@ -413,6 +482,24 @@ export default class WorkerFarm extends EventEmitter {
413
482
  };
414
483
  }
415
484
 
485
+ getSerializedSharedReference(ref: SharedReference): ArrayBuffer {
486
+ let cached = this.serializedSharedReferences.get(ref);
487
+ if (cached) {
488
+ return cached;
489
+ }
490
+
491
+ let value = this.sharedReferences.get(ref);
492
+ let buf = serialize(value).buffer;
493
+
494
+ // If the reference was created with the isCacheable option set to false,
495
+ // serializedSharedReferences will contain `null` as the value.
496
+ if (cached !== null) {
497
+ this.serializedSharedReferences.set(ref, buf);
498
+ }
499
+
500
+ return buf;
501
+ }
502
+
416
503
  async startProfile() {
417
504
  let promises = [];
418
505
  for (let worker of this.workers.values()) {
@@ -424,12 +511,13 @@ export default class WorkerFarm extends EventEmitter {
424
511
  resolve,
425
512
  reject,
426
513
  retries: 0,
514
+ skipReadyCheck: true,
427
515
  });
428
516
  }),
429
517
  );
430
518
  }
431
519
 
432
- this.profiler = new Profiler();
520
+ this.profiler = new SamplingProfiler();
433
521
 
434
522
  promises.push(this.profiler.startProfiling());
435
523
  await Promise.all(promises);
@@ -453,6 +541,7 @@ export default class WorkerFarm extends EventEmitter {
453
541
  resolve,
454
542
  reject,
455
543
  retries: 0,
544
+ skipReadyCheck: true,
456
545
  });
457
546
  }),
458
547
  );
@@ -460,7 +549,7 @@ export default class WorkerFarm extends EventEmitter {
460
549
 
461
550
  var profiles = await Promise.all(promises);
462
551
  let trace = new Trace();
463
- let filename = `profile-${profileId++}.trace`;
552
+ let filename = `profile-${getTimeId()}.trace`;
464
553
  let stream = trace.pipe(fs.createWriteStream(filename));
465
554
 
466
555
  for (let profile of profiles) {
@@ -474,21 +563,84 @@ export default class WorkerFarm extends EventEmitter {
474
563
 
475
564
  logger.info({
476
565
  origin: '@parcel/workers',
477
- message: `Wrote profile to ${filename}`,
566
+ message: md`Wrote profile to ${filename}`,
478
567
  });
479
568
  }
480
569
 
481
- static getNumWorkers() {
570
+ async callAllWorkers(method: string, args: Array<any>) {
571
+ let promises = [];
572
+ for (let worker of this.workers.values()) {
573
+ promises.push(
574
+ new Promise((resolve, reject) => {
575
+ worker.call({
576
+ method,
577
+ args,
578
+ resolve,
579
+ reject,
580
+ retries: 0,
581
+ });
582
+ }),
583
+ );
584
+ }
585
+
586
+ promises.push(this.localWorker[method](this.workerApi, ...args));
587
+ await Promise.all(promises);
588
+ }
589
+
590
+ async takeHeapSnapshot() {
591
+ let snapshotId = getTimeId();
592
+
593
+ try {
594
+ let snapshotPaths = await Promise.all(
595
+ [...this.workers.values()].map(
596
+ worker =>
597
+ new Promise((resolve, reject) => {
598
+ worker.call({
599
+ method: 'takeHeapSnapshot',
600
+ args: [snapshotId],
601
+ resolve,
602
+ reject,
603
+ retries: 0,
604
+ skipReadyCheck: true,
605
+ });
606
+ }),
607
+ ),
608
+ );
609
+
610
+ logger.info({
611
+ origin: '@parcel/workers',
612
+ message: md`Wrote heap snapshots to the following paths:\n${snapshotPaths.join(
613
+ '\n',
614
+ )}`,
615
+ });
616
+ } catch {
617
+ logger.error({
618
+ origin: '@parcel/workers',
619
+ message: 'Unable to take heap snapshots. Note: requires Node 11.13.0+',
620
+ });
621
+ }
622
+ }
623
+
624
+ static getNumWorkers(): number {
482
625
  return process.env.PARCEL_WORKERS
483
626
  ? parseInt(process.env.PARCEL_WORKERS, 10)
484
- : cpuCount();
627
+ : Math.ceil(cpuCount() / 2);
485
628
  }
486
629
 
487
- static isWorker() {
630
+ static isWorker(): boolean {
488
631
  return !!child;
489
632
  }
490
633
 
491
- static getWorkerApi() {
634
+ static getWorkerApi(): {|
635
+ callMaster: (
636
+ request: CallRequest,
637
+ awaitResponse?: ?boolean,
638
+ ) => Promise<mixed>,
639
+ createReverseHandle: (fn: (...args: Array<any>) => mixed) => Handle,
640
+ getSharedReference: (ref: SharedReference) => mixed,
641
+ resolveSharedReference: (value: mixed) => void | SharedReference,
642
+ runHandle: (handle: Handle, args: Array<any>) => Promise<mixed>,
643
+ |} {
492
644
  invariant(
493
645
  child != null,
494
646
  'WorkerFarm.getWorkerApi can only be called within workers',
@@ -496,7 +648,20 @@ export default class WorkerFarm extends EventEmitter {
496
648
  return child.workerApi;
497
649
  }
498
650
 
499
- static getConcurrentCallsPerWorker() {
500
- return parseInt(process.env.PARCEL_MAX_CONCURRENT_CALLS, 10) || 5;
651
+ static getConcurrentCallsPerWorker(): number {
652
+ return parseInt(process.env.PARCEL_MAX_CONCURRENT_CALLS, 10) || 30;
501
653
  }
502
654
  }
655
+
656
+ function getTimeId() {
657
+ let now = new Date();
658
+ return (
659
+ String(now.getFullYear()) +
660
+ String(now.getMonth() + 1).padStart(2, '0') +
661
+ String(now.getDate()).padStart(2, '0') +
662
+ '-' +
663
+ String(now.getHours()).padStart(2, '0') +
664
+ String(now.getMinutes()).padStart(2, '0') +
665
+ String(now.getSeconds()).padStart(2, '0')
666
+ );
667
+ }
package/src/bus.js CHANGED
@@ -20,4 +20,4 @@ class Bus extends EventEmitter {
20
20
  }
21
21
  }
22
22
 
23
- export default new Bus();
23
+ export default (new Bus(): Bus);