threadsx 2.0.2 → 2.2.0
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/dist/errors.d.ts +16 -0
- package/dist/errors.js +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/master/implementation.node.js +34 -18
- package/dist/master/invocation-proxy.js +45 -11
- package/dist/master/pool.d.ts +1 -24
- package/dist/master/pool.js +41 -8
- package/dist/master/spawn.js +24 -7
- package/dist/node-require.d.ts +13 -0
- package/dist/node-require.js +87 -0
- package/dist/transferable.d.ts +1 -0
- package/dist/types/master.d.ts +22 -1
- package/dist/types/master.js +0 -1
- package/dist/types/worker.d.ts +1 -0
- package/dist/worker/implementation.browser.d.ts +1 -1
- package/dist/worker/implementation.worker_threads.d.ts +1 -1
- package/dist/worker_threads.d.ts +3 -7
- package/dist/worker_threads.js +35 -11
- package/dist-esm/common.js +1 -0
- package/dist-esm/common.js.map +1 -0
- package/dist-esm/errors.js +27 -0
- package/dist-esm/errors.js.map +1 -0
- package/dist-esm/index.js +2 -0
- package/dist-esm/index.js.map +1 -0
- package/dist-esm/master/get-bundle-url.browser.js +1 -0
- package/dist-esm/master/get-bundle-url.browser.js.map +1 -0
- package/dist-esm/master/implementation.browser.js +1 -0
- package/dist-esm/master/implementation.browser.js.map +1 -0
- package/dist-esm/master/implementation.js +1 -0
- package/dist-esm/master/implementation.js.map +1 -0
- package/dist-esm/master/implementation.node.js +35 -18
- package/dist-esm/master/implementation.node.js.map +1 -0
- package/dist-esm/master/index.js +1 -0
- package/dist-esm/master/index.js.map +1 -0
- package/dist-esm/master/invocation-proxy.js +46 -11
- package/dist-esm/master/invocation-proxy.js.map +1 -0
- package/dist-esm/master/pool-types.js +1 -0
- package/dist-esm/master/pool-types.js.map +1 -0
- package/dist-esm/master/pool.js +42 -8
- package/dist-esm/master/pool.js.map +1 -0
- package/dist-esm/master/register.js +1 -0
- package/dist-esm/master/register.js.map +1 -0
- package/dist-esm/master/spawn.js +25 -7
- package/dist-esm/master/spawn.js.map +1 -0
- package/dist-esm/master/thread.js +1 -0
- package/dist-esm/master/thread.js.map +1 -0
- package/dist-esm/node-require.js +50 -0
- package/dist-esm/node-require.js.map +1 -0
- package/dist-esm/observable-promise.js +1 -0
- package/dist-esm/observable-promise.js.map +1 -0
- package/dist-esm/observable.js +1 -0
- package/dist-esm/observable.js.map +1 -0
- package/dist-esm/ponyfills.js +1 -0
- package/dist-esm/ponyfills.js.map +1 -0
- package/dist-esm/promise.js +1 -0
- package/dist-esm/promise.js.map +1 -0
- package/dist-esm/serializers.js +1 -0
- package/dist-esm/serializers.js.map +1 -0
- package/dist-esm/symbols.js +1 -0
- package/dist-esm/symbols.js.map +1 -0
- package/dist-esm/transferable.js +1 -0
- package/dist-esm/transferable.js.map +1 -0
- package/dist-esm/types/master.js +1 -1
- package/dist-esm/types/master.js.map +1 -0
- package/dist-esm/types/messages.js +1 -0
- package/dist-esm/types/messages.js.map +1 -0
- package/dist-esm/types/worker.js +1 -0
- package/dist-esm/types/worker.js.map +1 -0
- package/dist-esm/worker/bundle-entry.js +1 -0
- package/dist-esm/worker/bundle-entry.js.map +1 -0
- package/dist-esm/worker/implementation.browser.js +1 -0
- package/dist-esm/worker/implementation.browser.js.map +1 -0
- package/dist-esm/worker/implementation.js +1 -0
- package/dist-esm/worker/implementation.js.map +1 -0
- package/dist-esm/worker/implementation.worker_threads.js +1 -0
- package/dist-esm/worker/implementation.worker_threads.js.map +1 -0
- package/dist-esm/worker/index.js +1 -0
- package/dist-esm/worker/index.js.map +1 -0
- package/dist-esm/worker_threads.js +3 -11
- package/dist-esm/worker_threads.js.map +1 -0
- package/esm/common.mjs +20 -0
- package/esm/errors.mjs +34 -0
- package/esm/index.mjs +16 -0
- package/esm/master/get-bundle-url.browser.mjs +32 -0
- package/esm/master/implementation.browser.mjs +76 -0
- package/esm/master/implementation.mjs +24 -0
- package/esm/master/implementation.node.mjs +186 -0
- package/esm/master/index.mjs +17 -0
- package/esm/master/invocation-proxy.mjs +164 -0
- package/esm/master/pool-types.mjs +20 -0
- package/esm/master/pool.mjs +324 -0
- package/esm/master/register.mjs +14 -0
- package/esm/master/spawn.mjs +179 -0
- package/esm/master/thread.mjs +28 -0
- package/esm/node-require.mjs +58 -0
- package/esm/observable-promise.mjs +155 -0
- package/esm/observable.mjs +45 -0
- package/esm/ponyfills.mjs +26 -0
- package/esm/promise.mjs +32 -0
- package/esm/serializers.mjs +55 -0
- package/esm/symbols.mjs +13 -0
- package/esm/transferable.mjs +31 -0
- package/esm/types/master.mjs +15 -0
- package/esm/types/messages.mjs +25 -0
- package/esm/worker/implementation.browser.mjs +31 -0
- package/esm/worker/implementation.mjs +21 -0
- package/esm/worker/implementation.worker_threads.mjs +44 -0
- package/esm/worker/index.mjs +224 -0
- package/esm/worker_threads.mjs +13 -0
- package/package.json +32 -24
- package/index.mjs +0 -4
- package/observable.mjs +0 -1
- package/register.mjs +0 -2
- package/worker.mjs +0 -1
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when a value passed to a thread cannot be cloned by the structured
|
|
3
|
+
* clone algorithm (e.g. functions, class instances with methods, or other
|
|
4
|
+
* non-serializable values). Wraps the underlying `DataCloneError` / `DOMException`
|
|
5
|
+
* with an actionable message. Access the original error via `.cause`.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ThreadCloneError extends Error {
|
|
8
|
+
readonly cause?: unknown;
|
|
9
|
+
constructor(message: string, cause?: unknown);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether an error is a structured-clone failure. Both browsers and Node's
|
|
13
|
+
* `worker_threads` throw a `DOMException`/error named `"DataCloneError"` when a
|
|
14
|
+
* value cannot be cloned across the thread boundary.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isDataCloneError(error: unknown): boolean;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ThreadCloneError = void 0;
|
|
4
|
+
exports.isDataCloneError = isDataCloneError;
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a value passed to a thread cannot be cloned by the structured
|
|
7
|
+
* clone algorithm (e.g. functions, class instances with methods, or other
|
|
8
|
+
* non-serializable values). Wraps the underlying `DataCloneError` / `DOMException`
|
|
9
|
+
* with an actionable message. Access the original error via `.cause`.
|
|
10
|
+
*/
|
|
11
|
+
class ThreadCloneError extends Error {
|
|
12
|
+
constructor(message, cause) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "ThreadCloneError";
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
// Restore the prototype chain — extending built-ins breaks `instanceof`
|
|
17
|
+
// when compiled down to ES2015 without this.
|
|
18
|
+
Object.setPrototypeOf(this, ThreadCloneError.prototype);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.ThreadCloneError = ThreadCloneError;
|
|
22
|
+
/**
|
|
23
|
+
* Whether an error is a structured-clone failure. Both browsers and Node's
|
|
24
|
+
* `worker_threads` throw a `DOMException`/error named `"DataCloneError"` when a
|
|
25
|
+
* value cannot be cloned across the thread boundary.
|
|
26
|
+
*/
|
|
27
|
+
function isDataCloneError(error) {
|
|
28
|
+
return Boolean(error &&
|
|
29
|
+
typeof error === "object" &&
|
|
30
|
+
error.name === "DataCloneError");
|
|
31
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { registerSerializer } from "./common";
|
|
2
|
+
export { ThreadCloneError } from "./errors";
|
|
2
3
|
export * from "./master/index";
|
|
3
4
|
export { expose } from "./worker/index";
|
|
4
5
|
export { DefaultSerializer, JsonSerializable, Serializer, SerializerImplementation } from "./serializers";
|
package/dist/index.js
CHANGED
|
@@ -14,9 +14,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.Transfer = exports.DefaultSerializer = exports.expose = exports.registerSerializer = void 0;
|
|
17
|
+
exports.Transfer = exports.DefaultSerializer = exports.expose = exports.ThreadCloneError = exports.registerSerializer = void 0;
|
|
18
18
|
var common_1 = require("./common");
|
|
19
19
|
Object.defineProperty(exports, "registerSerializer", { enumerable: true, get: function () { return common_1.registerSerializer; } });
|
|
20
|
+
var errors_1 = require("./errors");
|
|
21
|
+
Object.defineProperty(exports, "ThreadCloneError", { enumerable: true, get: function () { return errors_1.ThreadCloneError; } });
|
|
20
22
|
__exportStar(require("./master/index"), exports);
|
|
21
23
|
var index_1 = require("./worker/index");
|
|
22
24
|
Object.defineProperty(exports, "expose", { enumerable: true, get: function () { return index_1.expose; } });
|
|
@@ -40,18 +40,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
40
40
|
exports.defaultPoolSize = void 0;
|
|
41
41
|
exports.getWorkerImplementation = getWorkerImplementation;
|
|
42
42
|
exports.isWorkerRuntime = isWorkerRuntime;
|
|
43
|
+
// NOTE: `callsites` is intentionally pinned to ^3. v4+ is ESM-only and would
|
|
44
|
+
// break the CommonJS (dist/) build. Do not bump it to v4 without moving the
|
|
45
|
+
// package to ESM-first output. (See .github/dependabot.yml, which also ignores
|
|
46
|
+
// its major updates.)
|
|
43
47
|
const callsites_1 = __importDefault(require("callsites"));
|
|
44
48
|
const os_1 = require("os");
|
|
45
49
|
const path = __importStar(require("path"));
|
|
46
50
|
const url_1 = require("url");
|
|
51
|
+
const node_require_1 = require("../node-require");
|
|
52
|
+
const worker_threads_1 = __importDefault(require("../worker_threads"));
|
|
47
53
|
let detectedTsRuntime;
|
|
48
54
|
exports.defaultPoolSize = (0, os_1.cpus)().length;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the application opted out of threadsx installing SIGINT/SIGTERM
|
|
57
|
+
* handlers (which call `process.exit()`), via `THREADS_SKIP_SIGNAL_HANDLERS`.
|
|
58
|
+
*/
|
|
59
|
+
function signalHandlersDisabled() {
|
|
60
|
+
const value = typeof process !== "undefined" && process.env
|
|
61
|
+
? process.env.THREADS_SKIP_SIGNAL_HANDLERS
|
|
62
|
+
: undefined;
|
|
63
|
+
return value === "1" || value === "true";
|
|
64
|
+
}
|
|
49
65
|
/**
|
|
50
66
|
* Detects an available TypeScript runtime so `.ts`/`.tsx` worker files can be
|
|
51
67
|
* spawned directly during development. Prefers `tsx`, falls back to `ts-node`.
|
|
52
68
|
*/
|
|
53
69
|
function detectTsRuntime() {
|
|
54
|
-
if (
|
|
70
|
+
if ((0, node_require_1.isWebpackBundle)()) {
|
|
55
71
|
// Webpack build: => No TS runtime required or possible
|
|
56
72
|
return null;
|
|
57
73
|
}
|
|
@@ -61,7 +77,7 @@ function detectTsRuntime() {
|
|
|
61
77
|
detectedTsRuntime = null;
|
|
62
78
|
for (const candidate of ["tsx", "ts-node"]) {
|
|
63
79
|
try {
|
|
64
|
-
|
|
80
|
+
(0, node_require_1.getNodeRequire)().resolve(candidate);
|
|
65
81
|
detectedTsRuntime = candidate;
|
|
66
82
|
break;
|
|
67
83
|
}
|
|
@@ -109,19 +125,17 @@ function rebaseScriptPath(scriptPath, ignoreRegex) {
|
|
|
109
125
|
}
|
|
110
126
|
function resolveScriptPath(scriptPath, baseURL) {
|
|
111
127
|
const makeRelative = (filePath) => {
|
|
112
|
-
|
|
113
|
-
return path.isAbsolute(filePath) ? filePath : path.join(baseURL || eval("__dirname"), filePath);
|
|
128
|
+
return path.isAbsolute(filePath) ? filePath : path.join(baseURL || (0, node_require_1.getModuleDirname)(), filePath);
|
|
114
129
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
// In a webpack bundle, walking the call stack is useless (every caller lives
|
|
131
|
+
// inside the bundle), so relative paths resolve without rebasing.
|
|
132
|
+
const workerFilePath = (0, node_require_1.isWebpackBundle)()
|
|
133
|
+
? (0, node_require_1.getNodeRequire)().resolve(makeRelative(scriptPath))
|
|
134
|
+
: (0, node_require_1.getNodeRequire)().resolve(makeRelative(rebaseScriptPath(scriptPath, /[/\\]worker_threads[/\\]/)));
|
|
118
135
|
return workerFilePath;
|
|
119
136
|
}
|
|
120
137
|
function initWorkerThreadsWorker() {
|
|
121
|
-
|
|
122
|
-
const NativeWorker = typeof __non_webpack_require__ === "function"
|
|
123
|
-
? __non_webpack_require__("worker_threads").Worker
|
|
124
|
-
: eval("require")("worker_threads").Worker;
|
|
138
|
+
const NativeWorker = (0, worker_threads_1.default)().Worker;
|
|
125
139
|
let allWorkers = [];
|
|
126
140
|
class Worker extends NativeWorker {
|
|
127
141
|
constructor(scriptPath, options) {
|
|
@@ -168,8 +182,14 @@ function initWorkerThreadsWorker() {
|
|
|
168
182
|
allWorkers = [];
|
|
169
183
|
};
|
|
170
184
|
// Take care to not leave orphaned processes behind. See #147.
|
|
171
|
-
|
|
172
|
-
process.
|
|
185
|
+
//
|
|
186
|
+
// These handlers call process.exit(), which hijacks the host application's
|
|
187
|
+
// own shutdown. Applications that manage their own graceful shutdown can opt
|
|
188
|
+
// out by setting THREADS_SKIP_SIGNAL_HANDLERS. See upstream #388 / #484.
|
|
189
|
+
if (!signalHandlersDisabled()) {
|
|
190
|
+
process.on("SIGINT", () => terminateWorkersAndMaster());
|
|
191
|
+
process.on("SIGTERM", () => terminateWorkersAndMaster());
|
|
192
|
+
}
|
|
173
193
|
class BlobWorker extends Worker {
|
|
174
194
|
constructor(blob, options) {
|
|
175
195
|
super(Buffer.from(blob).toString("utf-8"), Object.assign(Object.assign({}, options), { fromSource: true }));
|
|
@@ -194,9 +214,5 @@ function getWorkerImplementation() {
|
|
|
194
214
|
return implementation;
|
|
195
215
|
}
|
|
196
216
|
function isWorkerRuntime() {
|
|
197
|
-
|
|
198
|
-
const isMainThread = typeof __non_webpack_require__ === "function"
|
|
199
|
-
? __non_webpack_require__("worker_threads").isMainThread
|
|
200
|
-
: eval("require")("worker_threads").isMainThread;
|
|
201
|
-
return !isMainThread;
|
|
217
|
+
return !(0, worker_threads_1.default)().isMainThread;
|
|
202
218
|
}
|
|
@@ -14,6 +14,7 @@ exports.createProxyModule = createProxyModule;
|
|
|
14
14
|
const debug_1 = __importDefault(require("debug"));
|
|
15
15
|
const observable_fns_1 = require("observable-fns");
|
|
16
16
|
const common_1 = require("../common");
|
|
17
|
+
const errors_1 = require("../errors");
|
|
17
18
|
const observable_promise_1 = require("../observable-promise");
|
|
18
19
|
const transferable_1 = require("../transferable");
|
|
19
20
|
const messages_1 = require("../types/messages");
|
|
@@ -26,6 +27,12 @@ const isJobStartMessage = (data) => data && data.type === messages_1.WorkerMessa
|
|
|
26
27
|
function createObservableForJob(worker, jobUID) {
|
|
27
28
|
return new observable_fns_1.Observable(observer => {
|
|
28
29
|
let asyncType;
|
|
30
|
+
let settled = false;
|
|
31
|
+
const cleanup = () => {
|
|
32
|
+
worker.removeEventListener("message", messageHandler);
|
|
33
|
+
worker.removeEventListener("error", errorHandler);
|
|
34
|
+
worker.removeEventListener("exit", exitHandler);
|
|
35
|
+
};
|
|
29
36
|
const messageHandler = ((event) => {
|
|
30
37
|
debugMessages("Message from worker:", event.data);
|
|
31
38
|
if (!event.data || event.data.uid !== jobUID)
|
|
@@ -38,31 +45,52 @@ function createObservableForJob(worker, jobUID) {
|
|
|
38
45
|
if (typeof event.data.payload !== "undefined") {
|
|
39
46
|
observer.next((0, common_1.deserialize)(event.data.payload));
|
|
40
47
|
}
|
|
48
|
+
settled = true;
|
|
41
49
|
observer.complete();
|
|
42
|
-
|
|
50
|
+
cleanup();
|
|
43
51
|
}
|
|
44
52
|
else {
|
|
45
|
-
if (event.data.payload) {
|
|
53
|
+
if (typeof event.data.payload !== "undefined") {
|
|
46
54
|
observer.next((0, common_1.deserialize)(event.data.payload));
|
|
47
55
|
}
|
|
48
56
|
if (event.data.complete) {
|
|
57
|
+
settled = true;
|
|
49
58
|
observer.complete();
|
|
50
|
-
|
|
59
|
+
cleanup();
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
}
|
|
54
63
|
else if (isJobErrorMessage(event.data)) {
|
|
55
64
|
const error = (0, common_1.deserialize)(event.data.error);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
else {
|
|
60
|
-
observer.error(error);
|
|
61
|
-
}
|
|
62
|
-
worker.removeEventListener("message", messageHandler);
|
|
65
|
+
settled = true;
|
|
66
|
+
observer.error(error);
|
|
67
|
+
cleanup();
|
|
63
68
|
}
|
|
64
69
|
});
|
|
70
|
+
// If the worker crashes or is terminated before the job produces a result,
|
|
71
|
+
// reject the pending job instead of leaving the promise hanging forever.
|
|
72
|
+
// See #386.
|
|
73
|
+
const errorHandler = ((event) => {
|
|
74
|
+
if (settled)
|
|
75
|
+
return;
|
|
76
|
+
settled = true;
|
|
77
|
+
const error = event && event.data instanceof Error
|
|
78
|
+
? event.data
|
|
79
|
+
: Error(String((event && event.data) || "Worker errored before the job completed."));
|
|
80
|
+
observer.error(error);
|
|
81
|
+
cleanup();
|
|
82
|
+
});
|
|
83
|
+
const exitHandler = ((event) => {
|
|
84
|
+
if (settled)
|
|
85
|
+
return;
|
|
86
|
+
settled = true;
|
|
87
|
+
const exitCode = event ? event.data : undefined;
|
|
88
|
+
observer.error(Error(`Worker terminated before the job completed (exit code: ${exitCode}).`));
|
|
89
|
+
cleanup();
|
|
90
|
+
});
|
|
65
91
|
worker.addEventListener("message", messageHandler);
|
|
92
|
+
worker.addEventListener("error", errorHandler);
|
|
93
|
+
worker.addEventListener("exit", exitHandler);
|
|
66
94
|
return () => {
|
|
67
95
|
if (asyncType === "observable" || !asyncType) {
|
|
68
96
|
const cancelMessage = {
|
|
@@ -71,7 +99,7 @@ function createObservableForJob(worker, jobUID) {
|
|
|
71
99
|
};
|
|
72
100
|
worker.postMessage(cancelMessage);
|
|
73
101
|
}
|
|
74
|
-
|
|
102
|
+
cleanup();
|
|
75
103
|
};
|
|
76
104
|
});
|
|
77
105
|
}
|
|
@@ -114,6 +142,12 @@ function createProxyFunction(worker, method) {
|
|
|
114
142
|
worker.postMessage(runMessage, transferables);
|
|
115
143
|
}
|
|
116
144
|
catch (error) {
|
|
145
|
+
if ((0, errors_1.isDataCloneError)(error)) {
|
|
146
|
+
const cloneError = new errors_1.ThreadCloneError(`Cannot send arguments to the worker thread: a value is not structured-cloneable. ` +
|
|
147
|
+
`Functions, class instances and other non-serializable values cannot be passed to a thread. ` +
|
|
148
|
+
`Original error: ${error.message}`, error);
|
|
149
|
+
return observable_promise_1.ObservablePromise.from(Promise.reject(cloneError));
|
|
150
|
+
}
|
|
117
151
|
return observable_promise_1.ObservablePromise.from(Promise.reject(error));
|
|
118
152
|
}
|
|
119
153
|
return observable_promise_1.ObservablePromise.from((0, observable_fns_1.multicast)(createObservableForJob(worker, uid)));
|
package/dist/master/pool.d.ts
CHANGED
|
@@ -54,33 +54,10 @@ export interface PoolOptions {
|
|
|
54
54
|
/** No. of worker threads to spawn and to be managed by the pool. */
|
|
55
55
|
size?: number;
|
|
56
56
|
}
|
|
57
|
-
declare class WorkerPool<ThreadType extends Thread> implements Pool<ThreadType> {
|
|
58
|
-
static EventType: typeof PoolEventType;
|
|
59
|
-
private readonly debug;
|
|
60
|
-
private readonly eventObservable;
|
|
61
|
-
private readonly options;
|
|
62
|
-
private readonly workers;
|
|
63
|
-
private readonly eventSubject;
|
|
64
|
-
private initErrors;
|
|
65
|
-
private isClosing;
|
|
66
|
-
private nextTaskID;
|
|
67
|
-
private taskQueue;
|
|
68
|
-
constructor(spawnWorker: () => Promise<ThreadType>, optionsOrSize?: number | PoolOptions);
|
|
69
|
-
private findIdlingWorker;
|
|
70
|
-
private runPoolTask;
|
|
71
|
-
private run;
|
|
72
|
-
private scheduleWork;
|
|
73
|
-
private taskCompletion;
|
|
74
|
-
settled(allowResolvingImmediately?: boolean): Promise<Error[]>;
|
|
75
|
-
completed(allowResolvingImmediately?: boolean): Promise<void>;
|
|
76
|
-
events(): Observable<PoolEvent<ThreadType>>;
|
|
77
|
-
queue(taskFunction: TaskRunFunction<ThreadType, any>): QueuedTask<ThreadType, any>;
|
|
78
|
-
terminate(force?: boolean): Promise<void>;
|
|
79
|
-
}
|
|
80
57
|
/**
|
|
81
58
|
* Thread pool constructor. Creates a new pool and spawns its worker threads.
|
|
82
59
|
*/
|
|
83
|
-
declare function PoolConstructor<ThreadType extends Thread>(spawnWorker: () => Promise<ThreadType>, optionsOrSize?: number | PoolOptions):
|
|
60
|
+
declare function PoolConstructor<ThreadType extends Thread>(spawnWorker: () => Promise<ThreadType>, optionsOrSize?: number | PoolOptions): Pool<ThreadType>;
|
|
84
61
|
/**
|
|
85
62
|
* Thread pool constructor. Creates a new pool and spawns its worker threads.
|
|
86
63
|
*/
|
package/dist/master/pool.js
CHANGED
|
@@ -147,6 +147,13 @@ class WorkerPool {
|
|
|
147
147
|
eventSubscription.unsubscribe();
|
|
148
148
|
reject(event.error);
|
|
149
149
|
}
|
|
150
|
+
else if (event.type === pool_types_1.PoolEventType.taskCanceled && event.taskID === taskID) {
|
|
151
|
+
// Without this branch a canceled task would keep its event
|
|
152
|
+
// subscription alive for the pool's whole lifetime and its promise
|
|
153
|
+
// would never settle.
|
|
154
|
+
eventSubscription.unsubscribe();
|
|
155
|
+
reject(Error("Task has been canceled."));
|
|
156
|
+
}
|
|
150
157
|
else if (event.type === pool_types_1.PoolEventType.terminated) {
|
|
151
158
|
eventSubscription.unsubscribe();
|
|
152
159
|
reject(Error("Pool has been terminated before task was run."));
|
|
@@ -164,20 +171,27 @@ class WorkerPool {
|
|
|
164
171
|
}
|
|
165
172
|
});
|
|
166
173
|
if (this.initErrors.length > 0) {
|
|
174
|
+
failureSubscription.unsubscribe();
|
|
167
175
|
return Promise.reject(this.initErrors[0]);
|
|
168
176
|
}
|
|
169
177
|
if (allowResolvingImmediately && this.taskQueue.length === 0) {
|
|
170
178
|
yield (0, ponyfills_1.allSettled)(getCurrentlyRunningTasks());
|
|
179
|
+
failureSubscription.unsubscribe();
|
|
171
180
|
return taskFailures;
|
|
172
181
|
}
|
|
173
182
|
yield new Promise((resolve, reject) => {
|
|
174
183
|
const subscription = this.eventObservable.subscribe({
|
|
175
184
|
next(event) {
|
|
176
|
-
if (event.type === pool_types_1.PoolEventType.taskQueueDrained) {
|
|
185
|
+
if (event.type === pool_types_1.PoolEventType.taskQueueDrained || event.type === pool_types_1.PoolEventType.terminated) {
|
|
177
186
|
subscription.unsubscribe();
|
|
178
187
|
resolve(void 0);
|
|
179
188
|
}
|
|
180
189
|
},
|
|
190
|
+
complete() {
|
|
191
|
+
// The pool was terminated while we were waiting; resolve instead of hanging.
|
|
192
|
+
subscription.unsubscribe();
|
|
193
|
+
resolve(void 0);
|
|
194
|
+
},
|
|
181
195
|
error: reject // make a pool-wide error reject the completed() result promise
|
|
182
196
|
});
|
|
183
197
|
});
|
|
@@ -192,7 +206,7 @@ class WorkerPool {
|
|
|
192
206
|
const earlyExitPromise = new Promise((resolve, reject) => {
|
|
193
207
|
const subscription = this.eventObservable.subscribe({
|
|
194
208
|
next(event) {
|
|
195
|
-
if (event.type === pool_types_1.PoolEventType.taskQueueDrained) {
|
|
209
|
+
if (event.type === pool_types_1.PoolEventType.taskQueueDrained || event.type === pool_types_1.PoolEventType.terminated) {
|
|
196
210
|
subscription.unsubscribe();
|
|
197
211
|
resolve(settlementPromise);
|
|
198
212
|
}
|
|
@@ -201,6 +215,11 @@ class WorkerPool {
|
|
|
201
215
|
reject(event.error);
|
|
202
216
|
}
|
|
203
217
|
},
|
|
218
|
+
complete() {
|
|
219
|
+
// The pool was terminated while we were waiting; resolve instead of hanging.
|
|
220
|
+
subscription.unsubscribe();
|
|
221
|
+
resolve(settlementPromise);
|
|
222
|
+
},
|
|
204
223
|
error: reject // make a pool-wide error reject the completed() result promise
|
|
205
224
|
});
|
|
206
225
|
});
|
|
@@ -224,6 +243,13 @@ class WorkerPool {
|
|
|
224
243
|
if (this.initErrors.length > 0) {
|
|
225
244
|
throw this.initErrors[0];
|
|
226
245
|
}
|
|
246
|
+
// Check the queue limit before taskCompletion() below subscribes to the
|
|
247
|
+
// pool events — throwing after would leak that subscription.
|
|
248
|
+
if (this.taskQueue.length >= maxQueuedJobs) {
|
|
249
|
+
throw Error("Maximum number of pool tasks queued. Refusing to queue another one.\n" +
|
|
250
|
+
"This usually happens for one of two reasons: We are either at peak " +
|
|
251
|
+
"workload right now or some tasks just won't finish, thus blocking the pool.");
|
|
252
|
+
}
|
|
227
253
|
const taskID = this.nextTaskID++;
|
|
228
254
|
const taskCompletion = this.taskCompletion(taskID);
|
|
229
255
|
taskCompletion.catch((error) => {
|
|
@@ -245,11 +271,6 @@ class WorkerPool {
|
|
|
245
271
|
},
|
|
246
272
|
then: taskCompletion.then.bind(taskCompletion)
|
|
247
273
|
};
|
|
248
|
-
if (this.taskQueue.length >= maxQueuedJobs) {
|
|
249
|
-
throw Error("Maximum number of pool tasks queued. Refusing to queue another one.\n" +
|
|
250
|
-
"This usually happens for one of two reasons: We are either at peak " +
|
|
251
|
-
"workload right now or some tasks just won't finish, thus blocking the pool.");
|
|
252
|
-
}
|
|
253
274
|
this.debug(`Queueing task #${task.id}...`);
|
|
254
275
|
this.taskQueue.push(task);
|
|
255
276
|
this.eventSubject.next({
|
|
@@ -270,7 +291,17 @@ class WorkerPool {
|
|
|
270
291
|
remainingQueue: [...this.taskQueue]
|
|
271
292
|
});
|
|
272
293
|
this.eventSubject.complete();
|
|
273
|
-
yield Promise.all(this.workers.map((worker) => __awaiter(this, void 0, void 0, function* () {
|
|
294
|
+
yield Promise.all(this.workers.map((worker) => __awaiter(this, void 0, void 0, function* () {
|
|
295
|
+
try {
|
|
296
|
+
yield thread_1.Thread.terminate(yield worker.init);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
// The worker never finished initializing (e.g. it hit the init
|
|
300
|
+
// timeout), so `worker.init` rejected and there is no thread to
|
|
301
|
+
// terminate here — spawn() already tore down the underlying worker.
|
|
302
|
+
this.debug("Worker did not initialize; nothing to terminate:", error);
|
|
303
|
+
}
|
|
304
|
+
})));
|
|
274
305
|
});
|
|
275
306
|
}
|
|
276
307
|
}
|
|
@@ -281,6 +312,8 @@ WorkerPool.EventType = pool_types_1.PoolEventType;
|
|
|
281
312
|
function PoolConstructor(spawnWorker, optionsOrSize) {
|
|
282
313
|
// The function exists only so we don't need to use `new` to create a pool (we still can, though).
|
|
283
314
|
// If the Pool is a class or not is an implementation detail that should not concern the user.
|
|
315
|
+
// The explicit `Pool<ThreadType>` return type keeps the private `WorkerPool` class out of the
|
|
316
|
+
// public type surface, so consumers can name/re-export the pool type (#417).
|
|
284
317
|
return new WorkerPool(spawnWorker, optionsOrSize);
|
|
285
318
|
}
|
|
286
319
|
PoolConstructor.EventType = pool_types_1.PoolEventType;
|
package/dist/master/spawn.js
CHANGED
|
@@ -81,17 +81,32 @@ function createEventObservable(worker, workerTermination) {
|
|
|
81
81
|
};
|
|
82
82
|
observer.next(workerEvent);
|
|
83
83
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
let finished = false;
|
|
85
|
+
const removeListeners = () => {
|
|
86
|
+
worker.removeEventListener("message", messageHandler);
|
|
87
|
+
worker.removeEventListener("unhandledrejection", rejectionHandler);
|
|
88
|
+
worker.removeEventListener("exit", exitHandler);
|
|
89
|
+
};
|
|
90
|
+
const finish = () => {
|
|
91
|
+
if (finished)
|
|
92
|
+
return;
|
|
93
|
+
finished = true;
|
|
87
94
|
const terminationEvent = {
|
|
88
95
|
type: master_1.WorkerEventType.termination
|
|
89
96
|
};
|
|
90
|
-
|
|
91
|
-
worker.removeEventListener("unhandledrejection", rejectionHandler);
|
|
97
|
+
removeListeners();
|
|
92
98
|
observer.next(terminationEvent);
|
|
93
99
|
observer.complete();
|
|
94
|
-
}
|
|
100
|
+
};
|
|
101
|
+
// A worker can also go away without Thread.terminate() being called (crash,
|
|
102
|
+
// process.exit() in the worker). Complete the observable and detach the
|
|
103
|
+
// listeners in that case, too, instead of leaving them behind forever.
|
|
104
|
+
const exitHandler = (() => finish());
|
|
105
|
+
worker.addEventListener("message", messageHandler);
|
|
106
|
+
worker.addEventListener("unhandledrejection", rejectionHandler);
|
|
107
|
+
worker.addEventListener("exit", exitHandler);
|
|
108
|
+
workerTermination.then(finish);
|
|
109
|
+
return removeListeners;
|
|
95
110
|
});
|
|
96
111
|
}
|
|
97
112
|
function createTerminator(worker) {
|
|
@@ -126,7 +141,7 @@ function setPrivateThreadProps(raw, worker, workerEvents, terminate) {
|
|
|
126
141
|
*/
|
|
127
142
|
function spawn(worker, options) {
|
|
128
143
|
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
-
var _a, _b;
|
|
144
|
+
var _a, _b, _c, _d;
|
|
130
145
|
debugSpawn("Initializing new thread");
|
|
131
146
|
const timeout = options && options.timeout ? options.timeout : initMessageTimeout;
|
|
132
147
|
let initMessage;
|
|
@@ -153,6 +168,8 @@ function spawn(worker, options) {
|
|
|
153
168
|
}
|
|
154
169
|
else {
|
|
155
170
|
const type = exposed.type;
|
|
171
|
+
// Same teardown as a failed init: don't leak a live worker handle.
|
|
172
|
+
yield Promise.resolve((_d = (_c = worker).terminate) === null || _d === void 0 ? void 0 : _d.call(_c)).catch(() => undefined);
|
|
156
173
|
throw Error(`Worker init message states unexpected type of expose(): ${type}`);
|
|
157
174
|
}
|
|
158
175
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Whether this code is running inside a webpack bundle. */
|
|
2
|
+
export declare function isWebpackBundle(): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Returns a `require` that resolves against the real filesystem at runtime,
|
|
5
|
+
* even inside a bundle.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getNodeRequire(): NodeRequire;
|
|
8
|
+
/**
|
|
9
|
+
* Directory to resolve relative worker paths against when no caller path and
|
|
10
|
+
* no base URL are available. Matches this module's directory where the runtime
|
|
11
|
+
* provides one, and falls back to the current working directory.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getModuleDirname(): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isWebpackBundle = isWebpackBundle;
|
|
37
|
+
exports.getNodeRequire = getNodeRequire;
|
|
38
|
+
exports.getModuleDirname = getModuleDirname;
|
|
39
|
+
/*
|
|
40
|
+
* Runtime module/path resolution for the Node.js implementation.
|
|
41
|
+
*
|
|
42
|
+
* Spawning a worker needs real filesystem paths at runtime, but webpack
|
|
43
|
+
* rewrites `require`, `require.resolve` and `__dirname` at compile time.
|
|
44
|
+
* `__non_webpack_require__` is webpack's documented escape hatch: it compiles
|
|
45
|
+
* to the untouched `require` of the output bundle, so resolution happens at
|
|
46
|
+
* runtime against the real filesystem. Every other environment (plain Node,
|
|
47
|
+
* esbuild, rollup, Vite) keeps `createRequire` and `__dirname` intact, so no
|
|
48
|
+
* `eval()` tricks are needed there.
|
|
49
|
+
*
|
|
50
|
+
* This module is only ever loaded on Node targets: the package.json "browser"
|
|
51
|
+
* field maps its sole importers to `false` for browser bundles.
|
|
52
|
+
*/
|
|
53
|
+
const module_1 = require("module");
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
/** Whether this code is running inside a webpack bundle. */
|
|
56
|
+
function isWebpackBundle() {
|
|
57
|
+
return typeof __non_webpack_require__ === "function";
|
|
58
|
+
}
|
|
59
|
+
let nodeRequire;
|
|
60
|
+
/**
|
|
61
|
+
* Returns a `require` that resolves against the real filesystem at runtime,
|
|
62
|
+
* even inside a bundle.
|
|
63
|
+
*/
|
|
64
|
+
function getNodeRequire() {
|
|
65
|
+
if (typeof __non_webpack_require__ === "function") {
|
|
66
|
+
return __non_webpack_require__;
|
|
67
|
+
}
|
|
68
|
+
if (!nodeRequire) {
|
|
69
|
+
// CJS build: `__filename` is this module's real path. ESM build (esm/):
|
|
70
|
+
// the build banner derives `__filename` from `import.meta.url`. In an
|
|
71
|
+
// environment that defines neither (dist-esm bundled straight to ESM by a
|
|
72
|
+
// non-webpack bundler), resolve from the current working directory.
|
|
73
|
+
const base = typeof __filename !== "undefined"
|
|
74
|
+
? __filename
|
|
75
|
+
: path.join(process.cwd(), "__threadsx_resolve__.js");
|
|
76
|
+
nodeRequire = (0, module_1.createRequire)(base);
|
|
77
|
+
}
|
|
78
|
+
return nodeRequire;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Directory to resolve relative worker paths against when no caller path and
|
|
82
|
+
* no base URL are available. Matches this module's directory where the runtime
|
|
83
|
+
* provides one, and falls back to the current working directory.
|
|
84
|
+
*/
|
|
85
|
+
function getModuleDirname() {
|
|
86
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
87
|
+
}
|
package/dist/transferable.d.ts
CHANGED
package/dist/types/master.d.ts
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import { Observable } from "observable-fns";
|
|
2
2
|
import { ObservablePromise } from "../observable-promise";
|
|
3
3
|
import { $errors, $events, $terminate, $worker } from "../symbols";
|
|
4
|
-
import { TransferDescriptor } from "../transferable";
|
|
4
|
+
import { Transferable, TransferDescriptor } from "../transferable";
|
|
5
|
+
declare class EventTarget {
|
|
6
|
+
addEventListener(type: string, listener: any, options?: any): void;
|
|
7
|
+
removeEventListener(type: string, listener: any, options?: any): void;
|
|
8
|
+
dispatchEvent(event: any): boolean;
|
|
9
|
+
}
|
|
10
|
+
interface WorkerOptions {
|
|
11
|
+
type?: "classic" | "module";
|
|
12
|
+
credentials?: "omit" | "same-origin" | "include";
|
|
13
|
+
name?: string;
|
|
14
|
+
}
|
|
15
|
+
interface URL {
|
|
16
|
+
readonly href: string;
|
|
17
|
+
toString(): string;
|
|
18
|
+
}
|
|
19
|
+
interface Blob {
|
|
20
|
+
readonly size: number;
|
|
21
|
+
readonly type: string;
|
|
22
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
23
|
+
slice(start?: number, end?: number, contentType?: string): Blob;
|
|
24
|
+
text(): Promise<string>;
|
|
25
|
+
}
|
|
5
26
|
interface ObservableLikeSubscription {
|
|
6
27
|
unsubscribe(): any;
|
|
7
28
|
}
|
package/dist/types/master.js
CHANGED
package/dist/types/worker.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare const _default: {
|
|
2
2
|
isWorkerRuntime: () => boolean;
|
|
3
|
-
postMessageToMaster: (message: any, transferList?: Transferable[]) => void;
|
|
3
|
+
postMessageToMaster: (message: any, transferList?: import("../transferable").Transferable[]) => void;
|
|
4
4
|
subscribeToMasterMessages: (onMessage: (data: any) => void) => () => void;
|
|
5
5
|
};
|
|
6
6
|
export default _default;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
declare function testImplementation(): void;
|
|
2
2
|
declare const _default: {
|
|
3
3
|
isWorkerRuntime: () => boolean;
|
|
4
|
-
postMessageToMaster: (message: any, transferList?: Transferable[]) => void;
|
|
4
|
+
postMessageToMaster: (message: any, transferList?: import("../transferable").Transferable[]) => void;
|
|
5
5
|
subscribeToMasterMessages: (onMessage: (data: any) => void) => () => void;
|
|
6
6
|
testImplementation: typeof testImplementation;
|
|
7
7
|
};
|