@atlaspack/workers 2.12.1-canary.3354

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.
Files changed (52) hide show
  1. package/LICENSE +201 -0
  2. package/index.d.ts +23 -0
  3. package/lib/Handle.js +45 -0
  4. package/lib/Worker.js +188 -0
  5. package/lib/WorkerFarm.js +563 -0
  6. package/lib/backend.js +34 -0
  7. package/lib/bus.js +31 -0
  8. package/lib/child.js +294 -0
  9. package/lib/childState.js +14 -0
  10. package/lib/core-worker.browser.js +4 -0
  11. package/lib/core-worker.js +4 -0
  12. package/lib/cpuCount.js +79 -0
  13. package/lib/index.js +75 -0
  14. package/lib/process/ProcessChild.js +58 -0
  15. package/lib/process/ProcessWorker.js +83 -0
  16. package/lib/threads/ThreadsChild.js +49 -0
  17. package/lib/threads/ThreadsWorker.js +61 -0
  18. package/lib/types.js +1 -0
  19. package/lib/web/WebChild.js +44 -0
  20. package/lib/web/WebWorker.js +85 -0
  21. package/package.json +36 -0
  22. package/src/Handle.js +48 -0
  23. package/src/Worker.js +227 -0
  24. package/src/WorkerFarm.js +707 -0
  25. package/src/backend.js +33 -0
  26. package/src/bus.js +26 -0
  27. package/src/child.js +322 -0
  28. package/src/childState.js +10 -0
  29. package/src/core-worker.browser.js +3 -0
  30. package/src/core-worker.js +2 -0
  31. package/src/cpuCount.js +75 -0
  32. package/src/index.js +43 -0
  33. package/src/process/ProcessChild.js +56 -0
  34. package/src/process/ProcessWorker.js +91 -0
  35. package/src/threads/ThreadsChild.js +42 -0
  36. package/src/threads/ThreadsWorker.js +66 -0
  37. package/src/types.js +68 -0
  38. package/src/web/WebChild.js +53 -0
  39. package/src/web/WebWorker.js +88 -0
  40. package/test/cpuCount.test.js +19 -0
  41. package/test/integration/workerfarm/console.js +15 -0
  42. package/test/integration/workerfarm/echo.js +5 -0
  43. package/test/integration/workerfarm/ipc-pid.js +18 -0
  44. package/test/integration/workerfarm/ipc.js +10 -0
  45. package/test/integration/workerfarm/logging.js +19 -0
  46. package/test/integration/workerfarm/master-process-id.js +3 -0
  47. package/test/integration/workerfarm/master-sum.js +3 -0
  48. package/test/integration/workerfarm/ping.js +5 -0
  49. package/test/integration/workerfarm/resolve-shared-reference.js +5 -0
  50. package/test/integration/workerfarm/reverse-handle.js +5 -0
  51. package/test/integration/workerfarm/shared-reference.js +6 -0
  52. package/test/workerfarm.js +362 -0
