@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
package/src/backend.js ADDED
@@ -0,0 +1,33 @@
1
+ // @flow
2
+ import type {BackendType, WorkerImpl} from './types';
3
+
4
+ export function detectBackend(): BackendType {
5
+ // $FlowFixMe
6
+ if (process.browser) return 'web';
7
+
8
+ switch (process.env.ATLASPACK_WORKER_BACKEND) {
9
+ case 'threads':
10
+ case 'process':
11
+ return process.env.ATLASPACK_WORKER_BACKEND;
12
+ }
13
+
14
+ try {
15
+ require('worker_threads');
16
+ return 'threads';
17
+ } catch (err) {
18
+ return 'process';
19
+ }
20
+ }
21
+
22
+ export function getWorkerBackend(backend: BackendType): Class<WorkerImpl> {
23
+ switch (backend) {
24
+ case 'threads':
25
+ return require('./threads/ThreadsWorker').default;
26
+ case 'process':
27
+ return require('./process/ProcessWorker').default;
28
+ case 'web':
29
+ return require('./web/WebWorker').default;
30
+ default:
31
+ throw new Error(`Invalid backend: ${backend}`);
32
+ }
33
+ }
package/src/bus.js ADDED
@@ -0,0 +1,26 @@
1
+ // @flow
2
+ import EventEmitter from 'events';
3
+ import {child} from './childState';
4
+
5
+ class Bus extends EventEmitter {
6
+ emit(event: string, ...args: Array<any>): boolean {
7
+ if (child) {
8
+ child.workerApi.callMaster(
9
+ {
10
+ // $FlowFixMe
11
+ location: process.browser
12
+ ? '@atlaspack/workers/src/bus.js'
13
+ : __filename,
14
+ method: 'emit',
15
+ args: [event, ...args],
16
+ },
17
+ false,
18
+ );
19
+ return true;
20
+ } else {
21
+ return super.emit(event, ...args);
22
+ }
23
+ }
24
+ }
25
+
26
+ export default (new Bus(): Bus);
package/src/child.js ADDED
@@ -0,0 +1,322 @@
1
+ // @flow
2
+
3
+ import type {
4
+ CallRequest,
5
+ WorkerDataResponse,
6
+ WorkerErrorResponse,
7
+ WorkerMessage,
8
+ WorkerRequest,
9
+ WorkerResponse,
10
+ ChildImpl,
11
+ } from './types';
12
+ import type {Async, IDisposable} from '@atlaspack/types-internal';
13
+ import type {SharedReference} from './WorkerFarm';
14
+
15
+ import * as coreWorker from './core-worker';
16
+ import invariant from 'assert';
17
+ import nullthrows from 'nullthrows';
18
+ import Logger, {patchConsole, unpatchConsole} from '@atlaspack/logger';
19
+ import ThrowableDiagnostic, {anyToDiagnostic} from '@atlaspack/diagnostic';
20
+ import {deserialize} from '@atlaspack/core';
21
+ import bus from './bus';
22
+ import {SamplingProfiler, tracer} from '@atlaspack/profiler';
23
+ import _Handle from './Handle';
24
+
25
+ // The import of './Handle' should really be imported eagerly (with @babel/plugin-transform-modules-commonjs's lazy mode).
26
+ const Handle = _Handle;
27
+
28
+ type ChildCall = WorkerRequest & {|
29
+ resolve: (result: Promise<any> | any) => void,
30
+ reject: (error: any) => void,
31
+ |};
32
+
33
+ export class Child {
34
+ callQueue: Array<ChildCall> = [];
35
+ childId: ?number;
36
+ maxConcurrentCalls: number = 10;
37
+ module: ?any;
38
+ responseId: number = 0;
39
+ responseQueue: Map<number, ChildCall> = new Map();
40
+ loggerDisposable: IDisposable;
41
+ tracerDisposable: IDisposable;
42
+ child: ChildImpl;
43
+ profiler: ?SamplingProfiler;
44
+ handles: Map<number, Handle> = new Map();
45
+ sharedReferences: Map<SharedReference, mixed> = new Map();
46
+ sharedReferencesByValue: Map<mixed, SharedReference> = new Map();
47
+
48
+ constructor(ChildBackend: Class<ChildImpl>) {
49
+ this.child = new ChildBackend(
50
+ m => {
51
+ this.messageListener(m);
52
+ },
53
+ () => this.handleEnd(),
54
+ );
55
+
56
+ // Monitior all logging events inside this child process and forward to
57
+ // the main process via the bus.
58
+ this.loggerDisposable = Logger.onLog(event => {
59
+ bus.emit('logEvent', event);
60
+ });
61
+ // .. and do the same for trace events
62
+ this.tracerDisposable = tracer.onTrace(event => {
63
+ bus.emit('traceEvent', event);
64
+ });
65
+ }
66
+
67
+ workerApi: {|
68
+ callMaster: (
69
+ request: CallRequest,
70
+ awaitResponse?: ?boolean,
71
+ ) => Promise<mixed>,
72
+ createReverseHandle: (fn: (...args: Array<any>) => mixed) => Handle,
73
+ getSharedReference: (ref: SharedReference) => mixed,
74
+ resolveSharedReference: (value: mixed) => void | SharedReference,
75
+ runHandle: (handle: Handle, args: Array<any>) => Promise<mixed>,
76
+ |} = {
77
+ callMaster: (
78
+ request: CallRequest,
79
+ awaitResponse: ?boolean = true,
80
+ ): Promise<mixed> => this.addCall(request, awaitResponse),
81
+ createReverseHandle: (fn: (...args: Array<any>) => mixed): Handle =>
82
+ this.createReverseHandle(fn),
83
+ runHandle: (handle: Handle, args: Array<any>): Promise<mixed> =>
84
+ this.workerApi.callMaster({handle: handle.id, args}, true),
85
+ getSharedReference: (ref: SharedReference) =>
86
+ this.sharedReferences.get(ref),
87
+ resolveSharedReference: (value: mixed) =>
88
+ this.sharedReferencesByValue.get(value),
89
+ };
90
+
91
+ messageListener(message: WorkerMessage): Async<void> {
92
+ if (message.type === 'response') {
93
+ return this.handleResponse(message);
94
+ } else if (message.type === 'request') {
95
+ return this.handleRequest(message);
96
+ }
97
+ }
98
+
99
+ send(data: WorkerMessage): void {
100
+ this.child.send(data);
101
+ }
102
+
103
+ async childInit(module: string, childId: number): Promise<void> {
104
+ // $FlowFixMe
105
+ if (process.browser) {
106
+ if (module === '@atlaspack/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
+ }
115
+ this.childId = childId;
116
+
117
+ if (this.module.childInit != null) {
118
+ await this.module.childInit();
119
+ }
120
+ }
121
+
122
+ async handleRequest(data: WorkerRequest): Promise<void> {
123
+ let {idx, method, args, handle: handleId} = data;
124
+ let child = nullthrows(data.child);
125
+
126
+ const responseFromContent = (content: any): WorkerDataResponse => ({
127
+ idx,
128
+ child,
129
+ type: 'response',
130
+ contentType: 'data',
131
+ content,
132
+ });
133
+
134
+ const errorResponseFromError = (e: Error): WorkerErrorResponse => ({
135
+ idx,
136
+ child,
137
+ type: 'response',
138
+ contentType: 'error',
139
+ content: anyToDiagnostic(e),
140
+ });
141
+
142
+ let result;
143
+ if (handleId != null) {
144
+ try {
145
+ let fn = nullthrows(this.handles.get(handleId)?.fn);
146
+ result = responseFromContent(fn(...args));
147
+ } catch (e) {
148
+ result = errorResponseFromError(e);
149
+ }
150
+ } else if (method === 'childInit') {
151
+ try {
152
+ let [moduleName, childOptions] = args;
153
+ if (childOptions.shouldPatchConsole) {
154
+ patchConsole();
155
+ } else {
156
+ unpatchConsole();
157
+ }
158
+
159
+ if (childOptions.shouldTrace) {
160
+ tracer.enable();
161
+ }
162
+
163
+ result = responseFromContent(await this.childInit(moduleName, child));
164
+ } catch (e) {
165
+ result = errorResponseFromError(e);
166
+ }
167
+ } else if (method === 'startProfile') {
168
+ this.profiler = new SamplingProfiler();
169
+ try {
170
+ result = responseFromContent(await this.profiler.startProfiling());
171
+ } catch (e) {
172
+ result = errorResponseFromError(e);
173
+ }
174
+ } else if (method === 'endProfile') {
175
+ try {
176
+ let res = this.profiler ? await this.profiler.stopProfiling() : null;
177
+ result = responseFromContent(res);
178
+ } catch (e) {
179
+ result = errorResponseFromError(e);
180
+ }
181
+ } else if (method === 'takeHeapSnapshot') {
182
+ try {
183
+ let v8 = require('v8');
184
+ result = responseFromContent(
185
+ v8.writeHeapSnapshot(
186
+ 'heap-' +
187
+ args[0] +
188
+ '-' +
189
+ (this.childId ? 'worker' + this.childId : 'main') +
190
+ '.heapsnapshot',
191
+ ),
192
+ );
193
+ } catch (e) {
194
+ result = errorResponseFromError(e);
195
+ }
196
+ } else if (method === 'createSharedReference') {
197
+ let [ref, _value] = args;
198
+ let value =
199
+ _value instanceof ArrayBuffer
200
+ ? // In the case the value is pre-serialized as a buffer,
201
+ // deserialize it.
202
+ deserialize(Buffer.from(_value))
203
+ : _value;
204
+ this.sharedReferences.set(ref, value);
205
+ this.sharedReferencesByValue.set(value, ref);
206
+ result = responseFromContent(null);
207
+ } else if (method === 'deleteSharedReference') {
208
+ let ref = args[0];
209
+ let value = this.sharedReferences.get(ref);
210
+ this.sharedReferencesByValue.delete(value);
211
+ this.sharedReferences.delete(ref);
212
+ result = responseFromContent(null);
213
+ } else {
214
+ try {
215
+ result = responseFromContent(
216
+ // $FlowFixMe
217
+ await this.module[method](this.workerApi, ...args),
218
+ );
219
+ } catch (e) {
220
+ result = errorResponseFromError(e);
221
+ }
222
+ }
223
+
224
+ try {
225
+ this.send(result);
226
+ } catch (e) {
227
+ result = this.send(errorResponseFromError(e));
228
+ }
229
+ }
230
+
231
+ handleResponse(data: WorkerResponse): void {
232
+ let idx = nullthrows(data.idx);
233
+ let contentType = data.contentType;
234
+ let content = data.content;
235
+ let call = nullthrows(this.responseQueue.get(idx));
236
+
237
+ if (contentType === 'error') {
238
+ invariant(typeof content !== 'string');
239
+ call.reject(new ThrowableDiagnostic({diagnostic: content}));
240
+ } else {
241
+ call.resolve(content);
242
+ }
243
+
244
+ this.responseQueue.delete(idx);
245
+
246
+ // Process the next call
247
+ this.processQueue();
248
+ }
249
+
250
+ // Keep in mind to make sure responses to these calls are JSON.Stringify safe
251
+ addCall(
252
+ request: CallRequest,
253
+ awaitResponse: ?boolean = true,
254
+ ): Promise<mixed> {
255
+ // $FlowFixMe
256
+ let call: ChildCall = {
257
+ ...request,
258
+ type: 'request',
259
+ child: this.childId,
260
+ // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
261
+ awaitResponse,
262
+ resolve: () => {},
263
+ reject: () => {},
264
+ };
265
+
266
+ let promise;
267
+ if (awaitResponse) {
268
+ promise = new Promise((resolve, reject) => {
269
+ call.resolve = resolve;
270
+ call.reject = reject;
271
+ });
272
+ }
273
+
274
+ this.callQueue.push(call);
275
+ this.processQueue();
276
+
277
+ return promise ?? Promise.resolve();
278
+ }
279
+
280
+ sendRequest(call: ChildCall): void {
281
+ let idx;
282
+ if (call.awaitResponse) {
283
+ idx = this.responseId++;
284
+ this.responseQueue.set(idx, call);
285
+ }
286
+
287
+ this.send({
288
+ idx,
289
+ child: call.child,
290
+ type: call.type,
291
+ location: call.location,
292
+ handle: call.handle,
293
+ method: call.method,
294
+ args: call.args,
295
+ awaitResponse: call.awaitResponse,
296
+ });
297
+ }
298
+
299
+ processQueue(): void {
300
+ if (!this.callQueue.length) {
301
+ return;
302
+ }
303
+
304
+ if (this.responseQueue.size < this.maxConcurrentCalls) {
305
+ this.sendRequest(this.callQueue.shift());
306
+ }
307
+ }
308
+
309
+ handleEnd(): void {
310
+ this.loggerDisposable.dispose();
311
+ this.tracerDisposable.dispose();
312
+ }
313
+
314
+ createReverseHandle(fn: (...args: Array<any>) => mixed): Handle {
315
+ let handle = new Handle({
316
+ fn,
317
+ childId: this.childId,
318
+ });
319
+ this.handles.set(handle.id, handle);
320
+ return handle;
321
+ }
322
+ }
@@ -0,0 +1,10 @@
1
+ // @flow
2
+ import type {Child} from './child';
3
+
4
+ // This file is imported by both the WorkerFarm and child implementation.
5
+ // When a worker is inited, it sets the state in this file.
6
+ // This way, WorkerFarm can access the state without directly importing the child code.
7
+ export let child: ?Child = null;
8
+ export function setChild(c: Child) {
9
+ child = c;
10
+ }
@@ -0,0 +1,3 @@
1
+ // @flow
2
+ // eslint-disable-next-line monorepo/no-internal-import
3
+ module.exports = require('@atlaspack/core/src/worker.js');
@@ -0,0 +1,2 @@
1
+ // This is used only in browser builds
2
+ module.exports = {};
@@ -0,0 +1,75 @@
1
+ // @flow
2
+ import os from 'os';
3
+ import {execSync} from 'child_process';
4
+
5
+ const exec = (command: string): string => {
6
+ try {
7
+ let stdout = execSync(command, {
8
+ encoding: 'utf8',
9
+ // This prevents the command from outputting to the console
10
+ stdio: [null, null, null],
11
+ });
12
+ return stdout.trim();
13
+ } catch (e) {
14
+ return '';
15
+ }
16
+ };
17
+
18
+ export function detectRealCores(): number {
19
+ let platform = os.platform();
20
+ let amount = 0;
21
+
22
+ if (platform === 'linux') {
23
+ amount = parseInt(
24
+ exec('lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l'),
25
+ 10,
26
+ );
27
+ } else if (platform === 'darwin') {
28
+ amount = parseInt(exec('sysctl -n hw.physicalcpu_max'), 10);
29
+ } else if (platform === 'win32') {
30
+ const str = exec('wmic cpu get NumberOfCores').match(/\d+/g);
31
+ if (str !== null) {
32
+ amount = parseInt(str.filter(n => n !== '')[0], 10);
33
+ }
34
+ }
35
+
36
+ if (!amount || amount <= 0) {
37
+ throw new Error('Could not detect cpu count!');
38
+ }
39
+
40
+ return amount;
41
+ }
42
+
43
+ let cores;
44
+ export default function getCores(bypassCache?: boolean = false): number {
45
+ // Do not re-run commands if we already have the count...
46
+ if (cores && !bypassCache) {
47
+ return cores;
48
+ }
49
+
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
+ }
67
+ }
68
+
69
+ // Another fallback
70
+ if (!cores) {
71
+ cores = 1;
72
+ }
73
+
74
+ return cores;
75
+ }
package/src/index.js ADDED
@@ -0,0 +1,43 @@
1
+ // @flow
2
+ import type {TraceEvent, LogEvent} from '@atlaspack/types-internal';
3
+ import invariant from 'assert';
4
+ import WorkerFarm from './WorkerFarm';
5
+ import Logger from '@atlaspack/logger';
6
+ import bus from './bus';
7
+ import {tracer} from '@atlaspack/profiler';
8
+
9
+ if (!WorkerFarm.isWorker()) {
10
+ // Forward all logger events originating from workers into the main process
11
+ bus.on('logEvent', (e: LogEvent) => {
12
+ switch (e.level) {
13
+ case 'info':
14
+ Logger.info(e.diagnostics);
15
+ break;
16
+ case 'progress':
17
+ invariant(typeof e.message === 'string');
18
+ Logger.progress(e.message);
19
+ break;
20
+ case 'verbose':
21
+ Logger.verbose(e.diagnostics);
22
+ break;
23
+ case 'warn':
24
+ Logger.warn(e.diagnostics);
25
+ break;
26
+ case 'error':
27
+ Logger.error(e.diagnostics);
28
+ break;
29
+ default:
30
+ throw new Error('Unknown log level');
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
+ });
38
+ }
39
+
40
+ export default WorkerFarm;
41
+ export {bus};
42
+ export {Handle} from './WorkerFarm';
43
+ export type {WorkerApi, FarmOptions, SharedReference} from './WorkerFarm';
@@ -0,0 +1,56 @@
1
+ // @flow
2
+
3
+ import type {
4
+ ChildImpl,
5
+ MessageHandler,
6
+ ExitHandler,
7
+ WorkerMessage,
8
+ } from '../types';
9
+ import nullthrows from 'nullthrows';
10
+ import {setChild} from '../childState';
11
+ import {Child} from '../child';
12
+ import {serialize, deserialize} from '@atlaspack/core';
13
+
14
+ export default class ProcessChild implements ChildImpl {
15
+ onMessage: MessageHandler;
16
+ onExit: ExitHandler;
17
+
18
+ constructor(onMessage: MessageHandler, onExit: ExitHandler) {
19
+ if (!process.send) {
20
+ throw new Error('Only create ProcessChild instances in a worker!');
21
+ }
22
+
23
+ this.onMessage = onMessage;
24
+ this.onExit = onExit;
25
+ process.on('message', data => this.handleMessage(data));
26
+ }
27
+
28
+ handleMessage(data: string): void {
29
+ if (data === 'die') {
30
+ return this.stop();
31
+ }
32
+
33
+ this.onMessage(deserialize(Buffer.from(data, 'base64')));
34
+ }
35
+
36
+ send(data: WorkerMessage) {
37
+ let processSend = nullthrows(process.send).bind(process);
38
+ processSend(serialize(data).toString('base64'), err => {
39
+ if (err && err instanceof Error) {
40
+ // $FlowFixMe[prop-missing]
41
+ if (err.code === 'ERR_IPC_CHANNEL_CLOSED') {
42
+ // IPC connection closed
43
+ // no need to keep the worker running if it can't send or receive data
44
+ return this.stop();
45
+ }
46
+ }
47
+ });
48
+ }
49
+
50
+ stop() {
51
+ this.onExit(0);
52
+ process.exit();
53
+ }
54
+ }
55
+
56
+ setChild(new Child(ProcessChild));
@@ -0,0 +1,91 @@
1
+ // @flow
2
+
3
+ import type {
4
+ WorkerImpl,
5
+ MessageHandler,
6
+ ErrorHandler,
7
+ ExitHandler,
8
+ WorkerMessage,
9
+ } from '../types';
10
+ import childProcess, {type ChildProcess} from 'child_process';
11
+ import path from 'path';
12
+ import {serialize, deserialize} from '@atlaspack/core';
13
+
14
+ const WORKER_PATH = path.join(__dirname, 'ProcessChild.js');
15
+
16
+ export default class ProcessWorker implements WorkerImpl {
17
+ execArgv: Object;
18
+ onMessage: MessageHandler;
19
+ onError: ErrorHandler;
20
+ onExit: ExitHandler;
21
+ child: ChildProcess;
22
+ processQueue: boolean = true;
23
+ sendQueue: Array<any> = [];
24
+
25
+ constructor(
26
+ execArgv: Object,
27
+ onMessage: MessageHandler,
28
+ onError: ErrorHandler,
29
+ onExit: ExitHandler,
30
+ ) {
31
+ this.execArgv = execArgv;
32
+ this.onMessage = onMessage;
33
+ this.onError = onError;
34
+ this.onExit = onExit;
35
+ }
36
+
37
+ start(): Promise<void> {
38
+ this.child = childProcess.fork(WORKER_PATH, process.argv, {
39
+ execArgv: this.execArgv,
40
+ env: process.env,
41
+ cwd: process.cwd(),
42
+ });
43
+
44
+ this.child.on('message', (data: string) => {
45
+ this.onMessage(deserialize(Buffer.from(data, 'base64')));
46
+ });
47
+
48
+ this.child.once('exit', this.onExit);
49
+ this.child.on('error', this.onError);
50
+
51
+ return Promise.resolve();
52
+ }
53
+
54
+ async stop() {
55
+ this.child.send('die');
56
+
57
+ let forceKill = setTimeout(() => this.child.kill('SIGINT'), 500);
58
+ await new Promise(resolve => {
59
+ this.child.once('exit', resolve);
60
+ });
61
+
62
+ clearTimeout(forceKill);
63
+ }
64
+
65
+ send(data: WorkerMessage) {
66
+ if (!this.processQueue) {
67
+ this.sendQueue.push(data);
68
+ return;
69
+ }
70
+
71
+ let result = this.child.send(serialize(data).toString('base64'), error => {
72
+ if (error && error instanceof Error) {
73
+ // Ignore this, the workerfarm handles child errors
74
+ return;
75
+ }
76
+
77
+ this.processQueue = true;
78
+
79
+ if (this.sendQueue.length > 0) {
80
+ let queueCopy = this.sendQueue.slice(0);
81
+ this.sendQueue = [];
82
+ queueCopy.forEach(entry => this.send(entry));
83
+ }
84
+ });
85
+
86
+ if (!result || /^win/.test(process.platform)) {
87
+ // Queue is handling too much messages throttle it
88
+ this.processQueue = false;
89
+ }
90
+ }
91
+ }