@atlaspack/core 2.13.2-canary.3657 → 2.13.2-canary.3658
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 +5 -18
- package/lib/atlaspack-v3/NapiWorkerPool.js +71 -0
- package/lib/atlaspack-v3/index.js +8 -1
- package/lib/atlaspack-v3/worker/worker.js +4 -25
- package/package.json +17 -17
- package/src/atlaspack-v3/AtlaspackV3.js +6 -22
- package/src/atlaspack-v3/NapiWorkerPool.js +53 -0
- package/src/atlaspack-v3/index.js +2 -0
- package/src/atlaspack-v3/worker/worker.js +3 -21
- package/lib/atlaspack-v3/WorkerPool.js +0 -171
- package/src/atlaspack-v3/WorkerPool.js +0 -171
- package/test/atlaspack-v3/WorkerPool.test.js +0 -147
|
@@ -11,7 +11,7 @@ function _rust() {
|
|
|
11
11
|
};
|
|
12
12
|
return data;
|
|
13
13
|
}
|
|
14
|
-
var
|
|
14
|
+
var _NapiWorkerPool = require("./NapiWorkerPool");
|
|
15
15
|
function _diagnostic() {
|
|
16
16
|
const data = _interopRequireDefault(require("@atlaspack/diagnostic"));
|
|
17
17
|
_diagnostic = function () {
|
|
@@ -23,39 +23,26 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
23
23
|
class AtlaspackV3 {
|
|
24
24
|
constructor({
|
|
25
25
|
fs,
|
|
26
|
-
nodeWorkers,
|
|
27
26
|
packageManager,
|
|
28
27
|
threads,
|
|
29
28
|
lmdb,
|
|
29
|
+
napiWorkerPool = new _NapiWorkerPool.NapiWorkerPool(),
|
|
30
30
|
...options
|
|
31
31
|
}) {
|
|
32
32
|
options.logLevel = options.logLevel || 'error';
|
|
33
33
|
options.defaultTargetOptions = options.defaultTargetOptions || {};
|
|
34
34
|
// $FlowFixMe "engines" are readonly
|
|
35
35
|
options.defaultTargetOptions.engines = options.defaultTargetOptions.engines || {};
|
|
36
|
-
this._workerIds = [];
|
|
37
36
|
this._internal = _rust().AtlaspackNapi.create({
|
|
38
37
|
fs,
|
|
39
|
-
nodeWorkers,
|
|
40
38
|
packageManager,
|
|
41
39
|
threads,
|
|
42
|
-
options
|
|
40
|
+
options,
|
|
41
|
+
napiWorkerPool
|
|
43
42
|
}, lmdb);
|
|
44
43
|
}
|
|
45
44
|
async buildAssetGraph() {
|
|
46
|
-
|
|
47
|
-
let [graph, error] = await this._internal.buildAssetGraph({
|
|
48
|
-
registerWorker: tx_worker => {
|
|
49
|
-
// $FlowFixMe
|
|
50
|
-
const workerId = _WorkerPool.workerPool.registerWorker(tx_worker);
|
|
51
|
-
workerIds.push(workerId);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// In the integration tests we keep the workers alive so they don't need to
|
|
56
|
-
// be re-initialized for the next test
|
|
57
|
-
|
|
58
|
-
_WorkerPool.workerPool.shutdown();
|
|
45
|
+
let [graph, error] = await this._internal.buildAssetGraph();
|
|
59
46
|
if (error !== null) {
|
|
60
47
|
throw new (_diagnostic().default)({
|
|
61
48
|
diagnostic: error
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NapiWorkerPool = void 0;
|
|
7
|
+
function _worker_threads() {
|
|
8
|
+
const data = require("worker_threads");
|
|
9
|
+
_worker_threads = function () {
|
|
10
|
+
return data;
|
|
11
|
+
};
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
function _path() {
|
|
15
|
+
const data = _interopRequireDefault(require("path"));
|
|
16
|
+
_path = function () {
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
function _process() {
|
|
22
|
+
const data = _interopRequireDefault(require("process"));
|
|
23
|
+
_process = function () {
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
function _rust() {
|
|
29
|
+
const data = require("@atlaspack/rust");
|
|
30
|
+
_rust = function () {
|
|
31
|
+
return data;
|
|
32
|
+
};
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
36
|
+
const WORKER_PATH = _path().default.join(__dirname, 'worker', 'index.js');
|
|
37
|
+
const ATLASPACK_NAPI_WORKERS = _process().default.env.ATLASPACK_NAPI_WORKERS && parseInt(_process().default.env.ATLASPACK_NAPI_WORKERS, 10);
|
|
38
|
+
class NapiWorkerPool {
|
|
39
|
+
#workers;
|
|
40
|
+
#napiWorkers;
|
|
41
|
+
#workerCount;
|
|
42
|
+
constructor({
|
|
43
|
+
workerCount
|
|
44
|
+
} = {
|
|
45
|
+
workerCount: undefined
|
|
46
|
+
}) {
|
|
47
|
+
this.#workerCount = workerCount ?? ATLASPACK_NAPI_WORKERS ?? (0, _rust().getAvailableThreads)();
|
|
48
|
+
if (!this.#workerCount) {
|
|
49
|
+
// TODO use main thread if workerCount is 0
|
|
50
|
+
}
|
|
51
|
+
this.#workers = [];
|
|
52
|
+
this.#napiWorkers = [];
|
|
53
|
+
for (let i = 0; i < this.#workerCount; i++) {
|
|
54
|
+
let worker = new (_worker_threads().Worker)(WORKER_PATH);
|
|
55
|
+
this.#workers.push(worker);
|
|
56
|
+
this.#napiWorkers.push(new Promise(res => worker.once('message', res)));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
workerCount() {
|
|
60
|
+
return this.#workerCount;
|
|
61
|
+
}
|
|
62
|
+
getWorkers() {
|
|
63
|
+
return Promise.all(this.#napiWorkers);
|
|
64
|
+
}
|
|
65
|
+
shutdown() {
|
|
66
|
+
for (const worker of this.#workers) {
|
|
67
|
+
worker.terminate();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.NapiWorkerPool = NapiWorkerPool;
|
|
@@ -15,5 +15,12 @@ Object.defineProperty(exports, "FileSystemV3", {
|
|
|
15
15
|
return _fs.FileSystemV3;
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
|
+
Object.defineProperty(exports, "NapiWorkerPool", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _NapiWorkerPool.NapiWorkerPool;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
18
24
|
var _fs = require("./fs");
|
|
19
|
-
var _AtlaspackV = require("./AtlaspackV3");
|
|
25
|
+
var _AtlaspackV = require("./AtlaspackV3");
|
|
26
|
+
var _NapiWorkerPool = require("./NapiWorkerPool");
|
|
@@ -272,30 +272,9 @@ class AtlaspackWorker {
|
|
|
272
272
|
JSON.stringify((await mutableAsset.getMap()).toVLQ()) : ''];
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
|
+
|
|
276
|
+
// Create napi worker and send it back to main thread
|
|
275
277
|
exports.AtlaspackWorker = AtlaspackWorker;
|
|
276
278
|
const worker = new AtlaspackWorker();
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
napi().registerWorker(event.tx_worker, worker);
|
|
281
|
-
} catch (err) {
|
|
282
|
-
// eslint-disable-next-line no-console
|
|
283
|
-
console.error('Registering worker failed... This might mean atlaspack is getting shut-down before the worker registered', err);
|
|
284
|
-
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.postMessage({
|
|
285
|
-
type: 'workerError',
|
|
286
|
-
error: err
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.postMessage({
|
|
290
|
-
type: 'workerRegistered'
|
|
291
|
-
});
|
|
292
|
-
} else if (event.type === 'probeStatus') {
|
|
293
|
-
_worker_threads().parentPort.postMessage({
|
|
294
|
-
type: 'status',
|
|
295
|
-
status: 'ok'
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.postMessage({
|
|
300
|
-
type: 'workerLoaded'
|
|
301
|
-
});
|
|
279
|
+
const napiWorker = napi().newNodejsWorker(worker);
|
|
280
|
+
_worker_threads().parentPort === null || _worker_threads().parentPort === void 0 || _worker_threads().parentPort.postMessage(napiWorker);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/core",
|
|
3
|
-
"version": "2.13.2-canary.
|
|
3
|
+
"version": "2.13.2-canary.3658+4812d0f74",
|
|
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.13.2-canary.
|
|
24
|
-
"@atlaspack/cache": "2.13.2-canary.
|
|
25
|
-
"@atlaspack/diagnostic": "2.13.2-canary.
|
|
26
|
-
"@atlaspack/events": "2.13.2-canary.
|
|
27
|
-
"@atlaspack/feature-flags": "2.13.2-canary.
|
|
28
|
-
"@atlaspack/fs": "2.13.2-canary.
|
|
29
|
-
"@atlaspack/graph": "3.3.2-canary.
|
|
30
|
-
"@atlaspack/logger": "2.13.2-canary.
|
|
31
|
-
"@atlaspack/package-manager": "2.13.2-canary.
|
|
32
|
-
"@atlaspack/plugin": "2.13.2-canary.
|
|
33
|
-
"@atlaspack/profiler": "2.13.2-canary.
|
|
34
|
-
"@atlaspack/rust": "2.13.2-canary.
|
|
35
|
-
"@atlaspack/types": "2.13.2-canary.
|
|
36
|
-
"@atlaspack/utils": "2.13.2-canary.
|
|
37
|
-
"@atlaspack/workers": "2.13.2-canary.
|
|
23
|
+
"@atlaspack/build-cache": "2.13.2-canary.3658+4812d0f74",
|
|
24
|
+
"@atlaspack/cache": "2.13.2-canary.3658+4812d0f74",
|
|
25
|
+
"@atlaspack/diagnostic": "2.13.2-canary.3658+4812d0f74",
|
|
26
|
+
"@atlaspack/events": "2.13.2-canary.3658+4812d0f74",
|
|
27
|
+
"@atlaspack/feature-flags": "2.13.2-canary.3658+4812d0f74",
|
|
28
|
+
"@atlaspack/fs": "2.13.2-canary.3658+4812d0f74",
|
|
29
|
+
"@atlaspack/graph": "3.3.2-canary.3658+4812d0f74",
|
|
30
|
+
"@atlaspack/logger": "2.13.2-canary.3658+4812d0f74",
|
|
31
|
+
"@atlaspack/package-manager": "2.13.2-canary.3658+4812d0f74",
|
|
32
|
+
"@atlaspack/plugin": "2.13.2-canary.3658+4812d0f74",
|
|
33
|
+
"@atlaspack/profiler": "2.13.2-canary.3658+4812d0f74",
|
|
34
|
+
"@atlaspack/rust": "2.13.2-canary.3658+4812d0f74",
|
|
35
|
+
"@atlaspack/types": "2.13.2-canary.3658+4812d0f74",
|
|
36
|
+
"@atlaspack/utils": "2.13.2-canary.3658+4812d0f74",
|
|
37
|
+
"@atlaspack/workers": "2.13.2-canary.3658+4812d0f74",
|
|
38
38
|
"@mischnic/json-sourcemap": "^0.1.0",
|
|
39
39
|
"@parcel/source-map": "^2.1.1",
|
|
40
40
|
"base-x": "^3.0.8",
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"browser": {
|
|
67
67
|
"./src/serializerCore.js": "./src/serializerCore.browser.js"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "4812d0f7400af0f8416f1b7175ecb87700860a68"
|
|
70
70
|
}
|
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
type Lmdb,
|
|
6
6
|
type AtlaspackNapiOptions,
|
|
7
7
|
} from '@atlaspack/rust';
|
|
8
|
-
import {
|
|
8
|
+
import {NapiWorkerPool} from './NapiWorkerPool';
|
|
9
9
|
import ThrowableDiagnostic from '@atlaspack/diagnostic';
|
|
10
10
|
import type {Event} from '@parcel/watcher';
|
|
11
|
+
import type {NapiWorkerPool as INapiWorkerPool} from '@atlaspack/types';
|
|
11
12
|
|
|
12
13
|
export type AtlaspackV3Options = {|
|
|
13
14
|
fs?: AtlaspackNapiOptions['fs'],
|
|
14
|
-
nodeWorkers?: number,
|
|
15
15
|
packageManager?: AtlaspackNapiOptions['packageManager'],
|
|
16
16
|
threads?: number,
|
|
17
17
|
/**
|
|
@@ -19,6 +19,7 @@ export type AtlaspackV3Options = {|
|
|
|
19
19
|
*/
|
|
20
20
|
lmdb: Lmdb,
|
|
21
21
|
featureFlags?: {[string]: string | boolean},
|
|
22
|
+
napiWorkerPool?: INapiWorkerPool,
|
|
22
23
|
...AtlaspackNapiOptions['options'],
|
|
23
24
|
|};
|
|
24
25
|
|
|
@@ -28,10 +29,10 @@ export class AtlaspackV3 {
|
|
|
28
29
|
|
|
29
30
|
constructor({
|
|
30
31
|
fs,
|
|
31
|
-
nodeWorkers,
|
|
32
32
|
packageManager,
|
|
33
33
|
threads,
|
|
34
34
|
lmdb,
|
|
35
|
+
napiWorkerPool = new NapiWorkerPool(),
|
|
35
36
|
...options
|
|
36
37
|
}: AtlaspackV3Options) {
|
|
37
38
|
options.logLevel = options.logLevel || 'error';
|
|
@@ -40,37 +41,20 @@ export class AtlaspackV3 {
|
|
|
40
41
|
options.defaultTargetOptions.engines =
|
|
41
42
|
options.defaultTargetOptions.engines || {};
|
|
42
43
|
|
|
43
|
-
this._workerIds = [];
|
|
44
44
|
this._internal = AtlaspackNapi.create(
|
|
45
45
|
{
|
|
46
46
|
fs,
|
|
47
|
-
nodeWorkers,
|
|
48
47
|
packageManager,
|
|
49
48
|
threads,
|
|
50
49
|
options,
|
|
50
|
+
napiWorkerPool,
|
|
51
51
|
},
|
|
52
52
|
lmdb,
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async buildAssetGraph(): Promise<any> {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let [graph, error] = await this._internal.buildAssetGraph({
|
|
60
|
-
registerWorker: (tx_worker) => {
|
|
61
|
-
// $FlowFixMe
|
|
62
|
-
const workerId = workerPool.registerWorker(tx_worker);
|
|
63
|
-
workerIds.push(workerId);
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// In the integration tests we keep the workers alive so they don't need to
|
|
68
|
-
// be re-initialized for the next test
|
|
69
|
-
if (process.env.ATLASPACK_BUILD_ENV === 'test') {
|
|
70
|
-
workerPool.releaseWorkers(workerIds);
|
|
71
|
-
} else {
|
|
72
|
-
workerPool.shutdown();
|
|
73
|
-
}
|
|
57
|
+
let [graph, error] = await this._internal.buildAssetGraph();
|
|
74
58
|
|
|
75
59
|
if (error !== null) {
|
|
76
60
|
throw new ThrowableDiagnostic({
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {NapiWorkerPool as INapiWorkerPool} from '@atlaspack/types';
|
|
3
|
+
import {Worker} from 'worker_threads';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import process from 'process';
|
|
6
|
+
import type {Transferable} from '@atlaspack/rust';
|
|
7
|
+
import {getAvailableThreads} from '@atlaspack/rust';
|
|
8
|
+
|
|
9
|
+
const WORKER_PATH = path.join(__dirname, 'worker', 'index.js');
|
|
10
|
+
const ATLASPACK_NAPI_WORKERS =
|
|
11
|
+
process.env.ATLASPACK_NAPI_WORKERS &&
|
|
12
|
+
parseInt(process.env.ATLASPACK_NAPI_WORKERS, 10);
|
|
13
|
+
|
|
14
|
+
export type NapiWorkerPoolOptions = {|
|
|
15
|
+
workerCount?: number,
|
|
16
|
+
|};
|
|
17
|
+
|
|
18
|
+
export class NapiWorkerPool implements INapiWorkerPool {
|
|
19
|
+
#workers: Worker[];
|
|
20
|
+
#napiWorkers: Array<Promise<Transferable>>;
|
|
21
|
+
#workerCount: number;
|
|
22
|
+
|
|
23
|
+
constructor({workerCount}: NapiWorkerPoolOptions = {workerCount: undefined}) {
|
|
24
|
+
this.#workerCount =
|
|
25
|
+
workerCount ?? ATLASPACK_NAPI_WORKERS ?? getAvailableThreads();
|
|
26
|
+
if (!this.#workerCount) {
|
|
27
|
+
// TODO use main thread if workerCount is 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.#workers = [];
|
|
31
|
+
this.#napiWorkers = [];
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < this.#workerCount; i++) {
|
|
34
|
+
let worker = new Worker(WORKER_PATH);
|
|
35
|
+
this.#workers.push(worker);
|
|
36
|
+
this.#napiWorkers.push(new Promise((res) => worker.once('message', res)));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
workerCount(): number {
|
|
41
|
+
return this.#workerCount;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getWorkers(): Promise<Array<Transferable>> {
|
|
45
|
+
return Promise.all(this.#napiWorkers);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
shutdown(): void {
|
|
49
|
+
for (const worker of this.#workers) {
|
|
50
|
+
worker.terminate();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -299,28 +299,10 @@ export class AtlaspackWorker {
|
|
|
299
299
|
);
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
// Create napi worker and send it back to main thread
|
|
302
303
|
const worker = new AtlaspackWorker();
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
napi.registerWorker(event.tx_worker, worker);
|
|
307
|
-
} catch (err) {
|
|
308
|
-
// eslint-disable-next-line no-console
|
|
309
|
-
console.error(
|
|
310
|
-
'Registering worker failed... This might mean atlaspack is getting shut-down before the worker registered',
|
|
311
|
-
err,
|
|
312
|
-
);
|
|
313
|
-
parentPort?.postMessage({type: 'workerError', error: err});
|
|
314
|
-
}
|
|
315
|
-
parentPort?.postMessage({type: 'workerRegistered'});
|
|
316
|
-
} else if (event.type === 'probeStatus') {
|
|
317
|
-
parentPort.postMessage({
|
|
318
|
-
type: 'status',
|
|
319
|
-
status: 'ok',
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
parentPort?.postMessage({type: 'workerLoaded'});
|
|
304
|
+
const napiWorker = napi.newNodejsWorker(worker);
|
|
305
|
+
parentPort?.postMessage(napiWorker);
|
|
324
306
|
|
|
325
307
|
type ResolverState<T> = {|
|
|
326
308
|
resolver: Resolver<T>,
|
|
@@ -1,171 +0,0 @@
|
|
|
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 _promises() {
|
|
17
|
-
const data = require("timers/promises");
|
|
18
|
-
_promises = function () {
|
|
19
|
-
return data;
|
|
20
|
-
};
|
|
21
|
-
return data;
|
|
22
|
-
}
|
|
23
|
-
function _worker_threads() {
|
|
24
|
-
const data = require("worker_threads");
|
|
25
|
-
_worker_threads = function () {
|
|
26
|
-
return data;
|
|
27
|
-
};
|
|
28
|
-
return data;
|
|
29
|
-
}
|
|
30
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
31
|
-
/* eslint-disable no-console */
|
|
32
|
-
/*!
|
|
33
|
-
* Atlaspack V3 delegates work to node.js worker threads.
|
|
34
|
-
*
|
|
35
|
-
* Starting-up each worker is relatively expensive, in particular when atlaspack
|
|
36
|
-
* is running in development mode, in which case each worker will transpile the
|
|
37
|
-
* project on startup.
|
|
38
|
-
*
|
|
39
|
-
* This "WorkerPool" mitigates this problem by reusing worker threads across
|
|
40
|
-
* builds.
|
|
41
|
-
*/
|
|
42
|
-
// $FlowFixMe Missing types
|
|
43
|
-
const WORKER_PATH = _path().default.join(__dirname, 'worker', 'index.js');
|
|
44
|
-
function waitForMessage(worker, type, signal) {
|
|
45
|
-
return new Promise(resolve => {
|
|
46
|
-
const onMessage = message => {
|
|
47
|
-
if (message.type === type) {
|
|
48
|
-
resolve(message);
|
|
49
|
-
worker.off('message', onMessage);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
worker.on('message', onMessage);
|
|
53
|
-
const onAbort = () => {
|
|
54
|
-
signal.removeEventListener('abort', onAbort);
|
|
55
|
-
worker.off('message', onMessage);
|
|
56
|
-
};
|
|
57
|
-
signal.addEventListener('abort', onAbort);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
class WorkerPool {
|
|
61
|
-
#workerPool = [];
|
|
62
|
-
#usedWorkers = new Set();
|
|
63
|
-
#workerPath;
|
|
64
|
-
constructor(workerPath = WORKER_PATH) {
|
|
65
|
-
this.#workerPath = workerPath;
|
|
66
|
-
|
|
67
|
-
// $FlowFixMe
|
|
68
|
-
this[Symbol.dispose] = () => {
|
|
69
|
-
this.shutdown();
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Find a worker thread that is free to use or create a new one.
|
|
75
|
-
*
|
|
76
|
-
* Then register the `tx_worker` channel ID with the worker thread.
|
|
77
|
-
*/
|
|
78
|
-
registerWorker(tx_worker) {
|
|
79
|
-
const availableIndex = this.#workerPool.findIndex((worker, index) => !this.#usedWorkers.has(index));
|
|
80
|
-
const [workerId, worker] = availableIndex !== -1 ? [availableIndex, this.#workerPool[availableIndex]] : this.#createWorker();
|
|
81
|
-
this.#bootWorker(worker, tx_worker).catch(err => {
|
|
82
|
-
// eslint-disable-next-line no-console
|
|
83
|
-
console.error('Worker failed, retrying to create it...', err);
|
|
84
|
-
this.#workerPool[workerId] = new (_worker_threads().Worker)(this.#workerPath, {
|
|
85
|
-
workerData: {
|
|
86
|
-
attempt: 2
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
this.#bootWorker(this.#workerPool[workerId], tx_worker).catch(err => {
|
|
90
|
-
console.error('Worker failed to start, the build may hang:', err);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
this.#usedWorkers.add(workerId);
|
|
94
|
-
return workerId;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Release a set of workers back into the pool for re-use
|
|
99
|
-
*/
|
|
100
|
-
releaseWorkers(ids) {
|
|
101
|
-
for (let id of ids) {
|
|
102
|
-
this.#usedWorkers.delete(id);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Terminate all worker threads and reset state.
|
|
108
|
-
*/
|
|
109
|
-
shutdown() {
|
|
110
|
-
for (let worker of this.#workerPool) {
|
|
111
|
-
worker.terminate();
|
|
112
|
-
}
|
|
113
|
-
this.#usedWorkers.clear();
|
|
114
|
-
this.#workerPool = [];
|
|
115
|
-
}
|
|
116
|
-
getStats() {
|
|
117
|
-
return {
|
|
118
|
-
totalWorkers: this.#workerPool.length,
|
|
119
|
-
workersInUse: this.#usedWorkers.size
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get the worker thread. Used for testing.
|
|
125
|
-
*/
|
|
126
|
-
getWorker(workerId) {
|
|
127
|
-
if (!this.#workerPool[workerId]) {
|
|
128
|
-
throw new Error('Worker does not exist');
|
|
129
|
-
}
|
|
130
|
-
return this.#workerPool[workerId];
|
|
131
|
-
}
|
|
132
|
-
async #bootWorker(worker, tx_worker) {
|
|
133
|
-
const controller = new AbortController();
|
|
134
|
-
const signal = controller.signal;
|
|
135
|
-
const workerError = new Promise((_, reject) => {
|
|
136
|
-
const onError = err => {
|
|
137
|
-
reject(err);
|
|
138
|
-
};
|
|
139
|
-
worker.once('error', onError);
|
|
140
|
-
const onAbort = () => {
|
|
141
|
-
signal.removeEventListener('abort', onAbort);
|
|
142
|
-
worker.off('error', onError);
|
|
143
|
-
};
|
|
144
|
-
signal.addEventListener('abort', onAbort);
|
|
145
|
-
});
|
|
146
|
-
const workerReady = waitForMessage(worker, 'workerRegistered', signal);
|
|
147
|
-
worker.postMessage({
|
|
148
|
-
type: 'registerWorker',
|
|
149
|
-
tx_worker
|
|
150
|
-
});
|
|
151
|
-
try {
|
|
152
|
-
await Promise.race([(0, _promises().setTimeout)(5000, {
|
|
153
|
-
signal
|
|
154
|
-
}), workerError, workerReady]);
|
|
155
|
-
} finally {
|
|
156
|
-
controller.abort();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
#createWorker() {
|
|
160
|
-
const worker = new (_worker_threads().Worker)(this.#workerPath, {
|
|
161
|
-
workerData: {
|
|
162
|
-
attempt: 1
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
const workerId = this.#workerPool.length;
|
|
166
|
-
this.#workerPool.push(worker);
|
|
167
|
-
return [workerId, worker];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
exports.WorkerPool = WorkerPool;
|
|
171
|
-
const workerPool = exports.workerPool = new WorkerPool();
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
// @flow strict-local
|
|
2
|
-
|
|
3
|
-
/* eslint-disable no-console */
|
|
4
|
-
|
|
5
|
-
/*!
|
|
6
|
-
* Atlaspack V3 delegates work to node.js worker threads.
|
|
7
|
-
*
|
|
8
|
-
* Starting-up each worker is relatively expensive, in particular when atlaspack
|
|
9
|
-
* is running in development mode, in which case each worker will transpile the
|
|
10
|
-
* project on startup.
|
|
11
|
-
*
|
|
12
|
-
* This "WorkerPool" mitigates this problem by reusing worker threads across
|
|
13
|
-
* builds.
|
|
14
|
-
*/
|
|
15
|
-
import path from 'path';
|
|
16
|
-
// $FlowFixMe Missing types
|
|
17
|
-
import {setTimeout} from 'timers/promises';
|
|
18
|
-
import {Worker} from 'worker_threads';
|
|
19
|
-
|
|
20
|
-
const WORKER_PATH = path.join(__dirname, 'worker', 'index.js');
|
|
21
|
-
|
|
22
|
-
export function waitForMessage<T>(
|
|
23
|
-
worker: Worker,
|
|
24
|
-
type: string,
|
|
25
|
-
signal: AbortSignal,
|
|
26
|
-
): Promise<T> {
|
|
27
|
-
return new Promise((resolve) => {
|
|
28
|
-
const onMessage = (message: T & {|type: string|}) => {
|
|
29
|
-
if (message.type === type) {
|
|
30
|
-
resolve(message);
|
|
31
|
-
worker.off('message', onMessage);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
worker.on('message', onMessage);
|
|
36
|
-
|
|
37
|
-
const onAbort = () => {
|
|
38
|
-
signal.removeEventListener('abort', onAbort);
|
|
39
|
-
worker.off('message', onMessage);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
signal.addEventListener('abort', onAbort);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class WorkerPool {
|
|
47
|
-
#workerPool: Worker[] = [];
|
|
48
|
-
#usedWorkers: Set<number> = new Set();
|
|
49
|
-
#workerPath: string;
|
|
50
|
-
|
|
51
|
-
constructor(workerPath: string = WORKER_PATH) {
|
|
52
|
-
this.#workerPath = workerPath;
|
|
53
|
-
|
|
54
|
-
// $FlowFixMe
|
|
55
|
-
this[Symbol.dispose] = () => {
|
|
56
|
-
this.shutdown();
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Find a worker thread that is free to use or create a new one.
|
|
62
|
-
*
|
|
63
|
-
* Then register the `tx_worker` channel ID with the worker thread.
|
|
64
|
-
*/
|
|
65
|
-
registerWorker(tx_worker: number): number {
|
|
66
|
-
const availableIndex = this.#workerPool.findIndex(
|
|
67
|
-
(worker, index) => !this.#usedWorkers.has(index),
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const [workerId, worker] =
|
|
71
|
-
availableIndex !== -1
|
|
72
|
-
? [availableIndex, this.#workerPool[availableIndex]]
|
|
73
|
-
: this.#createWorker();
|
|
74
|
-
|
|
75
|
-
this.#bootWorker(worker, tx_worker).catch((err) => {
|
|
76
|
-
// eslint-disable-next-line no-console
|
|
77
|
-
console.error('Worker failed, retrying to create it...', err);
|
|
78
|
-
this.#workerPool[workerId] = new Worker(this.#workerPath, {
|
|
79
|
-
workerData: {attempt: 2},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
this.#bootWorker(this.#workerPool[workerId], tx_worker).catch((err) => {
|
|
83
|
-
console.error('Worker failed to start, the build may hang:', err);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
this.#usedWorkers.add(workerId);
|
|
88
|
-
|
|
89
|
-
return workerId;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Release a set of workers back into the pool for re-use
|
|
94
|
-
*/
|
|
95
|
-
releaseWorkers(ids: number[]) {
|
|
96
|
-
for (let id of ids) {
|
|
97
|
-
this.#usedWorkers.delete(id);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Terminate all worker threads and reset state.
|
|
103
|
-
*/
|
|
104
|
-
shutdown() {
|
|
105
|
-
for (let worker of this.#workerPool) {
|
|
106
|
-
worker.terminate();
|
|
107
|
-
}
|
|
108
|
-
this.#usedWorkers.clear();
|
|
109
|
-
this.#workerPool = [];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
getStats(): {|totalWorkers: number, workersInUse: number|} {
|
|
113
|
-
return {
|
|
114
|
-
totalWorkers: this.#workerPool.length,
|
|
115
|
-
workersInUse: this.#usedWorkers.size,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get the worker thread. Used for testing.
|
|
121
|
-
*/
|
|
122
|
-
getWorker(workerId: number): Worker {
|
|
123
|
-
if (!this.#workerPool[workerId]) {
|
|
124
|
-
throw new Error('Worker does not exist');
|
|
125
|
-
}
|
|
126
|
-
return this.#workerPool[workerId];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async #bootWorker(worker: Worker, tx_worker: number): Promise<void> {
|
|
130
|
-
const controller = new AbortController();
|
|
131
|
-
const signal = controller.signal;
|
|
132
|
-
|
|
133
|
-
const workerError = new Promise((_, reject) => {
|
|
134
|
-
const onError = (err: Error) => {
|
|
135
|
-
reject(err);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
worker.once('error', onError);
|
|
139
|
-
|
|
140
|
-
const onAbort = () => {
|
|
141
|
-
signal.removeEventListener('abort', onAbort);
|
|
142
|
-
worker.off('error', onError);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
signal.addEventListener('abort', onAbort);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const workerReady = waitForMessage(worker, 'workerRegistered', signal);
|
|
149
|
-
|
|
150
|
-
worker.postMessage({type: 'registerWorker', tx_worker});
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
await Promise.race([
|
|
154
|
-
setTimeout(5000, {signal}),
|
|
155
|
-
workerError,
|
|
156
|
-
workerReady,
|
|
157
|
-
]);
|
|
158
|
-
} finally {
|
|
159
|
-
controller.abort();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
#createWorker(): [number, Worker] {
|
|
164
|
-
const worker = new Worker(this.#workerPath, {workerData: {attempt: 1}});
|
|
165
|
-
const workerId = this.#workerPool.length;
|
|
166
|
-
this.#workerPool.push(worker);
|
|
167
|
-
return [workerId, worker];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export const workerPool: WorkerPool = new WorkerPool();
|
|
@@ -1,147 +0,0 @@
|
|
|
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(
|
|
10
|
-
worker,
|
|
11
|
-
'status',
|
|
12
|
-
new AbortController().signal,
|
|
13
|
-
);
|
|
14
|
-
worker.postMessage({type: 'probeStatus'});
|
|
15
|
-
return response;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
|
|
19
|
-
|
|
20
|
-
describe('WorkerPool', () => {
|
|
21
|
-
it('can create workers and will send them the tx_worker value', async () => {
|
|
22
|
-
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
23
|
-
const workerId = workerPool.registerWorker(0);
|
|
24
|
-
const worker = workerPool.getWorker(workerId);
|
|
25
|
-
const status = await probeStatus(worker);
|
|
26
|
-
|
|
27
|
-
assert.deepEqual(status, {
|
|
28
|
-
type: 'status',
|
|
29
|
-
status: 'test-status-ok',
|
|
30
|
-
receivedMessages: [
|
|
31
|
-
{
|
|
32
|
-
type: 'registerWorker',
|
|
33
|
-
tx_worker: 0,
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('recreates a failing worker', async () => {
|
|
40
|
-
const workerPool = new WorkerPool(path.join(__dirname, 'worker-error.js'));
|
|
41
|
-
const workerId = workerPool.registerWorker(0);
|
|
42
|
-
const failingWorker = workerPool.getWorker(workerId);
|
|
43
|
-
|
|
44
|
-
await new Promise((resolve) => {
|
|
45
|
-
failingWorker.on('error', resolve);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
await flushPromises();
|
|
49
|
-
|
|
50
|
-
const worker = workerPool.getWorker(workerId);
|
|
51
|
-
const status = await probeStatus(worker);
|
|
52
|
-
|
|
53
|
-
assert.deepEqual(status, {
|
|
54
|
-
type: 'status',
|
|
55
|
-
status: 'ok',
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('when a worker is created, it is tracked', () => {
|
|
60
|
-
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
61
|
-
const w1 = workerPool.registerWorker(0);
|
|
62
|
-
const w2 = workerPool.registerWorker(0);
|
|
63
|
-
const w3 = workerPool.registerWorker(0);
|
|
64
|
-
assert.notEqual(w1, w2);
|
|
65
|
-
assert.notEqual(w2, w3);
|
|
66
|
-
assert.notEqual(w1, w3);
|
|
67
|
-
|
|
68
|
-
assert.deepEqual(workerPool.getStats(), {
|
|
69
|
-
totalWorkers: 3,
|
|
70
|
-
workersInUse: 3,
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('workers can be released and will then be reused', async () => {
|
|
75
|
-
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
76
|
-
const worker1 = workerPool.registerWorker(0);
|
|
77
|
-
workerPool.registerWorker(5);
|
|
78
|
-
assert.deepEqual(workerPool.getStats(), {
|
|
79
|
-
totalWorkers: 2,
|
|
80
|
-
workersInUse: 2,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Release the worker
|
|
84
|
-
workerPool.releaseWorkers([worker1]);
|
|
85
|
-
assert.deepEqual(workerPool.getStats(), {
|
|
86
|
-
totalWorkers: 2,
|
|
87
|
-
workersInUse: 1,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const worker3 = workerPool.registerWorker(33);
|
|
91
|
-
assert.equal(worker1, worker3);
|
|
92
|
-
|
|
93
|
-
const worker = workerPool.getWorker(worker3);
|
|
94
|
-
const status = await probeStatus(worker);
|
|
95
|
-
assert.deepEqual(status, {
|
|
96
|
-
type: 'status',
|
|
97
|
-
status: 'test-status-ok',
|
|
98
|
-
receivedMessages: [
|
|
99
|
-
{
|
|
100
|
-
type: 'registerWorker',
|
|
101
|
-
tx_worker: 0,
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
type: 'registerWorker',
|
|
105
|
-
tx_worker: 33,
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('shutdown', () => {
|
|
112
|
-
it('terminates all workers', async () => {
|
|
113
|
-
const workerPool = new WorkerPool(path.join(__dirname, 'worker.js'));
|
|
114
|
-
const worker1Id = workerPool.registerWorker(0);
|
|
115
|
-
const worker2Id = workerPool.registerWorker(0);
|
|
116
|
-
const worker1 = workerPool.getWorker(worker1Id);
|
|
117
|
-
const worker2 = workerPool.getWorker(worker2Id);
|
|
118
|
-
|
|
119
|
-
const worker1Exit = new Promise((resolve) => {
|
|
120
|
-
worker1.on('exit', () => {
|
|
121
|
-
resolve(null);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
const worker2Exit = new Promise((resolve) => {
|
|
125
|
-
worker2.on('exit', () => {
|
|
126
|
-
resolve(null);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
workerPool.shutdown();
|
|
131
|
-
assert.throws(() => {
|
|
132
|
-
workerPool.getWorker(worker1Id);
|
|
133
|
-
});
|
|
134
|
-
assert.throws(() => {
|
|
135
|
-
workerPool.getWorker(worker2Id);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
await worker1Exit;
|
|
139
|
-
await worker2Exit;
|
|
140
|
-
|
|
141
|
-
assert.deepEqual(workerPool.getStats(), {
|
|
142
|
-
totalWorkers: 0,
|
|
143
|
-
workersInUse: 0,
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
});
|