@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.
- package/LICENSE +201 -0
- package/index.d.ts +23 -0
- package/lib/Handle.js +45 -0
- package/lib/Worker.js +188 -0
- package/lib/WorkerFarm.js +563 -0
- package/lib/backend.js +34 -0
- package/lib/bus.js +31 -0
- package/lib/child.js +294 -0
- package/lib/childState.js +14 -0
- package/lib/core-worker.browser.js +4 -0
- package/lib/core-worker.js +4 -0
- package/lib/cpuCount.js +79 -0
- package/lib/index.js +75 -0
- package/lib/process/ProcessChild.js +58 -0
- package/lib/process/ProcessWorker.js +83 -0
- package/lib/threads/ThreadsChild.js +49 -0
- package/lib/threads/ThreadsWorker.js +61 -0
- package/lib/types.js +1 -0
- package/lib/web/WebChild.js +44 -0
- package/lib/web/WebWorker.js +85 -0
- package/package.json +36 -0
- package/src/Handle.js +48 -0
- package/src/Worker.js +227 -0
- package/src/WorkerFarm.js +707 -0
- package/src/backend.js +33 -0
- package/src/bus.js +26 -0
- package/src/child.js +322 -0
- package/src/childState.js +10 -0
- package/src/core-worker.browser.js +3 -0
- package/src/core-worker.js +2 -0
- package/src/cpuCount.js +75 -0
- package/src/index.js +43 -0
- package/src/process/ProcessChild.js +56 -0
- package/src/process/ProcessWorker.js +91 -0
- package/src/threads/ThreadsChild.js +42 -0
- package/src/threads/ThreadsWorker.js +66 -0
- package/src/types.js +68 -0
- package/src/web/WebChild.js +53 -0
- package/src/web/WebWorker.js +88 -0
- package/test/cpuCount.test.js +19 -0
- package/test/integration/workerfarm/console.js +15 -0
- package/test/integration/workerfarm/echo.js +5 -0
- package/test/integration/workerfarm/ipc-pid.js +18 -0
- package/test/integration/workerfarm/ipc.js +10 -0
- package/test/integration/workerfarm/logging.js +19 -0
- package/test/integration/workerfarm/master-process-id.js +3 -0
- package/test/integration/workerfarm/master-sum.js +3 -0
- package/test/integration/workerfarm/ping.js +5 -0
- package/test/integration/workerfarm/resolve-shared-reference.js +5 -0
- package/test/integration/workerfarm/reverse-handle.js +5 -0
- package/test/integration/workerfarm/shared-reference.js +6 -0
- package/test/workerfarm.js +362 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ChildImpl,
|
|
5
|
+
MessageHandler,
|
|
6
|
+
ExitHandler,
|
|
7
|
+
WorkerMessage,
|
|
8
|
+
} from '../types';
|
|
9
|
+
import {isMainThread, parentPort} from 'worker_threads';
|
|
10
|
+
import nullthrows from 'nullthrows';
|
|
11
|
+
import {setChild} from '../childState';
|
|
12
|
+
import {Child} from '../child';
|
|
13
|
+
import {
|
|
14
|
+
prepareForSerialization,
|
|
15
|
+
restoreDeserializedObject,
|
|
16
|
+
} from '@atlaspack/core';
|
|
17
|
+
|
|
18
|
+
export default class ThreadsChild implements ChildImpl {
|
|
19
|
+
onMessage: MessageHandler;
|
|
20
|
+
onExit: ExitHandler;
|
|
21
|
+
|
|
22
|
+
constructor(onMessage: MessageHandler, onExit: ExitHandler) {
|
|
23
|
+
if (isMainThread || !parentPort) {
|
|
24
|
+
throw new Error('Only create ThreadsChild instances in a worker!');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.onMessage = onMessage;
|
|
28
|
+
this.onExit = onExit;
|
|
29
|
+
parentPort.on('message', data => this.handleMessage(data));
|
|
30
|
+
parentPort.on('close', this.onExit);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
handleMessage(data: WorkerMessage) {
|
|
34
|
+
this.onMessage(restoreDeserializedObject(data));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
send(data: WorkerMessage) {
|
|
38
|
+
nullthrows(parentPort).postMessage(prepareForSerialization(data));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setChild(new Child(ThreadsChild));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
WorkerImpl,
|
|
5
|
+
MessageHandler,
|
|
6
|
+
ErrorHandler,
|
|
7
|
+
ExitHandler,
|
|
8
|
+
WorkerMessage,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import {Worker} from 'worker_threads';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import {
|
|
13
|
+
prepareForSerialization,
|
|
14
|
+
restoreDeserializedObject,
|
|
15
|
+
} from '@atlaspack/core';
|
|
16
|
+
|
|
17
|
+
const WORKER_PATH = path.join(__dirname, 'ThreadsChild.js');
|
|
18
|
+
|
|
19
|
+
export default class ThreadsWorker implements WorkerImpl {
|
|
20
|
+
execArgv: Object;
|
|
21
|
+
onMessage: MessageHandler;
|
|
22
|
+
onError: ErrorHandler;
|
|
23
|
+
onExit: ExitHandler;
|
|
24
|
+
worker: Worker;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
execArgv: Object,
|
|
28
|
+
onMessage: MessageHandler,
|
|
29
|
+
onError: ErrorHandler,
|
|
30
|
+
onExit: ExitHandler,
|
|
31
|
+
) {
|
|
32
|
+
this.execArgv = execArgv;
|
|
33
|
+
this.onMessage = onMessage;
|
|
34
|
+
this.onError = onError;
|
|
35
|
+
this.onExit = onExit;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
start(): Promise<void> {
|
|
39
|
+
this.worker = new Worker(WORKER_PATH, {
|
|
40
|
+
execArgv: this.execArgv,
|
|
41
|
+
env: process.env,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.worker.on('message', data => this.handleMessage(data));
|
|
45
|
+
this.worker.on('error', this.onError);
|
|
46
|
+
this.worker.on('exit', this.onExit);
|
|
47
|
+
|
|
48
|
+
return new Promise<void>(resolve => {
|
|
49
|
+
this.worker.on('online', resolve);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
stop(): Promise<void> {
|
|
54
|
+
// In node 12, this returns a promise, but previously it accepted a callback
|
|
55
|
+
// TODO: Pass a callback in earlier versions of Node
|
|
56
|
+
return Promise.resolve(this.worker.terminate());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
handleMessage(data: WorkerMessage) {
|
|
60
|
+
this.onMessage(restoreDeserializedObject(data));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
send(data: WorkerMessage) {
|
|
64
|
+
this.worker.postMessage(prepareForSerialization(data));
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/types.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {Diagnostic} from '@atlaspack/diagnostic';
|
|
3
|
+
import type {FilePath} from '@atlaspack/types-internal';
|
|
4
|
+
|
|
5
|
+
export type LocationCallRequest = {|
|
|
6
|
+
args: $ReadOnlyArray<mixed>,
|
|
7
|
+
location: string,
|
|
8
|
+
method?: string,
|
|
9
|
+
|};
|
|
10
|
+
|
|
11
|
+
export type HandleCallRequest = {|
|
|
12
|
+
args: $ReadOnlyArray<mixed>,
|
|
13
|
+
handle: number,
|
|
14
|
+
|};
|
|
15
|
+
|
|
16
|
+
export type CallRequest = LocationCallRequest | HandleCallRequest;
|
|
17
|
+
|
|
18
|
+
export type WorkerRequest = {|
|
|
19
|
+
args: $ReadOnlyArray<any>,
|
|
20
|
+
awaitResponse?: boolean,
|
|
21
|
+
child?: ?number,
|
|
22
|
+
idx?: number,
|
|
23
|
+
location?: FilePath,
|
|
24
|
+
method?: ?string,
|
|
25
|
+
type: 'request',
|
|
26
|
+
handle?: number,
|
|
27
|
+
|};
|
|
28
|
+
|
|
29
|
+
export type WorkerDataResponse = {|
|
|
30
|
+
idx?: number,
|
|
31
|
+
child?: number,
|
|
32
|
+
type: 'response',
|
|
33
|
+
contentType: 'data',
|
|
34
|
+
content: string,
|
|
35
|
+
|};
|
|
36
|
+
|
|
37
|
+
export type WorkerErrorResponse = {|
|
|
38
|
+
idx?: number,
|
|
39
|
+
child?: number,
|
|
40
|
+
type: 'response',
|
|
41
|
+
contentType: 'error',
|
|
42
|
+
content: Diagnostic | Array<Diagnostic>,
|
|
43
|
+
|};
|
|
44
|
+
|
|
45
|
+
export type WorkerResponse = WorkerDataResponse | WorkerErrorResponse;
|
|
46
|
+
export type WorkerMessage = WorkerRequest | WorkerResponse;
|
|
47
|
+
|
|
48
|
+
export type MessageHandler = (data: WorkerMessage) => void;
|
|
49
|
+
export type ErrorHandler = (err: Error) => void;
|
|
50
|
+
export type ExitHandler = (code: number) => void;
|
|
51
|
+
export interface WorkerImpl {
|
|
52
|
+
constructor(
|
|
53
|
+
execArgv: Object,
|
|
54
|
+
onMessage: MessageHandler,
|
|
55
|
+
onError: ErrorHandler,
|
|
56
|
+
onExit: ExitHandler,
|
|
57
|
+
): void;
|
|
58
|
+
start(): Promise<void>;
|
|
59
|
+
stop(): Promise<void>;
|
|
60
|
+
send(data: WorkerMessage): void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ChildImpl {
|
|
64
|
+
constructor(onMessage: MessageHandler, onExit: ExitHandler): void;
|
|
65
|
+
send(data: WorkerMessage): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type BackendType = 'threads' | 'process' | 'web';
|
|
@@ -0,0 +1,53 @@
|
|
|
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 {
|
|
13
|
+
prepareForSerialization,
|
|
14
|
+
restoreDeserializedObject,
|
|
15
|
+
} from '@atlaspack/core';
|
|
16
|
+
|
|
17
|
+
export default class WebChild implements ChildImpl {
|
|
18
|
+
onMessage: MessageHandler;
|
|
19
|
+
onExit: ExitHandler;
|
|
20
|
+
|
|
21
|
+
constructor(onMessage: MessageHandler, onExit: ExitHandler) {
|
|
22
|
+
if (
|
|
23
|
+
!(
|
|
24
|
+
typeof WorkerGlobalScope !== 'undefined' &&
|
|
25
|
+
self instanceof WorkerGlobalScope
|
|
26
|
+
)
|
|
27
|
+
) {
|
|
28
|
+
throw new Error('Only create WebChild instances in a worker!');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.onMessage = onMessage;
|
|
32
|
+
this.onExit = onExit;
|
|
33
|
+
self.addEventListener('message', ({data}: MessageEvent) => {
|
|
34
|
+
if (data === 'stop') {
|
|
35
|
+
this.onExit(0);
|
|
36
|
+
self.postMessage('stopped');
|
|
37
|
+
}
|
|
38
|
+
// $FlowFixMe assume WorkerMessage as data
|
|
39
|
+
this.handleMessage(data);
|
|
40
|
+
});
|
|
41
|
+
self.postMessage('online');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handleMessage(data: WorkerMessage) {
|
|
45
|
+
this.onMessage(restoreDeserializedObject(data));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
send(data: WorkerMessage) {
|
|
49
|
+
self.postMessage(prepareForSerialization(data));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setChild(new Child(WebChild));
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
WorkerImpl,
|
|
5
|
+
MessageHandler,
|
|
6
|
+
ErrorHandler,
|
|
7
|
+
ExitHandler,
|
|
8
|
+
WorkerMessage,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import {
|
|
11
|
+
prepareForSerialization,
|
|
12
|
+
restoreDeserializedObject,
|
|
13
|
+
} from '@atlaspack/core';
|
|
14
|
+
import {makeDeferredWithPromise} from '@atlaspack/utils';
|
|
15
|
+
|
|
16
|
+
let id = 0;
|
|
17
|
+
|
|
18
|
+
export default class WebWorker implements WorkerImpl {
|
|
19
|
+
execArgv: Object;
|
|
20
|
+
onMessage: MessageHandler;
|
|
21
|
+
onError: ErrorHandler;
|
|
22
|
+
onExit: ExitHandler;
|
|
23
|
+
worker: Worker;
|
|
24
|
+
stopping: ?Promise<void>;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
execArgv: Object,
|
|
28
|
+
onMessage: MessageHandler,
|
|
29
|
+
onError: ErrorHandler,
|
|
30
|
+
onExit: ExitHandler,
|
|
31
|
+
) {
|
|
32
|
+
this.execArgv = execArgv;
|
|
33
|
+
this.onMessage = onMessage;
|
|
34
|
+
this.onError = onError;
|
|
35
|
+
this.onExit = onExit;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
start(): Promise<void> {
|
|
39
|
+
// $FlowFixMe[incompatible-call]
|
|
40
|
+
this.worker = new Worker(new URL('./WebChild.js', import.meta.url), {
|
|
41
|
+
name: `Atlaspack Worker ${id++}`,
|
|
42
|
+
type: 'module',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let {deferred, promise} = makeDeferredWithPromise();
|
|
46
|
+
|
|
47
|
+
this.worker.onmessage = ({data}) => {
|
|
48
|
+
if (data === 'online') {
|
|
49
|
+
deferred.resolve();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// $FlowFixMe assume WorkerMessage as data
|
|
54
|
+
this.handleMessage(data);
|
|
55
|
+
};
|
|
56
|
+
this.worker.onerror = this.onError;
|
|
57
|
+
// Web workers can't crash or intentionally stop on their own, apart from stop() below
|
|
58
|
+
// this.worker.on('exit', this.onExit);
|
|
59
|
+
|
|
60
|
+
return promise;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
stop(): Promise<void> {
|
|
64
|
+
if (!this.stopping) {
|
|
65
|
+
this.stopping = (async () => {
|
|
66
|
+
this.worker.postMessage('stop');
|
|
67
|
+
let {deferred, promise} = makeDeferredWithPromise();
|
|
68
|
+
this.worker.addEventListener('message', ({data}: MessageEvent) => {
|
|
69
|
+
if (data === 'stopped') {
|
|
70
|
+
deferred.resolve();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
await promise;
|
|
74
|
+
this.worker.terminate();
|
|
75
|
+
this.onExit(0);
|
|
76
|
+
})();
|
|
77
|
+
}
|
|
78
|
+
return this.stopping;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
handleMessage(data: WorkerMessage) {
|
|
82
|
+
this.onMessage(restoreDeserializedObject(data));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
send(data: WorkerMessage) {
|
|
86
|
+
this.worker.postMessage(prepareForSerialization(data));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
|
|
4
|
+
import getCores, {detectRealCores} from '../src/cpuCount';
|
|
5
|
+
|
|
6
|
+
describe('cpuCount', function () {
|
|
7
|
+
it('Should be able to detect real cpu count', () => {
|
|
8
|
+
// Windows not supported as getting the cpu count takes a couple seconds...
|
|
9
|
+
if (os.platform() === 'win32') return;
|
|
10
|
+
|
|
11
|
+
let cores = detectRealCores();
|
|
12
|
+
assert(cores > 0);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('getCores should return more than 0', () => {
|
|
16
|
+
let cores = getCores(true);
|
|
17
|
+
assert(cores > 0);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const WorkerFarm = require('../../../src/WorkerFarm').default;
|
|
2
|
+
|
|
3
|
+
function run() {
|
|
4
|
+
if (WorkerFarm.isWorker()) {
|
|
5
|
+
// Only test this behavior in workers. Logging in the main process will
|
|
6
|
+
// always work.
|
|
7
|
+
console.log('one');
|
|
8
|
+
console.info('two');
|
|
9
|
+
console.warn('three');
|
|
10
|
+
console.error('four');
|
|
11
|
+
console.debug('five');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.run = run;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const WorkerFarm = require('../../../src/WorkerFarm').default;
|
|
2
|
+
|
|
3
|
+
function run(api) {
|
|
4
|
+
let result = [process.pid];
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
api.callMaster({
|
|
7
|
+
location: require.resolve('./master-process-id.js'),
|
|
8
|
+
args: []
|
|
9
|
+
})
|
|
10
|
+
.then(pid => {
|
|
11
|
+
result.push(pid);
|
|
12
|
+
resolve(result);
|
|
13
|
+
})
|
|
14
|
+
.catch(reject);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.run = run;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const WorkerFarm = require('../../../src/WorkerFarm').default;
|
|
2
|
+
const Logger = require('@atlaspack/logger').default;
|
|
3
|
+
|
|
4
|
+
function run() {
|
|
5
|
+
if (WorkerFarm.isWorker()) {
|
|
6
|
+
// Only test this behavior in workers. Logging in the main process will
|
|
7
|
+
// always work.
|
|
8
|
+
Logger.info({
|
|
9
|
+
origin: 'logging-worker',
|
|
10
|
+
message: 'omg it works'
|
|
11
|
+
});
|
|
12
|
+
Logger.error({
|
|
13
|
+
origin: 'logging-worker',
|
|
14
|
+
message: 'errors objects dont work yet'
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.run = run;
|