@atlaspack/core 2.12.1-canary.3486 → 2.12.1-canary.3488
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/lib/atlaspack-v3/AtlaspackV3.js +8 -30
- package/lib/atlaspack-v3/WorkerPool.js +136 -0
- package/lib/atlaspack-v3/worker/worker.js +26 -1
- package/package.json +17 -17
- package/src/atlaspack-v3/AtlaspackV3.js +9 -24
- package/src/atlaspack-v3/WorkerPool.js +129 -0
- package/src/atlaspack-v3/worker/worker.js +23 -2
- package/test/atlaspack-v3/WorkerPool.test.js +121 -0
- package/test/atlaspack-v3/worker.js +23 -0
|
@@ -4,20 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.AtlaspackV3 = void 0;
|
|
7
|
-
|
|
8
|
-
const data = _interopRequireDefault(require("path"));
|
|
9
|
-
_path = function () {
|
|
10
|
-
return data;
|
|
11
|
-
};
|
|
12
|
-
return data;
|
|
13
|
-
}
|
|
14
|
-
function _worker_threads() {
|
|
15
|
-
const data = require("worker_threads");
|
|
16
|
-
_worker_threads = function () {
|
|
17
|
-
return data;
|
|
18
|
-
};
|
|
19
|
-
return data;
|
|
20
|
-
}
|
|
7
|
+
var _WorkerPool = require("./WorkerPool");
|
|
21
8
|
function _rust() {
|
|
22
9
|
const data = require("@atlaspack/rust");
|
|
23
10
|
_rust = function () {
|
|
@@ -25,8 +12,6 @@ function _rust() {
|
|
|
25
12
|
};
|
|
26
13
|
return data;
|
|
27
14
|
}
|
|
28
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
-
const WORKER_PATH = _path().default.join(__dirname, 'worker', 'index.js');
|
|
30
15
|
class AtlaspackV3 {
|
|
31
16
|
constructor({
|
|
32
17
|
fs,
|
|
@@ -50,23 +35,16 @@ class AtlaspackV3 {
|
|
|
50
35
|
});
|
|
51
36
|
}
|
|
52
37
|
async buildAssetGraph() {
|
|
53
|
-
const
|
|
38
|
+
const workerIds = [];
|
|
54
39
|
let result = await this._internal.buildAssetGraph({
|
|
55
|
-
registerWorker
|
|
40
|
+
registerWorker: tx_worker => {
|
|
41
|
+
// $FlowFixMe
|
|
42
|
+
const workerId = _WorkerPool.workerPool.registerWorker(tx_worker);
|
|
43
|
+
workerIds.push(workerId);
|
|
44
|
+
}
|
|
56
45
|
});
|
|
57
|
-
|
|
46
|
+
_WorkerPool.workerPool.releaseWorkers(workerIds);
|
|
58
47
|
return result;
|
|
59
48
|
}
|
|
60
|
-
#createWorkers() {
|
|
61
|
-
const workers = [];
|
|
62
|
-
return [workers, tx_worker => {
|
|
63
|
-
let worker = new (_worker_threads().Worker)(WORKER_PATH, {
|
|
64
|
-
workerData: {
|
|
65
|
-
tx_worker
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
workers.push(worker);
|
|
69
|
-
}];
|
|
70
|
-
}
|
|
71
49
|
}
|
|
72
50
|
exports.AtlaspackV3 = AtlaspackV3;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.WorkerPool = void 0;
|
|
7
|
+
exports.waitForMessage = waitForMessage;
|
|
8
|
+
exports.workerPool = void 0;
|
|
9
|
+
function _path() {
|
|
10
|
+
const data = _interopRequireDefault(require("path"));
|
|
11
|
+
_path = function () {
|
|
12
|
+
return data;
|
|
13
|
+
};
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
function _worker_threads() {
|
|
17
|
+
const data = require("worker_threads");
|
|
18
|
+
_worker_threads = function () {
|
|
19
|
+
return data;
|
|
20
|
+
};
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
24
|
+
/*!
|
|
25
|
+
* Atlaspack V3 delegates work to node.js worker threads.
|
|
26
|
+
*
|
|
27
|
+
* Starting-up each worker is relatively expensive, in particular when atlaspack
|
|
28
|
+
* is running in development mode, in which case each worker will transpile the
|
|
29
|
+
* project on startup.
|
|
30
|
+
*
|
|
31
|
+
* This "WorkerPool" mitigates this problem by reusing worker threads across
|
|
32
|
+
* builds.
|
|
33
|
+
*/
|
|
34
|
+
const WORKER_PATH = _path().default.join(__dirname, 'worker', 'index.js');
|
|
35
|
+
function waitForMessage(worker, type) {
|
|
36
|
+
return new Promise(resolve => {
|
|
37
|
+
const onMessage = message => {
|
|
38
|
+
if (message.type === type) {
|
|
39
|
+
resolve(message);
|
|
40
|
+
worker.off('message', onMessage);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
worker.on('message', onMessage);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
class WorkerPool {
|
|
47
|
+
#workerPool = [];
|
|
48
|
+
#usedWorkers = new Set();
|
|
49
|
+
#workerPath;
|
|
50
|
+
constructor(workerPath = WORKER_PATH) {
|
|
51
|
+
this.#workerPath = workerPath;
|
|
52
|
+
|
|
53
|
+
// $FlowFixMe
|
|
54
|
+
this[Symbol.dispose] = () => {
|
|
55
|
+
this.shutdown();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find a worker thread that is free to use or create a new one.
|
|
61
|
+
*
|
|
62
|
+
* Then register the `tx_worker` channel ID with the worker thread.
|
|
63
|
+
*/
|
|
64
|
+
registerWorker(tx_worker) {
|
|
65
|
+
const availableIndex = this.#workerPool.findIndex((worker, index) => !this.#usedWorkers.has(index));
|
|
66
|
+
const [workerId, worker] = availableIndex !== -1 ? [availableIndex, this.#workerPool[availableIndex]] : this.#createWorker();
|
|
67
|
+
this.#bootWorker(worker, tx_worker).catch(err => {
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.error('Worker failed, retrying to create it...', err);
|
|
70
|
+
this.#workerPool[workerId] = new (_worker_threads().Worker)(WORKER_PATH, {});
|
|
71
|
+
this.#bootWorker(this.#workerPool[workerId], tx_worker).catch(err => {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.error('Worker failed to start. The build may hang', err);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
this.#usedWorkers.add(workerId);
|
|
77
|
+
return workerId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Release a set of workers back into the pool for re-use
|
|
82
|
+
*/
|
|
83
|
+
releaseWorkers(ids) {
|
|
84
|
+
for (let id of ids) {
|
|
85
|
+
this.#usedWorkers.delete(id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Terminate all worker threads and reset state.
|
|
91
|
+
*/
|
|
92
|
+
shutdown() {
|
|
93
|
+
for (let worker of this.#workerPool) {
|
|
94
|
+
worker.terminate();
|
|
95
|
+
}
|
|
96
|
+
this.#usedWorkers.clear();
|
|
97
|
+
this.#workerPool = [];
|
|
98
|
+
}
|
|
99
|
+
getStats() {
|
|
100
|
+
return {
|
|
101
|
+
totalWorkers: this.#workerPool.length,
|
|
102
|
+
workersInUse: this.#usedWorkers.size
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the worker thread. Used for testing.
|
|
108
|
+
*/
|
|
109
|
+
getWorker(workerId) {
|
|
110
|
+
if (!this.#workerPool[workerId]) {
|
|
111
|
+
throw new Error('Worker does not exist');
|
|
112
|
+
}
|
|
113
|
+
return this.#workerPool[workerId];
|
|
114
|
+
}
|
|
115
|
+
async #bootWorker(worker, tx_worker) {
|
|
116
|
+
const timeout = new Promise((_, reject) => {
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
reject(new Error('Worker failed to register in time'));
|
|
119
|
+
}, 5000);
|
|
120
|
+
});
|
|
121
|
+
const workerReady = waitForMessage(worker, 'workerRegistered');
|
|
122
|
+
worker.postMessage({
|
|
123
|
+
type: 'registerWorker',
|
|
124
|
+
tx_worker
|
|
125
|
+
});
|
|
126
|
+
await Promise.race([workerReady, timeout]);
|
|
127
|
+
}
|
|
128
|
+
#createWorker() {
|
|
129
|
+
const worker = new (_worker_threads().Worker)(this.#workerPath, {});
|
|
130
|
+
const workerId = this.#workerPool.length;
|
|
131
|
+
this.#workerPool.push(worker);
|
|
132
|
+
return [workerId, worker];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.WorkerPool = WorkerPool;
|
|
136
|
+
const workerPool = exports.workerPool = new WorkerPool();
|
|
@@ -265,4 +265,29 @@ class AtlaspackWorker {
|
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
267
|
exports.AtlaspackWorker = AtlaspackWorker;
|
|
268
|
-
|
|
268
|
+
const worker = new AtlaspackWorker();
|
|
269
|
+
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.on('message', event => {
|
|
270
|
+
if (event.type === 'registerWorker') {
|
|
271
|
+
try {
|
|
272
|
+
napi().registerWorker(event.tx_worker, worker);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
// eslint-disable-next-line no-console
|
|
275
|
+
console.error('Registering worker failed... This might mean atlaspack is getting shut-down before the worker registered', err);
|
|
276
|
+
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.postMessage({
|
|
277
|
+
type: 'workerError',
|
|
278
|
+
error: err
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.postMessage({
|
|
282
|
+
type: 'workerRegistered'
|
|
283
|
+
});
|
|
284
|
+
} else if (event.type === 'probeStatus') {
|
|
285
|
+
_worker_threads().parentPort.postMessage({
|
|
286
|
+
type: 'status',
|
|
287
|
+
status: 'ok'
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.postMessage({
|
|
292
|
+
type: 'workerLoaded'
|
|
293
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/core",
|
|
3
|
-
"version": "2.12.1-canary.
|
|
3
|
+
"version": "2.12.1-canary.3488+bacf813b1",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -20,21 +20,21 @@
|
|
|
20
20
|
"check-ts": "tsc --noEmit index.d.ts"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@atlaspack/build-cache": "2.12.1-canary.
|
|
24
|
-
"@atlaspack/cache": "2.12.1-canary.
|
|
25
|
-
"@atlaspack/diagnostic": "2.12.1-canary.
|
|
26
|
-
"@atlaspack/events": "2.12.1-canary.
|
|
27
|
-
"@atlaspack/feature-flags": "2.12.1-canary.
|
|
28
|
-
"@atlaspack/fs": "2.12.1-canary.
|
|
29
|
-
"@atlaspack/graph": "3.2.1-canary.
|
|
30
|
-
"@atlaspack/logger": "2.12.1-canary.
|
|
31
|
-
"@atlaspack/package-manager": "2.12.1-canary.
|
|
32
|
-
"@atlaspack/plugin": "2.12.1-canary.
|
|
33
|
-
"@atlaspack/profiler": "2.12.1-canary.
|
|
34
|
-
"@atlaspack/rust": "2.12.1-canary.
|
|
35
|
-
"@atlaspack/types": "2.12.1-canary.
|
|
36
|
-
"@atlaspack/utils": "2.12.1-canary.
|
|
37
|
-
"@atlaspack/workers": "2.12.1-canary.
|
|
23
|
+
"@atlaspack/build-cache": "2.12.1-canary.3488+bacf813b1",
|
|
24
|
+
"@atlaspack/cache": "2.12.1-canary.3488+bacf813b1",
|
|
25
|
+
"@atlaspack/diagnostic": "2.12.1-canary.3488+bacf813b1",
|
|
26
|
+
"@atlaspack/events": "2.12.1-canary.3488+bacf813b1",
|
|
27
|
+
"@atlaspack/feature-flags": "2.12.1-canary.3488+bacf813b1",
|
|
28
|
+
"@atlaspack/fs": "2.12.1-canary.3488+bacf813b1",
|
|
29
|
+
"@atlaspack/graph": "3.2.1-canary.3488+bacf813b1",
|
|
30
|
+
"@atlaspack/logger": "2.12.1-canary.3488+bacf813b1",
|
|
31
|
+
"@atlaspack/package-manager": "2.12.1-canary.3488+bacf813b1",
|
|
32
|
+
"@atlaspack/plugin": "2.12.1-canary.3488+bacf813b1",
|
|
33
|
+
"@atlaspack/profiler": "2.12.1-canary.3488+bacf813b1",
|
|
34
|
+
"@atlaspack/rust": "2.12.1-canary.3488+bacf813b1",
|
|
35
|
+
"@atlaspack/types": "2.12.1-canary.3488+bacf813b1",
|
|
36
|
+
"@atlaspack/utils": "2.12.1-canary.3488+bacf813b1",
|
|
37
|
+
"@atlaspack/workers": "2.12.1-canary.3488+bacf813b1",
|
|
38
38
|
"@mischnic/json-sourcemap": "^0.1.0",
|
|
39
39
|
"@parcel/source-map": "^2.1.1",
|
|
40
40
|
"base-x": "^3.0.8",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"browser": {
|
|
66
66
|
"./src/serializerCore.js": "./src/serializerCore.browser.js"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "bacf813b1fd2c475991643425e6e1d1545791424"
|
|
69
69
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {Worker} from 'worker_threads';
|
|
3
|
+
import {workerPool} from './WorkerPool';
|
|
5
4
|
import {AtlaspackNapi, type AtlaspackNapiOptions} from '@atlaspack/rust';
|
|
6
5
|
|
|
7
|
-
const WORKER_PATH = path.join(__dirname, 'worker', 'index.js');
|
|
8
|
-
|
|
9
6
|
export type AtlaspackV3Options = {|
|
|
10
7
|
fs?: AtlaspackNapiOptions['fs'],
|
|
11
8
|
nodeWorkers?: number,
|
|
@@ -42,29 +39,17 @@ export class AtlaspackV3 {
|
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
async buildAssetGraph(): Promise<any> {
|
|
45
|
-
const
|
|
46
|
-
|
|
42
|
+
const workerIds = [];
|
|
47
43
|
let result = await this._internal.buildAssetGraph({
|
|
48
|
-
registerWorker
|
|
44
|
+
registerWorker: (tx_worker) => {
|
|
45
|
+
// $FlowFixMe
|
|
46
|
+
const workerId = workerPool.registerWorker(tx_worker);
|
|
47
|
+
workerIds.push(workerId);
|
|
48
|
+
},
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
return result;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
#createWorkers() {
|
|
56
|
-
const workers = [];
|
|
51
|
+
workerPool.releaseWorkers(workerIds);
|
|
57
52
|
|
|
58
|
-
return
|
|
59
|
-
workers,
|
|
60
|
-
(tx_worker) => {
|
|
61
|
-
let worker = new Worker(WORKER_PATH, {
|
|
62
|
-
workerData: {
|
|
63
|
-
tx_worker,
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
workers.push(worker);
|
|
67
|
-
},
|
|
68
|
-
];
|
|
53
|
+
return result;
|
|
69
54
|
}
|
|
70
55
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
/*!
|
|
3
|
+
* Atlaspack V3 delegates work to node.js worker threads.
|
|
4
|
+
*
|
|
5
|
+
* Starting-up each worker is relatively expensive, in particular when atlaspack
|
|
6
|
+
* is running in development mode, in which case each worker will transpile the
|
|
7
|
+
* project on startup.
|
|
8
|
+
*
|
|
9
|
+
* This "WorkerPool" mitigates this problem by reusing worker threads across
|
|
10
|
+
* builds.
|
|
11
|
+
*/
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import {Worker} from 'worker_threads';
|
|
14
|
+
|
|
15
|
+
const WORKER_PATH = path.join(__dirname, 'worker', 'index.js');
|
|
16
|
+
|
|
17
|
+
export function waitForMessage<T>(worker: Worker, type: string): Promise<T> {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const onMessage = (message: T & {|type: string|}) => {
|
|
20
|
+
if (message.type === type) {
|
|
21
|
+
resolve(message);
|
|
22
|
+
worker.off('message', onMessage);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
worker.on('message', onMessage);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class WorkerPool {
|
|
30
|
+
#workerPool: Worker[] = [];
|
|
31
|
+
#usedWorkers: Set<number> = new Set();
|
|
32
|
+
#workerPath: string;
|
|
33
|
+
|
|
34
|
+
constructor(workerPath: string = WORKER_PATH) {
|
|
35
|
+
this.#workerPath = workerPath;
|
|
36
|
+
|
|
37
|
+
// $FlowFixMe
|
|
38
|
+
this[Symbol.dispose] = () => {
|
|
39
|
+
this.shutdown();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find a worker thread that is free to use or create a new one.
|
|
45
|
+
*
|
|
46
|
+
* Then register the `tx_worker` channel ID with the worker thread.
|
|
47
|
+
*/
|
|
48
|
+
registerWorker(tx_worker: number): number {
|
|
49
|
+
const availableIndex = this.#workerPool.findIndex(
|
|
50
|
+
(worker, index) => !this.#usedWorkers.has(index),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const [workerId, worker] =
|
|
54
|
+
availableIndex !== -1
|
|
55
|
+
? [availableIndex, this.#workerPool[availableIndex]]
|
|
56
|
+
: this.#createWorker();
|
|
57
|
+
|
|
58
|
+
this.#bootWorker(worker, tx_worker).catch((err) => {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.error('Worker failed, retrying to create it...', err);
|
|
61
|
+
this.#workerPool[workerId] = new Worker(WORKER_PATH, {});
|
|
62
|
+
this.#bootWorker(this.#workerPool[workerId], tx_worker).catch((err) => {
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.error('Worker failed to start. The build may hang', err);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.#usedWorkers.add(workerId);
|
|
69
|
+
|
|
70
|
+
return workerId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Release a set of workers back into the pool for re-use
|
|
75
|
+
*/
|
|
76
|
+
releaseWorkers(ids: number[]) {
|
|
77
|
+
for (let id of ids) {
|
|
78
|
+
this.#usedWorkers.delete(id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Terminate all worker threads and reset state.
|
|
84
|
+
*/
|
|
85
|
+
shutdown() {
|
|
86
|
+
for (let worker of this.#workerPool) {
|
|
87
|
+
worker.terminate();
|
|
88
|
+
}
|
|
89
|
+
this.#usedWorkers.clear();
|
|
90
|
+
this.#workerPool = [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getStats(): {|totalWorkers: number, workersInUse: number|} {
|
|
94
|
+
return {
|
|
95
|
+
totalWorkers: this.#workerPool.length,
|
|
96
|
+
workersInUse: this.#usedWorkers.size,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get the worker thread. Used for testing.
|
|
102
|
+
*/
|
|
103
|
+
getWorker(workerId: number): Worker {
|
|
104
|
+
if (!this.#workerPool[workerId]) {
|
|
105
|
+
throw new Error('Worker does not exist');
|
|
106
|
+
}
|
|
107
|
+
return this.#workerPool[workerId];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async #bootWorker(worker: Worker, tx_worker: number): Promise<void> {
|
|
111
|
+
const timeout = new Promise((_, reject) => {
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
reject(new Error('Worker failed to register in time'));
|
|
114
|
+
}, 5000);
|
|
115
|
+
});
|
|
116
|
+
const workerReady = waitForMessage(worker, 'workerRegistered');
|
|
117
|
+
worker.postMessage({type: 'registerWorker', tx_worker});
|
|
118
|
+
await Promise.race([workerReady, timeout]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#createWorker(): [number, Worker] {
|
|
122
|
+
const worker = new Worker(this.#workerPath, {});
|
|
123
|
+
const workerId = this.#workerPool.length;
|
|
124
|
+
this.#workerPool.push(worker);
|
|
125
|
+
return [workerId, worker];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const workerPool: WorkerPool = new WorkerPool();
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
FilePath,
|
|
11
11
|
FileSystem,
|
|
12
12
|
} from '@atlaspack/types';
|
|
13
|
-
import {
|
|
13
|
+
import {parentPort} from 'worker_threads';
|
|
14
14
|
import * as module from 'module';
|
|
15
15
|
|
|
16
16
|
import {
|
|
@@ -273,7 +273,28 @@ export class AtlaspackWorker {
|
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
const worker = new AtlaspackWorker();
|
|
277
|
+
parentPort?.on('message', (event) => {
|
|
278
|
+
if (event.type === 'registerWorker') {
|
|
279
|
+
try {
|
|
280
|
+
napi.registerWorker(event.tx_worker, worker);
|
|
281
|
+
} catch (err) {
|
|
282
|
+
// eslint-disable-next-line no-console
|
|
283
|
+
console.error(
|
|
284
|
+
'Registering worker failed... This might mean atlaspack is getting shut-down before the worker registered',
|
|
285
|
+
err,
|
|
286
|
+
);
|
|
287
|
+
parentPort?.postMessage({type: 'workerError', error: err});
|
|
288
|
+
}
|
|
289
|
+
parentPort?.postMessage({type: 'workerRegistered'});
|
|
290
|
+
} else if (event.type === 'probeStatus') {
|
|
291
|
+
parentPort.postMessage({
|
|
292
|
+
type: 'status',
|
|
293
|
+
status: 'ok',
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
parentPort?.postMessage({type: 'workerLoaded'});
|
|
277
298
|
|
|
278
299
|
type ResolverState<T> = {|
|
|
279
300
|
resolver: Resolver<T>,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import {Worker} from 'worker_threads';
|
|
5
|
+
import {WorkerPool, waitForMessage} from '../../src/atlaspack-v3/WorkerPool';
|
|
6
|
+
import assert from 'assert';
|
|
7
|
+
|
|
8
|
+
function probeStatus(worker: Worker) {
|
|
9
|
+
const response = waitForMessage(worker, 'status');
|
|
10
|
+
worker.postMessage({type: 'probeStatus'});
|
|
11
|
+
return response;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('WorkerPool', () => {
|
|
15
|
+
it('can create workers and will send them the tx_worker value', async () => {
|
|
16
|
+
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
17
|
+
const workerId = workerPool.registerWorker(0);
|
|
18
|
+
const worker = workerPool.getWorker(workerId);
|
|
19
|
+
const status = await probeStatus(worker);
|
|
20
|
+
|
|
21
|
+
assert.deepEqual(status, {
|
|
22
|
+
type: 'status',
|
|
23
|
+
status: 'test-status-ok',
|
|
24
|
+
receivedMessages: [
|
|
25
|
+
{
|
|
26
|
+
type: 'registerWorker',
|
|
27
|
+
tx_worker: 0,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('when a worker is created, it is tracked', () => {
|
|
34
|
+
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
35
|
+
const w1 = workerPool.registerWorker(0);
|
|
36
|
+
const w2 = workerPool.registerWorker(0);
|
|
37
|
+
const w3 = workerPool.registerWorker(0);
|
|
38
|
+
assert.notEqual(w1, w2);
|
|
39
|
+
assert.notEqual(w2, w3);
|
|
40
|
+
assert.notEqual(w1, w3);
|
|
41
|
+
|
|
42
|
+
assert.deepEqual(workerPool.getStats(), {
|
|
43
|
+
totalWorkers: 3,
|
|
44
|
+
workersInUse: 3,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('workers can be released and will then be reused', async () => {
|
|
49
|
+
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
50
|
+
const worker1 = workerPool.registerWorker(0);
|
|
51
|
+
workerPool.registerWorker(5);
|
|
52
|
+
assert.deepEqual(workerPool.getStats(), {
|
|
53
|
+
totalWorkers: 2,
|
|
54
|
+
workersInUse: 2,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Release the worker
|
|
58
|
+
workerPool.releaseWorkers([worker1]);
|
|
59
|
+
assert.deepEqual(workerPool.getStats(), {
|
|
60
|
+
totalWorkers: 2,
|
|
61
|
+
workersInUse: 1,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const worker3 = workerPool.registerWorker(33);
|
|
65
|
+
assert.equal(worker1, worker3);
|
|
66
|
+
|
|
67
|
+
const worker = workerPool.getWorker(worker3);
|
|
68
|
+
const status = await probeStatus(worker);
|
|
69
|
+
assert.deepEqual(status, {
|
|
70
|
+
type: 'status',
|
|
71
|
+
status: 'test-status-ok',
|
|
72
|
+
receivedMessages: [
|
|
73
|
+
{
|
|
74
|
+
type: 'registerWorker',
|
|
75
|
+
tx_worker: 0,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: 'registerWorker',
|
|
79
|
+
tx_worker: 33,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('shutdown', () => {
|
|
86
|
+
it('terminates all workers', async () => {
|
|
87
|
+
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
88
|
+
const worker1Id = workerPool.registerWorker(0);
|
|
89
|
+
const worker2Id = workerPool.registerWorker(0);
|
|
90
|
+
const worker1 = workerPool.getWorker(worker1Id);
|
|
91
|
+
const worker2 = workerPool.getWorker(worker2Id);
|
|
92
|
+
|
|
93
|
+
const worker1Exit = new Promise((resolve) => {
|
|
94
|
+
worker1.on('exit', () => {
|
|
95
|
+
resolve(null);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const worker2Exit = new Promise((resolve) => {
|
|
99
|
+
worker2.on('exit', () => {
|
|
100
|
+
resolve(null);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
workerPool.shutdown();
|
|
105
|
+
assert.throws(() => {
|
|
106
|
+
workerPool.getWorker(worker1Id);
|
|
107
|
+
});
|
|
108
|
+
assert.throws(() => {
|
|
109
|
+
workerPool.getWorker(worker2Id);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await worker1Exit;
|
|
113
|
+
await worker2Exit;
|
|
114
|
+
|
|
115
|
+
assert.deepEqual(workerPool.getStats(), {
|
|
116
|
+
totalWorkers: 0,
|
|
117
|
+
workersInUse: 0,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const {parentPort} = require('worker_threads');
|
|
2
|
+
|
|
3
|
+
const receivedMessages = [];
|
|
4
|
+
|
|
5
|
+
parentPort.on('message', (message) => {
|
|
6
|
+
if (message.type === 'registerWorker') {
|
|
7
|
+
parentPort.postMessage({
|
|
8
|
+
type: 'workerRegistered',
|
|
9
|
+
});
|
|
10
|
+
} else if (message.type === 'probeStatus') {
|
|
11
|
+
parentPort.postMessage({
|
|
12
|
+
type: 'status',
|
|
13
|
+
status: 'test-status-ok',
|
|
14
|
+
receivedMessages,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
receivedMessages.push(message);
|
|
19
|
+
parentPort.postMessage({
|
|
20
|
+
type: 'received',
|
|
21
|
+
message,
|
|
22
|
+
});
|
|
23
|
+
});
|