@@ -0,0 +1,707 @@
1
+ // @flow
2
+
3
+ import type {ErrorWithCode, FilePath} from '@atlaspack/types-internal';
4
+ import type {
5
+ CallRequest,
6
+ HandleCallRequest,
7
+ WorkerRequest,
8
+ WorkerDataResponse,
9
+ WorkerErrorResponse,
10
+ BackendType,
11
+ } from './types';
12
+ import type {HandleFunction} from './Handle';
13
+
14
+ import * as coreWorker from './core-worker';
15
+ import * as bus from './bus';
16
+ import invariant from 'assert';
17
+ import nullthrows from 'nullthrows';
18
+ import EventEmitter from 'events';
19
+ import {
20
+ deserialize,
21
+ prepareForSerialization,
22
+ restoreDeserializedObject,
23
+ serialize,
24
+ } from '@atlaspack/core';
25
+ import ThrowableDiagnostic, {anyToDiagnostic, md} from '@atlaspack/diagnostic';
26
+ import Worker, {type WorkerCall} from './Worker';
27
+ import cpuCount from './cpuCount';
28
+ import Handle from './Handle';
29
+ import {child} from './childState';
30
+ import {detectBackend} from './backend';
31
+ import {SamplingProfiler, Trace} from '@atlaspack/profiler';
32
+ import fs from 'fs';
33
+ import logger from '@atlaspack/logger';
34
+
35
+ let referenceId = 1;
36
+
37
+ export opaque type SharedReference = number;
38
+
39
+ export type FarmOptions = {|
40
+ maxConcurrentWorkers: number,
41
+ maxConcurrentCallsPerWorker: number,
42
+ forcedKillTime: number,
43
+ useLocalWorker: boolean,
44
+ warmWorkers: boolean,
45
+ workerPath?: FilePath,
46
+ backend: BackendType,
47
+ shouldPatchConsole?: boolean,
48
+ shouldTrace?: boolean,
49
+ |};
50
+
51
+ type WorkerModule = {
52
+ +[string]: (...args: Array<mixed>) => Promise<mixed>,
53
+ ...
54
+ };
55
+
56
+ export type WorkerApi = {|
57
+ callMaster(CallRequest, ?boolean): Promise<mixed>,
58
+ createReverseHandle(fn: HandleFunction): Handle,
59
+ getSharedReference(ref: SharedReference): mixed,
60
+ resolveSharedReference(value: mixed): ?SharedReference,
61
+ callChild?: (childId: number, request: HandleCallRequest) => Promise<mixed>,
62
+ |};
63
+
64
+ export {Handle};
65
+
66
+ const DEFAULT_MAX_CONCURRENT_CALLS: number = 30;
67
+
68
+ /**
69
+ * workerPath should always be defined inside farmOptions
70
+ */
71
+
72
+ export default class WorkerFarm extends EventEmitter {
73
+ callQueue: Array<WorkerCall> = [];
74
+ ending: boolean = false;
75
+ localWorker: WorkerModule;
76
+ localWorkerInit: ?Promise<void>;
77
+ options: FarmOptions;
78
+ run: HandleFunction;
79
+ warmWorkers: number = 0;
80
+ readyWorkers: number = 0;
81
+ workers: Map<number, Worker> = new Map();
82
+ handles: Map<number, Handle> = new Map();
83
+ sharedReferences: Map<SharedReference, mixed> = new Map();
84
+ sharedReferencesByValue: Map<mixed, SharedReference> = new Map();
85
+ serializedSharedReferences: Map<SharedReference, ?ArrayBuffer> = new Map();
86
+ profiler: ?SamplingProfiler;
87
+
88
+ constructor(farmOptions: $Shape<FarmOptions> = {}) {
89
+ super();
90
+ this.options = {
91
+ maxConcurrentWorkers: WorkerFarm.getNumWorkers(),
92
+ maxConcurrentCallsPerWorker: WorkerFarm.getConcurrentCallsPerWorker(
93
+ farmOptions.shouldTrace ? 1 : DEFAULT_MAX_CONCURRENT_CALLS,
94
+ ),
95
+ forcedKillTime: 500,
96
+ warmWorkers: false,
97
+ useLocalWorker: true, // TODO: setting this to false makes some tests fail, figure out why
98
+ backend: detectBackend(),
99
+ ...farmOptions,
100
+ };
101
+
102
+ if (!this.options.workerPath) {
103
+ throw new Error('Please provide a worker path!');
104
+ }
105
+
106
+ // $FlowFixMe
107
+ if (process.browser) {
108
+ if (this.options.workerPath === '@atlaspack/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
+
123
+ this.run = this.createHandle('run');
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
+
140
+ this.startMaxWorkers();
141
+ }
142
+
143
+ workerApi: {|
144
+ callChild: (childId: number, request: HandleCallRequest) => Promise<mixed>,
145
+ callMaster: (
146
+ request: CallRequest,
147
+ awaitResponse?: ?boolean,
148
+ ) => Promise<mixed>,
149
+ createReverseHandle: (fn: HandleFunction) => Handle,
150
+ getSharedReference: (ref: SharedReference) => mixed,
151
+ resolveSharedReference: (value: mixed) => void | SharedReference,
152
+ runHandle: (handle: Handle, args: Array<any>) => Promise<mixed>,
153
+ |} = {
154
+ callMaster: async (
155
+ request: CallRequest,
156
+ awaitResponse: ?boolean = true,
157
+ ): Promise<mixed> => {
158
+ // $FlowFixMe
159
+ let result = await this.processRequest({
160
+ ...request,
161
+ awaitResponse,
162
+ });
163
+ return deserialize(serialize(result));
164
+ },
165
+ createReverseHandle: (fn: HandleFunction): Handle =>
166
+ this.createReverseHandle(fn),
167
+ callChild: (childId: number, request: HandleCallRequest): Promise<mixed> =>
168
+ new Promise((resolve, reject) => {
169
+ nullthrows(this.workers.get(childId)).call({
170
+ ...request,
171
+ resolve,
172
+ reject,
173
+ retries: 0,
174
+ });
175
+ }),
176
+ runHandle: (handle: Handle, args: Array<any>): Promise<mixed> =>
177
+ this.workerApi.callChild(nullthrows(handle.childId), {
178
+ handle: handle.id,
179
+ args,
180
+ }),
181
+ getSharedReference: (ref: SharedReference) =>
182
+ this.sharedReferences.get(ref),
183
+ resolveSharedReference: (value: mixed) =>
184
+ this.sharedReferencesByValue.get(value),
185
+ };
186
+
187
+ warmupWorker(method: string, args: Array<any>): void {
188
+ // Workers are already stopping
189
+ if (this.ending) {
190
+ return;
191
+ }
192
+
193
+ // Workers are not warmed up yet.
194
+ // Send the job to a remote worker in the background,
195
+ // but use the result from the local worker - it will be faster.
196
+ let promise = this.addCall(method, [...args, true]);
197
+ if (promise) {
198
+ promise
199
+ .then(() => {
200
+ this.warmWorkers++;
201
+ if (this.warmWorkers >= this.workers.size) {
202
+ this.emit('warmedup');
203
+ }
204
+ })
205
+ .catch(() => {});
206
+ }
207
+ }
208
+
209
+ shouldStartRemoteWorkers(): boolean {
210
+ return (
211
+ this.options.maxConcurrentWorkers > 0 || !this.options.useLocalWorker
212
+ );
213
+ }
214
+
215
+ createHandle(method: string, useMainThread: boolean = false): HandleFunction {
216
+ if (!this.options.useLocalWorker) {
217
+ useMainThread = false;
218
+ }
219
+
220
+ return async (...args) => {
221
+ // Child process workers are slow to start (~600ms).
222
+ // While we're waiting, just run on the main thread.
223
+ // This significantly speeds up startup time.
224
+ if (this.shouldUseRemoteWorkers() && !useMainThread) {
225
+ return this.addCall(method, [...args, false]);
226
+ } else {
227
+ if (this.options.warmWorkers && this.shouldStartRemoteWorkers()) {
228
+ this.warmupWorker(method, args);
229
+ }
230
+
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
+ }
244
+ return this.localWorker[method](this.workerApi, ...processedArgs);
245
+ }
246
+ };
247
+ }
248
+
249
+ onError(error: ErrorWithCode, worker: Worker): void | Promise<void> {
250
+ // Handle ipc errors
251
+ if (error.code === 'ERR_IPC_CHANNEL_CLOSED') {
252
+ return this.stopWorker(worker);
253
+ } else {
254
+ logger.error(error, '@atlaspack/workers');
255
+ }
256
+ }
257
+
258
+ startChild() {
259
+ let worker = new Worker({
260
+ forcedKillTime: this.options.forcedKillTime,
261
+ backend: this.options.backend,
262
+ shouldPatchConsole: this.options.shouldPatchConsole,
263
+ shouldTrace: this.options.shouldTrace,
264
+ sharedReferences: this.sharedReferences,
265
+ });
266
+
267
+ worker.fork(nullthrows(this.options.workerPath));
268
+
269
+ worker.on('request', data => this.processRequest(data, worker));
270
+
271
+ worker.on('ready', () => {
272
+ this.readyWorkers++;
273
+ if (this.readyWorkers === this.options.maxConcurrentWorkers) {
274
+ this.emit('ready');
275
+ }
276
+ this.processQueue();
277
+ });
278
+ worker.on('response', () => this.processQueue());
279
+
280
+ worker.on('error', err => this.onError(err, worker));
281
+ worker.once('exit', () => this.stopWorker(worker));
282
+
283
+ this.workers.set(worker.id, worker);
284
+ }
285
+
286
+ async stopWorker(worker: Worker): Promise<void> {
287
+ if (!worker.stopped) {
288
+ this.workers.delete(worker.id);
289
+
290
+ worker.isStopping = true;
291
+
292
+ if (worker.calls.size) {
293
+ for (let call of worker.calls.values()) {
294
+ call.retries++;
295
+ this.callQueue.unshift(call);
296
+ }
297
+ }
298
+
299
+ worker.calls.clear();
300
+
301
+ await worker.stop();
302
+
303
+ // Process any requests that failed and start a new worker
304
+ this.processQueue();
305
+ }
306
+ }
307
+
308
+ processQueue(): void {
309
+ if (this.ending || !this.callQueue.length) return;
310
+
311
+ if (this.workers.size < this.options.maxConcurrentWorkers) {
312
+ this.startChild();
313
+ }
314
+
315
+ let workers = [...this.workers.values()].sort(
316
+ (a, b) => a.calls.size - b.calls.size,
317
+ );
318
+
319
+ for (let worker of workers) {
320
+ if (!this.callQueue.length) {
321
+ break;
322
+ }
323
+
324
+ if (!worker.ready || worker.stopped || worker.isStopping) {
325
+ continue;
326
+ }
327
+
328
+ if (worker.calls.size < this.options.maxConcurrentCallsPerWorker) {
329
+ this.callWorker(worker, this.callQueue.shift());
330
+ }
331
+ }
332
+ }
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
+
347
+ async processRequest(
348
+ data: {|
349
+ location: FilePath,
350
+ |} & $Shape<WorkerRequest>,
351
+ worker?: Worker,
352
+ ): Promise<?string> {
353
+ let {method, args, location, awaitResponse, idx, handle: handleId} = data;
354
+ let mod;
355
+ if (handleId != null) {
356
+ mod = nullthrows(this.handles.get(handleId)?.fn);
357
+ } else if (location) {
358
+ // $FlowFixMe
359
+ if (process.browser) {
360
+ if (location === '@atlaspack/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
+ }
369
+ } else {
370
+ throw new Error('Unknown request');
371
+ }
372
+
373
+ const responseFromContent = (content: any): WorkerDataResponse => ({
374
+ idx,
375
+ type: 'response',
376
+ contentType: 'data',
377
+ content,
378
+ });
379
+
380
+ const errorResponseFromError = (e: Error): WorkerErrorResponse => ({
381
+ idx,
382
+ type: 'response',
383
+ contentType: 'error',
384
+ content: anyToDiagnostic(e),
385
+ });
386
+
387
+ let result;
388
+ if (method == null) {
389
+ try {
390
+ result = responseFromContent(await mod(...args));
391
+ } catch (e) {
392
+ result = errorResponseFromError(e);
393
+ }
394
+ } else {
395
+ // ESModule default interop
396
+ if (mod.__esModule && !mod[method] && mod.default) {
397
+ mod = mod.default;
398
+ }
399
+
400
+ try {
401
+ // $FlowFixMe
402
+ result = responseFromContent(await mod[method](...args));
403
+ } catch (e) {
404
+ result = errorResponseFromError(e);
405
+ }
406
+ }
407
+
408
+ if (awaitResponse) {
409
+ if (worker) {
410
+ worker.send(result);
411
+ } else {
412
+ if (result.contentType === 'error') {
413
+ throw new ThrowableDiagnostic({diagnostic: result.content});
414
+ }
415
+ return result.content;
416
+ }
417
+ }
418
+ }
419
+
420
+ addCall(method: string, args: Array<any>): Promise<any> {
421
+ if (this.ending) {
422
+ throw new Error('Cannot add a worker call if workerfarm is ending.');
423
+ }
424
+
425
+ return new Promise((resolve, reject) => {
426
+ this.callQueue.push({
427
+ method,
428
+ args: args,
429
+ retries: 0,
430
+ resolve,
431
+ reject,
432
+ });
433
+ this.processQueue();
434
+ });
435
+ }
436
+
437
+ async end(): Promise<void> {
438
+ this.ending = true;
439
+
440
+ await Promise.all(
441
+ Array.from(this.workers.values()).map(worker => this.stopWorker(worker)),
442
+ );
443
+
444
+ for (let handle of this.handles.values()) {
445
+ handle.dispose();
446
+ }
447
+ this.handles = new Map();
448
+ this.sharedReferences = new Map();
449
+ this.sharedReferencesByValue = new Map();
450
+
451
+ this.ending = false;
452
+ }
453
+
454
+ startMaxWorkers(): void {
455
+ // Starts workers until the maximum is reached
456
+ if (this.workers.size < this.options.maxConcurrentWorkers) {
457
+ let toStart = this.options.maxConcurrentWorkers - this.workers.size;
458
+ while (toStart--) {
459
+ this.startChild();
460
+ }
461
+ }
462
+ }
463
+
464
+ shouldUseRemoteWorkers(): boolean {
465
+ return (
466
+ !this.options.useLocalWorker ||
467
+ ((this.warmWorkers >= this.workers.size || !this.options.warmWorkers) &&
468
+ this.options.maxConcurrentWorkers > 0)
469
+ );
470
+ }
471
+
472
+ createReverseHandle(fn: HandleFunction): Handle {
473
+ let handle = new Handle({fn});
474
+ this.handles.set(handle.id, handle);
475
+ return handle;
476
+ }
477
+
478
+ createSharedReference(
479
+ value: mixed,
480
+ isCacheable: boolean = true,
481
+ ): {|ref: SharedReference, dispose(): Promise<mixed>|} {
482
+ let ref = referenceId++;
483
+ this.sharedReferences.set(ref, value);
484
+ this.sharedReferencesByValue.set(value, ref);
485
+ if (!isCacheable) {
486
+ this.serializedSharedReferences.set(ref, null);
487
+ }
488
+
489
+ return {
490
+ ref,
491
+ dispose: () => {
492
+ this.sharedReferences.delete(ref);
493
+ this.sharedReferencesByValue.delete(value);
494
+ this.serializedSharedReferences.delete(ref);
495
+
496
+ let promises = [];
497
+ for (let worker of this.workers.values()) {
498
+ if (!worker.sentSharedReferences.has(ref)) {
499
+ continue;
500
+ }
501
+
502
+ worker.sentSharedReferences.delete(ref);
503
+ promises.push(
504
+ new Promise((resolve, reject) => {
505
+ worker.call({
506
+ method: 'deleteSharedReference',
507
+ args: [ref],
508
+ resolve,
509
+ reject,
510
+ skipReadyCheck: true,
511
+ retries: 0,
512
+ });
513
+ }),
514
+ );
515
+ }
516
+ return Promise.all(promises);
517
+ },
518
+ };
519
+ }
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
+
539
+ async startProfile() {
540
+ let promises = [];
541
+ for (let worker of this.workers.values()) {
542
+ promises.push(
543
+ new Promise((resolve, reject) => {
544
+ worker.call({
545
+ method: 'startProfile',
546
+ args: [],
547
+ resolve,
548
+ reject,
549
+ retries: 0,
550
+ skipReadyCheck: true,
551
+ });
552
+ }),
553
+ );
554
+ }
555
+
556
+ this.profiler = new SamplingProfiler();
557
+
558
+ promises.push(this.profiler.startProfiling());
559
+ await Promise.all(promises);
560
+ }
561
+
562
+ async endProfile() {
563
+ if (!this.profiler) {
564
+ return;
565
+ }
566
+
567
+ let promises = [this.profiler.stopProfiling()];
568
+ let names = ['Master'];
569
+
570
+ for (let worker of this.workers.values()) {
571
+ names.push('Worker ' + worker.id);
572
+ promises.push(
573
+ new Promise((resolve, reject) => {
574
+ worker.call({
575
+ method: 'endProfile',
576
+ args: [],
577
+ resolve,
578
+ reject,
579
+ retries: 0,
580
+ skipReadyCheck: true,
581
+ });
582
+ }),
583
+ );
584
+ }
585
+
586
+ var profiles = await Promise.all(promises);
587
+ let trace = new Trace();
588
+ let filename = `profile-${getTimeId()}.trace`;
589
+ let stream = trace.pipe(fs.createWriteStream(filename));
590
+
591
+ for (let profile of profiles) {
592
+ trace.addCPUProfile(names.shift(), profile);
593
+ }
594
+
595
+ trace.flush();
596
+ await new Promise(resolve => {
597
+ stream.once('finish', resolve);
598
+ });
599
+
600
+ logger.info({
601
+ origin: '@atlaspack/workers',
602
+ message: md`Wrote profile to ${filename}`,
603
+ });
604
+ }
605
+
606
+ async callAllWorkers(method: string, args: Array<any>) {
607
+ let promises = [];
608
+ for (let worker of this.workers.values()) {
609
+ promises.push(
610
+ new Promise((resolve, reject) => {
611
+ worker.call({
612
+ method,
613
+ args,
614
+ resolve,
615
+ reject,
616
+ retries: 0,
617
+ });
618
+ }),
619
+ );
620
+ }
621
+
622
+ promises.push(this.localWorker[method](this.workerApi, ...args));
623
+ await Promise.all(promises);
624
+ }
625
+
626
+ async takeHeapSnapshot() {
627
+ let snapshotId = getTimeId();
628
+
629
+ try {
630
+ let snapshotPaths = await Promise.all(
631
+ [...this.workers.values()].map(
632
+ worker =>
633
+ new Promise((resolve, reject) => {
634
+ worker.call({
635
+ method: 'takeHeapSnapshot',
636
+ args: [snapshotId],
637
+ resolve,
638
+ reject,
639
+ retries: 0,
640
+ skipReadyCheck: true,
641
+ });
642
+ }),
643
+ ),
644
+ );
645
+
646
+ logger.info({
647
+ origin: '@atlaspack/workers',
648
+ message: md`Wrote heap snapshots to the following paths:\n${snapshotPaths.join(
649
+ '\n',
650
+ )}`,
651
+ });
652
+ } catch {
653
+ logger.error({
654
+ origin: '@atlaspack/workers',
655
+ message: 'Unable to take heap snapshots. Note: requires Node 11.13.0+',
656
+ });
657
+ }
658
+ }
659
+
660
+ static getNumWorkers(): number {
661
+ return process.env.ATLASPACK_WORKERS
662
+ ? parseInt(process.env.ATLASPACK_WORKERS, 10)
663
+ : Math.min(4, Math.ceil(cpuCount() / 2));
664
+ }
665
+
666
+ static isWorker(): boolean {
667
+ return !!child;
668
+ }
669
+
670
+ static getWorkerApi(): {|
671
+ callMaster: (
672
+ request: CallRequest,
673
+ awaitResponse?: ?boolean,
674
+ ) => Promise<mixed>,
675
+ createReverseHandle: (fn: (...args: Array<any>) => mixed) => Handle,
676
+ getSharedReference: (ref: SharedReference) => mixed,
677
+ resolveSharedReference: (value: mixed) => void | SharedReference,
678
+ runHandle: (handle: Handle, args: Array<any>) => Promise<mixed>,
679
+ |} {
680
+ invariant(
681
+ child != null,
682
+ 'WorkerFarm.getWorkerApi can only be called within workers',
683
+ );
684
+ return child.workerApi;
685
+ }
686
+
687
+ static getConcurrentCallsPerWorker(
688
+ defaultValue?: number = DEFAULT_MAX_CONCURRENT_CALLS,
689
+ ): number {
690
+ return (
691
+ parseInt(process.env.ATLASPACK_MAX_CONCURRENT_CALLS, 10) || defaultValue
692
+ );
693
+ }
694
+ }
695
+
696
+ function getTimeId() {
697
+ let now = new Date();
698
+ return (
699
+ String(now.getFullYear()) +
700
+ String(now.getMonth() + 1).padStart(2, '0') +
701
+ String(now.getDate()).padStart(2, '0') +
702
+ '-' +
703
+ String(now.getHours()).padStart(2, '0') +
704
+ String(now.getMinutes()).padStart(2, '0') +
705
+ String(now.getSeconds()).padStart(2, '0')
706
+ );
707
+ }