@parcel/workers 2.0.0-beta.3.1 → 2.0.0-dev.1510
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/index.d.ts +23 -0
- package/lib/Handle.js +7 -33
- package/lib/Worker.js +35 -86
- package/lib/WorkerFarm.js +156 -244
- package/lib/backend.js +4 -6
- package/lib/bus.js +6 -24
- package/lib/child.js +67 -132
- package/lib/childState.js +2 -4
- package/lib/core-worker.browser.js +4 -0
- package/lib/core-worker.js +4 -0
- package/lib/cpuCount.js +15 -25
- package/lib/index.js +17 -47
- package/lib/process/ProcessChild.js +4 -42
- package/lib/process/ProcessWorker.js +2 -37
- package/lib/threads/ThreadsChild.js +3 -45
- package/lib/threads/ThreadsWorker.js +0 -30
- package/lib/web/WebChild.js +44 -0
- package/lib/web/WebWorker.js +85 -0
- package/package.json +14 -7
- package/src/Worker.js +24 -14
- package/src/WorkerFarm.js +139 -36
- package/src/backend.js +5 -0
- package/src/bus.js +2 -1
- package/src/child.js +36 -12
- package/src/core-worker.browser.js +3 -0
- package/src/core-worker.js +2 -0
- package/src/cpuCount.js +17 -8
- package/src/index.js +7 -1
- package/src/process/ProcessChild.js +1 -0
- package/src/types.js +1 -1
- package/src/web/WebChild.js +50 -0
- package/src/web/WebWorker.js +85 -0
- package/test/cpuCount.test.js +1 -1
- package/test/workerfarm.js +1 -1
- package/lib/Profiler.js +0 -86
- package/lib/Trace.js +0 -139
- package/src/Profiler.js +0 -93
- package/src/Trace.js +0 -125
package/src/Worker.js
CHANGED
|
@@ -23,6 +23,7 @@ type WorkerOpts = {|
|
|
|
23
23
|
forcedKillTime: number,
|
|
24
24
|
backend: BackendType,
|
|
25
25
|
shouldPatchConsole?: boolean,
|
|
26
|
+
shouldTrace?: boolean,
|
|
26
27
|
sharedReferences: $ReadOnlyMap<SharedReference, mixed>,
|
|
27
28
|
|};
|
|
28
29
|
|
|
@@ -31,7 +32,7 @@ export default class Worker extends EventEmitter {
|
|
|
31
32
|
+options: WorkerOpts;
|
|
32
33
|
worker: WorkerImpl;
|
|
33
34
|
id: number = WORKER_ID++;
|
|
34
|
-
|
|
35
|
+
sentSharedReferences: Set<SharedReference> = new Set();
|
|
35
36
|
|
|
36
37
|
calls: Map<number, WorkerCall> = new Map();
|
|
37
38
|
exitCode: ?number = null;
|
|
@@ -47,18 +48,25 @@ export default class Worker extends EventEmitter {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
async fork(forkModule: FilePath) {
|
|
50
|
-
let filteredArgs =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
) {
|
|
60
|
-
filteredArgs
|
|
61
|
-
|
|
51
|
+
let filteredArgs = [];
|
|
52
|
+
if (process.execArgv) {
|
|
53
|
+
filteredArgs = process.execArgv.filter(
|
|
54
|
+
v =>
|
|
55
|
+
!/^--(debug|inspect|no-opt|max-old-space-size=|max-semi-space-size=|expose-gc)/.test(
|
|
56
|
+
v,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < filteredArgs.length; i++) {
|
|
61
|
+
let arg = filteredArgs[i];
|
|
62
|
+
let isArgWithParam =
|
|
63
|
+
((arg === '-r' || arg === '--require') &&
|
|
64
|
+
filteredArgs[i + 1] === '@parcel/register') ||
|
|
65
|
+
arg === '--title';
|
|
66
|
+
if (isArgWithParam) {
|
|
67
|
+
filteredArgs.splice(i, 2);
|
|
68
|
+
i--;
|
|
69
|
+
}
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
|
|
@@ -67,7 +75,7 @@ export default class Worker extends EventEmitter {
|
|
|
67
75
|
// arg parsing logic adapted from https://stackoverflow.com/a/46946420/2352201
|
|
68
76
|
let opts = [''];
|
|
69
77
|
let quote = false;
|
|
70
|
-
for (let c of nullthrows(process.env.NODE_OPTIONS.match(
|
|
78
|
+
for (let c of nullthrows(process.env.NODE_OPTIONS.match(/.|^$/g))) {
|
|
71
79
|
if (c === '"') {
|
|
72
80
|
quote = !quote;
|
|
73
81
|
} else if (!quote && c === ' ') {
|
|
@@ -107,6 +115,7 @@ export default class Worker extends EventEmitter {
|
|
|
107
115
|
forkModule,
|
|
108
116
|
{
|
|
109
117
|
shouldPatchConsole: !!this.options.shouldPatchConsole,
|
|
118
|
+
shouldTrace: !!this.options.shouldTrace,
|
|
110
119
|
},
|
|
111
120
|
],
|
|
112
121
|
retries: 0,
|
|
@@ -135,6 +144,7 @@ export default class Worker extends EventEmitter {
|
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
sendSharedReference(ref: SharedReference, value: mixed): Promise<any> {
|
|
147
|
+
this.sentSharedReferences.add(ref);
|
|
138
148
|
return new Promise((resolve, reject) => {
|
|
139
149
|
this.call({
|
|
140
150
|
method: 'createSharedReference',
|
package/src/WorkerFarm.js
CHANGED
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
} from './types';
|
|
12
12
|
import type {HandleFunction} from './Handle';
|
|
13
13
|
|
|
14
|
+
import * as coreWorker from './core-worker';
|
|
15
|
+
import * as bus from './bus';
|
|
14
16
|
import invariant from 'assert';
|
|
15
17
|
import nullthrows from 'nullthrows';
|
|
16
18
|
import EventEmitter from 'events';
|
|
@@ -26,8 +28,7 @@ import cpuCount from './cpuCount';
|
|
|
26
28
|
import Handle from './Handle';
|
|
27
29
|
import {child} from './childState';
|
|
28
30
|
import {detectBackend} from './backend';
|
|
29
|
-
import
|
|
30
|
-
import Trace from './Trace';
|
|
31
|
+
import {SamplingProfiler, Trace} from '@parcel/profiler';
|
|
31
32
|
import fs from 'fs';
|
|
32
33
|
import logger from '@parcel/logger';
|
|
33
34
|
|
|
@@ -44,11 +45,13 @@ export type FarmOptions = {|
|
|
|
44
45
|
workerPath?: FilePath,
|
|
45
46
|
backend: BackendType,
|
|
46
47
|
shouldPatchConsole?: boolean,
|
|
48
|
+
shouldTrace?: boolean,
|
|
47
49
|
|};
|
|
48
50
|
|
|
49
|
-
type WorkerModule = {
|
|
51
|
+
type WorkerModule = {
|
|
50
52
|
+[string]: (...args: Array<mixed>) => Promise<mixed>,
|
|
51
|
-
|
|
53
|
+
...
|
|
54
|
+
};
|
|
52
55
|
|
|
53
56
|
export type WorkerApi = {|
|
|
54
57
|
callMaster(CallRequest, ?boolean): Promise<mixed>,
|
|
@@ -60,6 +63,8 @@ export type WorkerApi = {|
|
|
|
60
63
|
|
|
61
64
|
export {Handle};
|
|
62
65
|
|
|
66
|
+
const DEFAULT_MAX_CONCURRENT_CALLS: number = 30;
|
|
67
|
+
|
|
63
68
|
/**
|
|
64
69
|
* workerPath should always be defined inside farmOptions
|
|
65
70
|
*/
|
|
@@ -68,20 +73,25 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
68
73
|
callQueue: Array<WorkerCall> = [];
|
|
69
74
|
ending: boolean = false;
|
|
70
75
|
localWorker: WorkerModule;
|
|
76
|
+
localWorkerInit: ?Promise<void>;
|
|
71
77
|
options: FarmOptions;
|
|
72
78
|
run: HandleFunction;
|
|
73
79
|
warmWorkers: number = 0;
|
|
80
|
+
readyWorkers: number = 0;
|
|
74
81
|
workers: Map<number, Worker> = new Map();
|
|
75
82
|
handles: Map<number, Handle> = new Map();
|
|
76
83
|
sharedReferences: Map<SharedReference, mixed> = new Map();
|
|
77
84
|
sharedReferencesByValue: Map<mixed, SharedReference> = new Map();
|
|
78
|
-
|
|
85
|
+
serializedSharedReferences: Map<SharedReference, ?ArrayBuffer> = new Map();
|
|
86
|
+
profiler: ?SamplingProfiler;
|
|
79
87
|
|
|
80
88
|
constructor(farmOptions: $Shape<FarmOptions> = {}) {
|
|
81
89
|
super();
|
|
82
90
|
this.options = {
|
|
83
91
|
maxConcurrentWorkers: WorkerFarm.getNumWorkers(),
|
|
84
|
-
maxConcurrentCallsPerWorker: WorkerFarm.getConcurrentCallsPerWorker(
|
|
92
|
+
maxConcurrentCallsPerWorker: WorkerFarm.getConcurrentCallsPerWorker(
|
|
93
|
+
farmOptions.shouldTrace ? 1 : DEFAULT_MAX_CONCURRENT_CALLS,
|
|
94
|
+
),
|
|
85
95
|
forcedKillTime: 500,
|
|
86
96
|
warmWorkers: false,
|
|
87
97
|
useLocalWorker: true, // TODO: setting this to false makes some tests fail, figure out why
|
|
@@ -93,10 +103,40 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
93
103
|
throw new Error('Please provide a worker path!');
|
|
94
104
|
}
|
|
95
105
|
|
|
96
|
-
// $FlowFixMe
|
|
97
|
-
|
|
106
|
+
// $FlowFixMe
|
|
107
|
+
if (process.browser) {
|
|
108
|
+
if (this.options.workerPath === '@parcel/core/src/worker.js') {
|
|
109
|
+
this.localWorker = coreWorker;
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'No dynamic require possible: ' + this.options.workerPath,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// $FlowFixMe this must be dynamic
|
|
117
|
+
this.localWorker = require(this.options.workerPath);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.localWorkerInit =
|
|
121
|
+
this.localWorker.childInit != null ? this.localWorker.childInit() : null;
|
|
122
|
+
|
|
98
123
|
this.run = this.createHandle('run');
|
|
99
124
|
|
|
125
|
+
// Worker thread stdout is by default piped into the process stdout, if there are enough worker
|
|
126
|
+
// threads to exceed the default listener limit, then anything else piping into stdout will trigger
|
|
127
|
+
// the `MaxListenersExceededWarning`, so we should ensure the max listeners is at least equal to the
|
|
128
|
+
// number of workers + 1 for the main thread.
|
|
129
|
+
//
|
|
130
|
+
// Note this can't be fixed easily where other things pipe into stdout - even after starting > 10 worker
|
|
131
|
+
// threads `process.stdout.getMaxListeners()` will still return 10, however adding another pipe into `stdout`
|
|
132
|
+
// will give the warning with `<worker count + 1>` as the number of listeners.
|
|
133
|
+
process.stdout?.setMaxListeners(
|
|
134
|
+
Math.max(
|
|
135
|
+
process.stdout.getMaxListeners(),
|
|
136
|
+
WorkerFarm.getNumWorkers() + 1,
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
|
|
100
140
|
this.startMaxWorkers();
|
|
101
141
|
}
|
|
102
142
|
|
|
@@ -172,21 +212,35 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
172
212
|
);
|
|
173
213
|
}
|
|
174
214
|
|
|
175
|
-
createHandle(method: string): HandleFunction {
|
|
176
|
-
|
|
215
|
+
createHandle(method: string, useMainThread: boolean = false): HandleFunction {
|
|
216
|
+
if (!this.options.useLocalWorker) {
|
|
217
|
+
useMainThread = false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return async (...args) => {
|
|
177
221
|
// Child process workers are slow to start (~600ms).
|
|
178
222
|
// While we're waiting, just run on the main thread.
|
|
179
223
|
// This significantly speeds up startup time.
|
|
180
|
-
if (this.shouldUseRemoteWorkers()) {
|
|
224
|
+
if (this.shouldUseRemoteWorkers() && !useMainThread) {
|
|
181
225
|
return this.addCall(method, [...args, false]);
|
|
182
226
|
} else {
|
|
183
227
|
if (this.options.warmWorkers && this.shouldStartRemoteWorkers()) {
|
|
184
228
|
this.warmupWorker(method, args);
|
|
185
229
|
}
|
|
186
230
|
|
|
187
|
-
let processedArgs
|
|
188
|
-
|
|
189
|
-
|
|
231
|
+
let processedArgs;
|
|
232
|
+
if (!useMainThread) {
|
|
233
|
+
processedArgs = restoreDeserializedObject(
|
|
234
|
+
prepareForSerialization([...args, false]),
|
|
235
|
+
);
|
|
236
|
+
} else {
|
|
237
|
+
processedArgs = args;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (this.localWorkerInit != null) {
|
|
241
|
+
await this.localWorkerInit;
|
|
242
|
+
this.localWorkerInit = null;
|
|
243
|
+
}
|
|
190
244
|
return this.localWorker[method](this.workerApi, ...processedArgs);
|
|
191
245
|
}
|
|
192
246
|
};
|
|
@@ -206,6 +260,7 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
206
260
|
forcedKillTime: this.options.forcedKillTime,
|
|
207
261
|
backend: this.options.backend,
|
|
208
262
|
shouldPatchConsole: this.options.shouldPatchConsole,
|
|
263
|
+
shouldTrace: this.options.shouldTrace,
|
|
209
264
|
sharedReferences: this.sharedReferences,
|
|
210
265
|
});
|
|
211
266
|
|
|
@@ -213,7 +268,13 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
213
268
|
|
|
214
269
|
worker.on('request', data => this.processRequest(data, worker));
|
|
215
270
|
|
|
216
|
-
worker.on('ready', () =>
|
|
271
|
+
worker.on('ready', () => {
|
|
272
|
+
this.readyWorkers++;
|
|
273
|
+
if (this.readyWorkers === this.options.maxConcurrentWorkers) {
|
|
274
|
+
this.emit('ready');
|
|
275
|
+
}
|
|
276
|
+
this.processQueue();
|
|
277
|
+
});
|
|
217
278
|
worker.on('response', () => this.processQueue());
|
|
218
279
|
|
|
219
280
|
worker.on('error', err => this.onError(err, worker));
|
|
@@ -265,11 +326,24 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
265
326
|
}
|
|
266
327
|
|
|
267
328
|
if (worker.calls.size < this.options.maxConcurrentCallsPerWorker) {
|
|
268
|
-
|
|
329
|
+
this.callWorker(worker, this.callQueue.shift());
|
|
269
330
|
}
|
|
270
331
|
}
|
|
271
332
|
}
|
|
272
333
|
|
|
334
|
+
async callWorker(worker: Worker, call: WorkerCall): Promise<void> {
|
|
335
|
+
for (let ref of this.sharedReferences.keys()) {
|
|
336
|
+
if (!worker.sentSharedReferences.has(ref)) {
|
|
337
|
+
await worker.sendSharedReference(
|
|
338
|
+
ref,
|
|
339
|
+
this.getSerializedSharedReference(ref),
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
worker.call(call);
|
|
345
|
+
}
|
|
346
|
+
|
|
273
347
|
async processRequest(
|
|
274
348
|
data: {|
|
|
275
349
|
location: FilePath,
|
|
@@ -281,8 +355,17 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
281
355
|
if (handleId != null) {
|
|
282
356
|
mod = nullthrows(this.handles.get(handleId)?.fn);
|
|
283
357
|
} else if (location) {
|
|
284
|
-
// $FlowFixMe
|
|
285
|
-
|
|
358
|
+
// $FlowFixMe
|
|
359
|
+
if (process.browser) {
|
|
360
|
+
if (location === '@parcel/workers/src/bus.js') {
|
|
361
|
+
mod = (bus: any);
|
|
362
|
+
} else {
|
|
363
|
+
throw new Error('No dynamic require possible: ' + location);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
// $FlowFixMe this must be dynamic
|
|
367
|
+
mod = require(location);
|
|
368
|
+
}
|
|
286
369
|
} else {
|
|
287
370
|
throw new Error('Unknown request');
|
|
288
371
|
}
|
|
@@ -392,33 +475,31 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
392
475
|
return handle;
|
|
393
476
|
}
|
|
394
477
|
|
|
395
|
-
|
|
478
|
+
createSharedReference(
|
|
396
479
|
value: mixed,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
buffer?: Buffer,
|
|
400
|
-
): Promise<{|ref: SharedReference, dispose(): Promise<mixed>|}> {
|
|
480
|
+
isCacheable: boolean = true,
|
|
481
|
+
): {|ref: SharedReference, dispose(): Promise<mixed>|} {
|
|
401
482
|
let ref = referenceId++;
|
|
402
483
|
this.sharedReferences.set(ref, value);
|
|
403
484
|
this.sharedReferencesByValue.set(value, ref);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
let promises = [];
|
|
407
|
-
for (let worker of this.workers.values()) {
|
|
408
|
-
if (worker.ready) {
|
|
409
|
-
promises.push(worker.sendSharedReference(ref, toSend));
|
|
410
|
-
}
|
|
485
|
+
if (!isCacheable) {
|
|
486
|
+
this.serializedSharedReferences.set(ref, null);
|
|
411
487
|
}
|
|
412
488
|
|
|
413
|
-
await Promise.all(promises);
|
|
414
|
-
|
|
415
489
|
return {
|
|
416
490
|
ref,
|
|
417
491
|
dispose: () => {
|
|
418
492
|
this.sharedReferences.delete(ref);
|
|
419
493
|
this.sharedReferencesByValue.delete(value);
|
|
494
|
+
this.serializedSharedReferences.delete(ref);
|
|
495
|
+
|
|
420
496
|
let promises = [];
|
|
421
497
|
for (let worker of this.workers.values()) {
|
|
498
|
+
if (!worker.sentSharedReferences.has(ref)) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
worker.sentSharedReferences.delete(ref);
|
|
422
503
|
promises.push(
|
|
423
504
|
new Promise((resolve, reject) => {
|
|
424
505
|
worker.call({
|
|
@@ -437,6 +518,24 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
437
518
|
};
|
|
438
519
|
}
|
|
439
520
|
|
|
521
|
+
getSerializedSharedReference(ref: SharedReference): ArrayBuffer {
|
|
522
|
+
let cached = this.serializedSharedReferences.get(ref);
|
|
523
|
+
if (cached) {
|
|
524
|
+
return cached;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let value = this.sharedReferences.get(ref);
|
|
528
|
+
let buf = serialize(value).buffer;
|
|
529
|
+
|
|
530
|
+
// If the reference was created with the isCacheable option set to false,
|
|
531
|
+
// serializedSharedReferences will contain `null` as the value.
|
|
532
|
+
if (cached !== null) {
|
|
533
|
+
this.serializedSharedReferences.set(ref, buf);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return buf;
|
|
537
|
+
}
|
|
538
|
+
|
|
440
539
|
async startProfile() {
|
|
441
540
|
let promises = [];
|
|
442
541
|
for (let worker of this.workers.values()) {
|
|
@@ -454,7 +553,7 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
454
553
|
);
|
|
455
554
|
}
|
|
456
555
|
|
|
457
|
-
this.profiler = new
|
|
556
|
+
this.profiler = new SamplingProfiler();
|
|
458
557
|
|
|
459
558
|
promises.push(this.profiler.startProfiling());
|
|
460
559
|
await Promise.all(promises);
|
|
@@ -561,7 +660,7 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
561
660
|
static getNumWorkers(): number {
|
|
562
661
|
return process.env.PARCEL_WORKERS
|
|
563
662
|
? parseInt(process.env.PARCEL_WORKERS, 10)
|
|
564
|
-
: Math.ceil(cpuCount() / 2);
|
|
663
|
+
: Math.min(4, Math.ceil(cpuCount() / 2));
|
|
565
664
|
}
|
|
566
665
|
|
|
567
666
|
static isWorker(): boolean {
|
|
@@ -585,8 +684,12 @@ export default class WorkerFarm extends EventEmitter {
|
|
|
585
684
|
return child.workerApi;
|
|
586
685
|
}
|
|
587
686
|
|
|
588
|
-
static getConcurrentCallsPerWorker(
|
|
589
|
-
|
|
687
|
+
static getConcurrentCallsPerWorker(
|
|
688
|
+
defaultValue?: number = DEFAULT_MAX_CONCURRENT_CALLS,
|
|
689
|
+
): number {
|
|
690
|
+
return (
|
|
691
|
+
parseInt(process.env.PARCEL_MAX_CONCURRENT_CALLS, 10) || defaultValue
|
|
692
|
+
);
|
|
590
693
|
}
|
|
591
694
|
}
|
|
592
695
|
|
package/src/backend.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import type {BackendType, WorkerImpl} from './types';
|
|
3
3
|
|
|
4
4
|
export function detectBackend(): BackendType {
|
|
5
|
+
// $FlowFixMe
|
|
6
|
+
if (process.browser) return 'web';
|
|
7
|
+
|
|
5
8
|
switch (process.env.PARCEL_WORKER_BACKEND) {
|
|
6
9
|
case 'threads':
|
|
7
10
|
case 'process':
|
|
@@ -22,6 +25,8 @@ export function getWorkerBackend(backend: BackendType): Class<WorkerImpl> {
|
|
|
22
25
|
return require('./threads/ThreadsWorker').default;
|
|
23
26
|
case 'process':
|
|
24
27
|
return require('./process/ProcessWorker').default;
|
|
28
|
+
case 'web':
|
|
29
|
+
return require('./web/WebWorker').default;
|
|
25
30
|
default:
|
|
26
31
|
throw new Error(`Invalid backend: ${backend}`);
|
|
27
32
|
}
|
package/src/bus.js
CHANGED
package/src/child.js
CHANGED
|
@@ -10,15 +10,16 @@ import type {
|
|
|
10
10
|
ChildImpl,
|
|
11
11
|
} from './types';
|
|
12
12
|
import type {Async, IDisposable} from '@parcel/types';
|
|
13
|
-
import type {SharedReference
|
|
13
|
+
import type {SharedReference} from './WorkerFarm';
|
|
14
14
|
|
|
15
|
+
import * as coreWorker from './core-worker';
|
|
15
16
|
import invariant from 'assert';
|
|
16
17
|
import nullthrows from 'nullthrows';
|
|
17
18
|
import Logger, {patchConsole, unpatchConsole} from '@parcel/logger';
|
|
18
19
|
import ThrowableDiagnostic, {anyToDiagnostic} from '@parcel/diagnostic';
|
|
19
20
|
import {deserialize} from '@parcel/core';
|
|
20
21
|
import bus from './bus';
|
|
21
|
-
import
|
|
22
|
+
import {SamplingProfiler, tracer} from '@parcel/profiler';
|
|
22
23
|
import _Handle from './Handle';
|
|
23
24
|
|
|
24
25
|
// The import of './Handle' should really be imported eagerly (with @babel/plugin-transform-modules-commonjs's lazy mode).
|
|
@@ -37,17 +38,19 @@ export class Child {
|
|
|
37
38
|
responseId: number = 0;
|
|
38
39
|
responseQueue: Map<number, ChildCall> = new Map();
|
|
39
40
|
loggerDisposable: IDisposable;
|
|
41
|
+
tracerDisposable: IDisposable;
|
|
40
42
|
child: ChildImpl;
|
|
41
|
-
profiler: ?
|
|
42
|
-
workerApi: WorkerApi;
|
|
43
|
+
profiler: ?SamplingProfiler;
|
|
43
44
|
handles: Map<number, Handle> = new Map();
|
|
44
45
|
sharedReferences: Map<SharedReference, mixed> = new Map();
|
|
45
46
|
sharedReferencesByValue: Map<mixed, SharedReference> = new Map();
|
|
46
47
|
|
|
47
48
|
constructor(ChildBackend: Class<ChildImpl>) {
|
|
48
49
|
this.child = new ChildBackend(
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
m => {
|
|
51
|
+
this.messageListener(m);
|
|
52
|
+
},
|
|
53
|
+
() => this.handleEnd(),
|
|
51
54
|
);
|
|
52
55
|
|
|
53
56
|
// Monitior all logging events inside this child process and forward to
|
|
@@ -55,6 +58,10 @@ export class Child {
|
|
|
55
58
|
this.loggerDisposable = Logger.onLog(event => {
|
|
56
59
|
bus.emit('logEvent', event);
|
|
57
60
|
});
|
|
61
|
+
// .. and do the same for trace events
|
|
62
|
+
this.tracerDisposable = tracer.onTrace(event => {
|
|
63
|
+
bus.emit('traceEvent', event);
|
|
64
|
+
});
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
workerApi: {|
|
|
@@ -93,10 +100,23 @@ export class Child {
|
|
|
93
100
|
this.child.send(data);
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
childInit(module: string, childId: number): void {
|
|
97
|
-
// $FlowFixMe
|
|
98
|
-
|
|
103
|
+
async childInit(module: string, childId: number): Promise<void> {
|
|
104
|
+
// $FlowFixMe
|
|
105
|
+
if (process.browser) {
|
|
106
|
+
if (module === '@parcel/core/src/worker.js') {
|
|
107
|
+
this.module = coreWorker;
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error('No dynamic require possible: ' + module);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// $FlowFixMe this must be dynamic
|
|
113
|
+
this.module = require(module);
|
|
114
|
+
}
|
|
99
115
|
this.childId = childId;
|
|
116
|
+
|
|
117
|
+
if (this.module.childInit != null) {
|
|
118
|
+
await this.module.childInit();
|
|
119
|
+
}
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
async handleRequest(data: WorkerRequest): Promise<void> {
|
|
@@ -136,12 +156,16 @@ export class Child {
|
|
|
136
156
|
unpatchConsole();
|
|
137
157
|
}
|
|
138
158
|
|
|
139
|
-
|
|
159
|
+
if (childOptions.shouldTrace) {
|
|
160
|
+
tracer.enable();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
result = responseFromContent(await this.childInit(moduleName, child));
|
|
140
164
|
} catch (e) {
|
|
141
165
|
result = errorResponseFromError(e);
|
|
142
166
|
}
|
|
143
167
|
} else if (method === 'startProfile') {
|
|
144
|
-
this.profiler = new
|
|
168
|
+
this.profiler = new SamplingProfiler();
|
|
145
169
|
try {
|
|
146
170
|
result = responseFromContent(await this.profiler.startProfiling());
|
|
147
171
|
} catch (e) {
|
|
@@ -158,7 +182,6 @@ export class Child {
|
|
|
158
182
|
try {
|
|
159
183
|
let v8 = require('v8');
|
|
160
184
|
result = responseFromContent(
|
|
161
|
-
// $FlowFixMe
|
|
162
185
|
v8.writeHeapSnapshot(
|
|
163
186
|
'heap-' +
|
|
164
187
|
args[0] +
|
|
@@ -285,6 +308,7 @@ export class Child {
|
|
|
285
308
|
|
|
286
309
|
handleEnd(): void {
|
|
287
310
|
this.loggerDisposable.dispose();
|
|
311
|
+
this.tracerDisposable.dispose();
|
|
288
312
|
}
|
|
289
313
|
|
|
290
314
|
createReverseHandle(fn: (...args: Array<any>) => mixed): Handle {
|
package/src/cpuCount.js
CHANGED
|
@@ -47,14 +47,23 @@ export default function getCores(bypassCache?: boolean = false): number {
|
|
|
47
47
|
return cores;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
// $FlowFixMe
|
|
51
|
+
if (process.browser) {
|
|
52
|
+
// eslint-disable-next-line no-undef
|
|
53
|
+
cores = navigator.hardwareConcurrency / 2;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!cores) {
|
|
57
|
+
try {
|
|
58
|
+
cores = detectRealCores();
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// Guess the amount of real cores
|
|
61
|
+
cores = os
|
|
62
|
+
.cpus()
|
|
63
|
+
.filter(
|
|
64
|
+
(cpu, index) => !cpu.model.includes('Intel') || index % 2 === 1,
|
|
65
|
+
).length;
|
|
66
|
+
}
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
// Another fallback
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import type {LogEvent} from '@parcel/types';
|
|
2
|
+
import type {TraceEvent, LogEvent} from '@parcel/types';
|
|
3
3
|
import invariant from 'assert';
|
|
4
4
|
import WorkerFarm from './WorkerFarm';
|
|
5
5
|
import Logger from '@parcel/logger';
|
|
6
6
|
import bus from './bus';
|
|
7
|
+
import {tracer} from '@parcel/profiler';
|
|
7
8
|
|
|
8
9
|
if (!WorkerFarm.isWorker()) {
|
|
9
10
|
// Forward all logger events originating from workers into the main process
|
|
@@ -29,6 +30,11 @@ if (!WorkerFarm.isWorker()) {
|
|
|
29
30
|
throw new Error('Unknown log level');
|
|
30
31
|
}
|
|
31
32
|
});
|
|
33
|
+
|
|
34
|
+
// Forward all trace events originating from workers into the main process
|
|
35
|
+
bus.on('traceEvent', (e: TraceEvent) => {
|
|
36
|
+
tracer.trace(e);
|
|
37
|
+
});
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export default WorkerFarm;
|
|
@@ -37,6 +37,7 @@ export default class ProcessChild implements ChildImpl {
|
|
|
37
37
|
let processSend = nullthrows(process.send).bind(process);
|
|
38
38
|
processSend(serialize(data).toString('base64'), err => {
|
|
39
39
|
if (err && err instanceof Error) {
|
|
40
|
+
// $FlowFixMe[prop-missing]
|
|
40
41
|
if (err.code === 'ERR_IPC_CHANNEL_CLOSED') {
|
|
41
42
|
// IPC connection closed
|
|
42
43
|
// no need to keep the worker running if it can't send or receive data
|
package/src/types.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
/* eslint-env worker*/
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ChildImpl,
|
|
6
|
+
MessageHandler,
|
|
7
|
+
ExitHandler,
|
|
8
|
+
WorkerMessage,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import {setChild} from '../childState';
|
|
11
|
+
import {Child} from '../child';
|
|
12
|
+
import {prepareForSerialization, restoreDeserializedObject} from '@parcel/core';
|
|
13
|
+
|
|
14
|
+
export default class WebChild implements ChildImpl {
|
|
15
|
+
onMessage: MessageHandler;
|
|
16
|
+
onExit: ExitHandler;
|
|
17
|
+
|
|
18
|
+
constructor(onMessage: MessageHandler, onExit: ExitHandler) {
|
|
19
|
+
if (
|
|
20
|
+
!(
|
|
21
|
+
typeof WorkerGlobalScope !== 'undefined' &&
|
|
22
|
+
self instanceof WorkerGlobalScope
|
|
23
|
+
)
|
|
24
|
+
) {
|
|
25
|
+
throw new Error('Only create WebChild instances in a worker!');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.onMessage = onMessage;
|
|
29
|
+
this.onExit = onExit;
|
|
30
|
+
self.addEventListener('message', ({data}: MessageEvent) => {
|
|
31
|
+
if (data === 'stop') {
|
|
32
|
+
this.onExit(0);
|
|
33
|
+
self.postMessage('stopped');
|
|
34
|
+
}
|
|
35
|
+
// $FlowFixMe assume WorkerMessage as data
|
|
36
|
+
this.handleMessage(data);
|
|
37
|
+
});
|
|
38
|
+
self.postMessage('online');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handleMessage(data: WorkerMessage) {
|
|
42
|
+
this.onMessage(restoreDeserializedObject(data));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
send(data: WorkerMessage) {
|
|
46
|
+
self.postMessage(prepareForSerialization(data));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setChild(new Child(WebChild));
|