threadsx 2.0.2 → 2.1.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 +22 -2
- package/dist/master/invocation-proxy.js +45 -11
- package/dist/master/pool.d.ts +1 -24
- package/dist/master/pool.js +25 -3
- 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-esm/errors.js +26 -0
- package/dist-esm/index.js +1 -0
- package/dist-esm/master/implementation.node.js +22 -2
- package/dist-esm/master/invocation-proxy.js +45 -11
- package/dist-esm/master/pool.js +25 -3
- package/dist-esm/types/master.js +0 -1
- 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 +190 -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 +313 -0
- package/esm/master/register.mjs +14 -0
- package/esm/master/spawn.mjs +162 -0
- package/esm/master/thread.mjs +28 -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 +22 -0
- package/esm/worker/implementation.worker_threads.mjs +46 -0
- package/esm/worker/index.mjs +224 -0
- package/esm/worker_threads.mjs +21 -0
- package/package.json +20 -20
- package/index.mjs +0 -4
- package/observable.mjs +0 -1
- package/register.mjs +0 -2
- package/worker.mjs +0 -1
package/esm/common.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
import { DefaultSerializer, extendSerializer } from './serializers.mjs';
|
|
8
|
+
|
|
9
|
+
let registeredSerializer = DefaultSerializer;
|
|
10
|
+
function registerSerializer(serializer) {
|
|
11
|
+
registeredSerializer = extendSerializer(registeredSerializer, serializer);
|
|
12
|
+
}
|
|
13
|
+
function deserialize(message) {
|
|
14
|
+
return registeredSerializer.deserialize(message);
|
|
15
|
+
}
|
|
16
|
+
function serialize(input) {
|
|
17
|
+
return registeredSerializer.serialize(input);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { deserialize, registerSerializer, serialize };
|
package/esm/errors.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
/**
|
|
8
|
+
* Thrown when a value passed to a thread cannot be cloned by the structured
|
|
9
|
+
* clone algorithm (e.g. functions, class instances with methods, or other
|
|
10
|
+
* non-serializable values). Wraps the underlying `DataCloneError` / `DOMException`
|
|
11
|
+
* with an actionable message. Access the original error via `.cause`.
|
|
12
|
+
*/
|
|
13
|
+
class ThreadCloneError extends Error {
|
|
14
|
+
constructor(message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "ThreadCloneError";
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
// Restore the prototype chain — extending built-ins breaks `instanceof`
|
|
19
|
+
// when compiled down to ES2015 without this.
|
|
20
|
+
Object.setPrototypeOf(this, ThreadCloneError.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether an error is a structured-clone failure. Both browsers and Node's
|
|
25
|
+
* `worker_threads` throw a `DOMException`/error named `"DataCloneError"` when a
|
|
26
|
+
* value cannot be cloned across the thread boundary.
|
|
27
|
+
*/
|
|
28
|
+
function isDataCloneError(error) {
|
|
29
|
+
return Boolean(error &&
|
|
30
|
+
typeof error === "object" &&
|
|
31
|
+
error.name === "DataCloneError");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { ThreadCloneError, isDataCloneError };
|
package/esm/index.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
export { registerSerializer } from './common.mjs';
|
|
8
|
+
export { ThreadCloneError } from './errors.mjs';
|
|
9
|
+
export { BlobWorker, Worker } from './master/index.mjs';
|
|
10
|
+
export { expose } from './worker/index.mjs';
|
|
11
|
+
export { DefaultSerializer } from './serializers.mjs';
|
|
12
|
+
export { Transfer } from './transferable.mjs';
|
|
13
|
+
export { Pool } from './master/pool.mjs';
|
|
14
|
+
export { Thread } from './master/thread.mjs';
|
|
15
|
+
export { isWorkerRuntime } from './master/implementation.mjs';
|
|
16
|
+
export { spawn } from './master/spawn.mjs';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
// Source: <https://github.com/parcel-bundler/parcel/blob/master/packages/core/parcel-bundler/src/builtins/bundle-url.js>
|
|
8
|
+
let bundleURL;
|
|
9
|
+
function getBundleURLCached() {
|
|
10
|
+
if (!bundleURL) {
|
|
11
|
+
bundleURL = getBundleURL();
|
|
12
|
+
}
|
|
13
|
+
return bundleURL;
|
|
14
|
+
}
|
|
15
|
+
function getBundleURL() {
|
|
16
|
+
// Attempt to find the URL of the current script and use that as the base URL
|
|
17
|
+
try {
|
|
18
|
+
throw new Error;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const matches = ("" + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\/\/[^)\n]+/g);
|
|
22
|
+
if (matches) {
|
|
23
|
+
return getBaseURL(matches[0]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return "/";
|
|
27
|
+
}
|
|
28
|
+
function getBaseURL(url) {
|
|
29
|
+
return ("" + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\/\/.+)?\/[^/]+(?:\?.*)?$/, '$1') + '/';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { getBaseURL, getBundleURLCached as getBundleURL };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
import { getBundleURL as getBundleURLCached } from './get-bundle-url.browser.mjs';
|
|
8
|
+
|
|
9
|
+
const defaultPoolSize = typeof navigator !== "undefined" && navigator.hardwareConcurrency
|
|
10
|
+
? navigator.hardwareConcurrency
|
|
11
|
+
: 4;
|
|
12
|
+
const isAbsoluteURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value);
|
|
13
|
+
function createSourceBlobURL(code) {
|
|
14
|
+
const blob = new Blob([code], { type: "application/javascript" });
|
|
15
|
+
return URL.createObjectURL(blob);
|
|
16
|
+
}
|
|
17
|
+
function selectWorkerImplementation() {
|
|
18
|
+
if (typeof Worker === "undefined") {
|
|
19
|
+
// Might happen on Safari, for instance
|
|
20
|
+
// The idea is to only fail if the constructor is actually used
|
|
21
|
+
return class NoWebWorker {
|
|
22
|
+
constructor() {
|
|
23
|
+
throw Error("No web worker implementation available. You might have tried to spawn a worker within a worker in a browser that doesn't support workers in workers.");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
class WebWorker extends Worker {
|
|
28
|
+
constructor(url, options) {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
if (typeof url === "string" && options && options._baseURL) {
|
|
31
|
+
url = new URL(url, options._baseURL);
|
|
32
|
+
}
|
|
33
|
+
else if (typeof url === "string" && !isAbsoluteURL(url) && getBundleURLCached().match(/^file:\/\//i)) {
|
|
34
|
+
url = new URL(url, getBundleURLCached().replace(/\/[^/]+$/, "/"));
|
|
35
|
+
if ((_a = options === null || options === void 0 ? void 0 : options.CORSWorkaround) !== null && _a !== void 0 ? _a : true) {
|
|
36
|
+
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (typeof url === "string" && isAbsoluteURL(url)) {
|
|
40
|
+
// Create source code blob loading JS file via `importScripts()`
|
|
41
|
+
// to circumvent worker CORS restrictions
|
|
42
|
+
if ((_b = options === null || options === void 0 ? void 0 : options.CORSWorkaround) !== null && _b !== void 0 ? _b : true) {
|
|
43
|
+
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
super(url, options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
class BlobWorker extends WebWorker {
|
|
50
|
+
constructor(blob, options) {
|
|
51
|
+
const url = window.URL.createObjectURL(blob);
|
|
52
|
+
super(url, options);
|
|
53
|
+
}
|
|
54
|
+
static fromText(source, options) {
|
|
55
|
+
const blob = new window.Blob([source], { type: "text/javascript" });
|
|
56
|
+
return new BlobWorker(blob, options);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
blob: BlobWorker,
|
|
61
|
+
default: WebWorker
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
let implementation;
|
|
65
|
+
function getWorkerImplementation() {
|
|
66
|
+
if (!implementation) {
|
|
67
|
+
implementation = selectWorkerImplementation();
|
|
68
|
+
}
|
|
69
|
+
return implementation;
|
|
70
|
+
}
|
|
71
|
+
function isWorkerRuntime() {
|
|
72
|
+
const isWindowContext = typeof self !== "undefined" && typeof Window !== "undefined" && self instanceof Window;
|
|
73
|
+
return typeof self !== "undefined" && typeof self.postMessage === "function" && !isWindowContext;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { defaultPoolSize, getWorkerImplementation, isWorkerRuntime };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
import * as implementation_browser from './implementation.browser.mjs';
|
|
8
|
+
import * as implementation_node from './implementation.node.mjs';
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* This file is only a stub to make './implementation' resolve to the right module.
|
|
12
|
+
*/
|
|
13
|
+
// We alias `src/master/implementation` to `src/master/implementation.browser` for web
|
|
14
|
+
// browsers already in the package.json, so if get here, it's safe to pass-through the
|
|
15
|
+
// node implementation
|
|
16
|
+
const runningInNode = typeof process !== 'undefined' && process.arch !== 'browser' && 'pid' in process;
|
|
17
|
+
const implementation = runningInNode ? implementation_node : implementation_browser;
|
|
18
|
+
/** Default size of pools. Depending on the platform the value might vary from device to device. */
|
|
19
|
+
const defaultPoolSize = implementation.defaultPoolSize;
|
|
20
|
+
const getWorkerImplementation = implementation.getWorkerImplementation;
|
|
21
|
+
/** Returns `true` if this code is currently running in a worker. */
|
|
22
|
+
const isWorkerRuntime = implementation.isWorkerRuntime;
|
|
23
|
+
|
|
24
|
+
export { defaultPoolSize, getWorkerImplementation, isWorkerRuntime };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
import getCallsites from 'callsites';
|
|
8
|
+
import { cpus } from 'os';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
/// <reference lib="dom" />
|
|
13
|
+
// NOTE: `callsites` is intentionally pinned to ^3. v4+ is ESM-only and would
|
|
14
|
+
// break the CommonJS (dist/) build. Do not bump it to v4 without moving the
|
|
15
|
+
// package to ESM-first output. (See .github/dependabot.yml, which also ignores
|
|
16
|
+
// its major updates.)
|
|
17
|
+
let detectedTsRuntime;
|
|
18
|
+
const defaultPoolSize = cpus().length;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the application opted out of threadsx installing SIGINT/SIGTERM
|
|
21
|
+
* handlers (which call `process.exit()`), via `THREADS_SKIP_SIGNAL_HANDLERS`.
|
|
22
|
+
*/
|
|
23
|
+
function signalHandlersDisabled() {
|
|
24
|
+
const value = typeof process !== "undefined" && process.env
|
|
25
|
+
? process.env.THREADS_SKIP_SIGNAL_HANDLERS
|
|
26
|
+
: undefined;
|
|
27
|
+
return value === "1" || value === "true";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Detects an available TypeScript runtime so `.ts`/`.tsx` worker files can be
|
|
31
|
+
* spawned directly during development. Prefers `tsx`, falls back to `ts-node`.
|
|
32
|
+
*/
|
|
33
|
+
function detectTsRuntime() {
|
|
34
|
+
if (typeof __non_webpack_require__ === "function") {
|
|
35
|
+
// Webpack build: => No TS runtime required or possible
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (detectedTsRuntime !== undefined) {
|
|
39
|
+
return detectedTsRuntime;
|
|
40
|
+
}
|
|
41
|
+
detectedTsRuntime = null;
|
|
42
|
+
for (const candidate of ["tsx", "ts-node"]) {
|
|
43
|
+
try {
|
|
44
|
+
eval("require").resolve(candidate);
|
|
45
|
+
detectedTsRuntime = candidate;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error && error.code === "MODULE_NOT_FOUND") {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// Re-throw
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return detectedTsRuntime;
|
|
57
|
+
}
|
|
58
|
+
function createTsRuntimeModule(scriptPath, runtime) {
|
|
59
|
+
const register = runtime === "tsx"
|
|
60
|
+
? `require("tsx/cjs");`
|
|
61
|
+
: `require("ts-node/register/transpile-only");`;
|
|
62
|
+
const content = `
|
|
63
|
+
${register}
|
|
64
|
+
require(${JSON.stringify(scriptPath)});
|
|
65
|
+
`;
|
|
66
|
+
return content;
|
|
67
|
+
}
|
|
68
|
+
function rebaseScriptPath(scriptPath, ignoreRegex) {
|
|
69
|
+
// An already-absolute path (e.g. from `new Worker(new URL("./w", import.meta.url))`,
|
|
70
|
+
// which threadsx normalizes to a filesystem path) must not be rebased onto the
|
|
71
|
+
// caller's directory — doing so would prepend it and corrupt the path.
|
|
72
|
+
if (path.isAbsolute(scriptPath)) {
|
|
73
|
+
return scriptPath;
|
|
74
|
+
}
|
|
75
|
+
const parentCallSite = getCallsites().find((callsite) => {
|
|
76
|
+
const filename = callsite.getFileName();
|
|
77
|
+
return Boolean(filename &&
|
|
78
|
+
!filename.match(ignoreRegex) &&
|
|
79
|
+
!filename.match(/[/\\]master[/\\]implementation/) &&
|
|
80
|
+
!filename.match(/^internal\/process/));
|
|
81
|
+
});
|
|
82
|
+
const rawCallerPath = parentCallSite ? parentCallSite.getFileName() : null;
|
|
83
|
+
let callerPath = rawCallerPath ? rawCallerPath : null;
|
|
84
|
+
if (callerPath && callerPath.startsWith('file:')) {
|
|
85
|
+
callerPath = fileURLToPath(callerPath);
|
|
86
|
+
}
|
|
87
|
+
const rebasedScriptPath = callerPath ? path.join(path.dirname(callerPath), scriptPath) : scriptPath;
|
|
88
|
+
return rebasedScriptPath;
|
|
89
|
+
}
|
|
90
|
+
function resolveScriptPath(scriptPath, baseURL) {
|
|
91
|
+
const makeRelative = (filePath) => {
|
|
92
|
+
// eval() hack is also webpack-related
|
|
93
|
+
return path.isAbsolute(filePath) ? filePath : path.join(baseURL || eval("__dirname"), filePath);
|
|
94
|
+
};
|
|
95
|
+
const workerFilePath = typeof __non_webpack_require__ === "function"
|
|
96
|
+
? __non_webpack_require__.resolve(makeRelative(scriptPath))
|
|
97
|
+
: eval("require").resolve(makeRelative(rebaseScriptPath(scriptPath, /[/\\]worker_threads[/\\]/)));
|
|
98
|
+
return workerFilePath;
|
|
99
|
+
}
|
|
100
|
+
function initWorkerThreadsWorker() {
|
|
101
|
+
// Webpack hack
|
|
102
|
+
const NativeWorker = typeof __non_webpack_require__ === "function"
|
|
103
|
+
? __non_webpack_require__("worker_threads").Worker
|
|
104
|
+
: eval("require")("worker_threads").Worker;
|
|
105
|
+
let allWorkers = [];
|
|
106
|
+
class Worker extends NativeWorker {
|
|
107
|
+
constructor(scriptPath, options) {
|
|
108
|
+
// Bundlers like webpack 5 pass a `URL` (from `new Worker(new URL(...))`)
|
|
109
|
+
// pointing at the emitted worker chunk.
|
|
110
|
+
const normalizedScriptPath = scriptPath instanceof URL ? fileURLToPath(scriptPath) : scriptPath;
|
|
111
|
+
const resolvedScriptPath = options && options.fromSource
|
|
112
|
+
? null
|
|
113
|
+
: resolveScriptPath(normalizedScriptPath, (options || {})._baseURL);
|
|
114
|
+
const tsRuntime = resolvedScriptPath && /\.tsx?$/i.test(resolvedScriptPath) ? detectTsRuntime() : null;
|
|
115
|
+
if (!resolvedScriptPath) {
|
|
116
|
+
// `options.fromSource` is true
|
|
117
|
+
const sourceCode = scriptPath;
|
|
118
|
+
super(sourceCode, Object.assign(Object.assign({}, options), { eval: true }));
|
|
119
|
+
}
|
|
120
|
+
else if (tsRuntime) {
|
|
121
|
+
super(createTsRuntimeModule(resolvedScriptPath, tsRuntime), Object.assign(Object.assign({}, options), { eval: true }));
|
|
122
|
+
}
|
|
123
|
+
else if (resolvedScriptPath.match(/\.asar[/\\]/)) {
|
|
124
|
+
// See <https://github.com/andywer/threads-plugin/issues/17>
|
|
125
|
+
super(resolvedScriptPath.replace(/\.asar([/\\])/, ".asar.unpacked$1"), options);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
super(resolvedScriptPath, options);
|
|
129
|
+
}
|
|
130
|
+
this.mappedEventListeners = new WeakMap();
|
|
131
|
+
allWorkers.push(this);
|
|
132
|
+
}
|
|
133
|
+
addEventListener(eventName, rawListener) {
|
|
134
|
+
const listener = (message) => {
|
|
135
|
+
rawListener({ data: message });
|
|
136
|
+
};
|
|
137
|
+
this.mappedEventListeners.set(rawListener, listener);
|
|
138
|
+
this.on(eventName, listener);
|
|
139
|
+
}
|
|
140
|
+
removeEventListener(eventName, rawListener) {
|
|
141
|
+
const listener = this.mappedEventListeners.get(rawListener) || rawListener;
|
|
142
|
+
this.off(eventName, listener);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const terminateWorkersAndMaster = () => {
|
|
146
|
+
// we should terminate all workers and then gracefully shutdown self process
|
|
147
|
+
Promise.all(allWorkers.map(worker => worker.terminate())).then(() => process.exit(0), () => process.exit(1));
|
|
148
|
+
allWorkers = [];
|
|
149
|
+
};
|
|
150
|
+
// Take care to not leave orphaned processes behind. See #147.
|
|
151
|
+
//
|
|
152
|
+
// These handlers call process.exit(), which hijacks the host application's
|
|
153
|
+
// own shutdown. Applications that manage their own graceful shutdown can opt
|
|
154
|
+
// out by setting THREADS_SKIP_SIGNAL_HANDLERS. See upstream #388 / #484.
|
|
155
|
+
if (!signalHandlersDisabled()) {
|
|
156
|
+
process.on("SIGINT", () => terminateWorkersAndMaster());
|
|
157
|
+
process.on("SIGTERM", () => terminateWorkersAndMaster());
|
|
158
|
+
}
|
|
159
|
+
class BlobWorker extends Worker {
|
|
160
|
+
constructor(blob, options) {
|
|
161
|
+
super(Buffer.from(blob).toString("utf-8"), Object.assign(Object.assign({}, options), { fromSource: true }));
|
|
162
|
+
}
|
|
163
|
+
static fromText(source, options) {
|
|
164
|
+
return new Worker(source, Object.assign(Object.assign({}, options), { fromSource: true }));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
blob: BlobWorker,
|
|
169
|
+
default: Worker
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
let implementation;
|
|
173
|
+
function selectWorkerImplementation() {
|
|
174
|
+
return initWorkerThreadsWorker();
|
|
175
|
+
}
|
|
176
|
+
function getWorkerImplementation() {
|
|
177
|
+
if (!implementation) {
|
|
178
|
+
implementation = selectWorkerImplementation();
|
|
179
|
+
}
|
|
180
|
+
return implementation;
|
|
181
|
+
}
|
|
182
|
+
function isWorkerRuntime() {
|
|
183
|
+
// Webpack hack
|
|
184
|
+
const isMainThread = typeof __non_webpack_require__ === "function"
|
|
185
|
+
? __non_webpack_require__("worker_threads").isMainThread
|
|
186
|
+
: eval("require")("worker_threads").isMainThread;
|
|
187
|
+
return !isMainThread;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { defaultPoolSize, getWorkerImplementation, isWorkerRuntime };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
import { getWorkerImplementation } from './implementation.mjs';
|
|
8
|
+
export { isWorkerRuntime } from './implementation.mjs';
|
|
9
|
+
export { Pool } from './pool.mjs';
|
|
10
|
+
export { spawn } from './spawn.mjs';
|
|
11
|
+
|
|
12
|
+
/** Separate class to spawn workers from source code blobs or strings. */
|
|
13
|
+
const BlobWorker = getWorkerImplementation().blob;
|
|
14
|
+
/** Worker implementation. Either web worker or a node.js Worker class. */
|
|
15
|
+
const Worker = getWorkerImplementation().default;
|
|
16
|
+
|
|
17
|
+
export { BlobWorker, Worker };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
import DebugLogger from 'debug';
|
|
8
|
+
import { multicast, Observable } from 'observable-fns';
|
|
9
|
+
import { serialize, deserialize } from '../common.mjs';
|
|
10
|
+
import { isDataCloneError, ThreadCloneError } from '../errors.mjs';
|
|
11
|
+
import { ObservablePromise } from '../observable-promise.mjs';
|
|
12
|
+
import { isTransferDescriptor } from '../transferable.mjs';
|
|
13
|
+
import { MasterMessageType, WorkerMessageType } from '../types/messages.mjs';
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* This source file contains the code for proxying calls in the master thread to calls in the workers
|
|
17
|
+
* by `.postMessage()`-ing.
|
|
18
|
+
*
|
|
19
|
+
* Keep in mind that this code can make or break the program's performance! Need to optimize more…
|
|
20
|
+
*/
|
|
21
|
+
const debugMessages = DebugLogger("threads:master:messages");
|
|
22
|
+
let nextJobUID = 1;
|
|
23
|
+
const dedupe = (array) => Array.from(new Set(array));
|
|
24
|
+
const isJobErrorMessage = (data) => data && data.type === WorkerMessageType.error;
|
|
25
|
+
const isJobResultMessage = (data) => data && data.type === WorkerMessageType.result;
|
|
26
|
+
const isJobStartMessage = (data) => data && data.type === WorkerMessageType.running;
|
|
27
|
+
function createObservableForJob(worker, jobUID) {
|
|
28
|
+
return new Observable(observer => {
|
|
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
|
+
};
|
|
36
|
+
const messageHandler = ((event) => {
|
|
37
|
+
debugMessages("Message from worker:", event.data);
|
|
38
|
+
if (!event.data || event.data.uid !== jobUID)
|
|
39
|
+
return;
|
|
40
|
+
if (isJobStartMessage(event.data)) {
|
|
41
|
+
asyncType = event.data.resultType;
|
|
42
|
+
}
|
|
43
|
+
else if (isJobResultMessage(event.data)) {
|
|
44
|
+
if (asyncType === "promise") {
|
|
45
|
+
if (typeof event.data.payload !== "undefined") {
|
|
46
|
+
observer.next(deserialize(event.data.payload));
|
|
47
|
+
}
|
|
48
|
+
settled = true;
|
|
49
|
+
observer.complete();
|
|
50
|
+
cleanup();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
if (typeof event.data.payload !== "undefined") {
|
|
54
|
+
observer.next(deserialize(event.data.payload));
|
|
55
|
+
}
|
|
56
|
+
if (event.data.complete) {
|
|
57
|
+
settled = true;
|
|
58
|
+
observer.complete();
|
|
59
|
+
cleanup();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (isJobErrorMessage(event.data)) {
|
|
64
|
+
const error = deserialize(event.data.error);
|
|
65
|
+
settled = true;
|
|
66
|
+
observer.error(error);
|
|
67
|
+
cleanup();
|
|
68
|
+
}
|
|
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
|
+
});
|
|
91
|
+
worker.addEventListener("message", messageHandler);
|
|
92
|
+
worker.addEventListener("error", errorHandler);
|
|
93
|
+
worker.addEventListener("exit", exitHandler);
|
|
94
|
+
return () => {
|
|
95
|
+
if (asyncType === "observable" || !asyncType) {
|
|
96
|
+
const cancelMessage = {
|
|
97
|
+
type: MasterMessageType.cancel,
|
|
98
|
+
uid: jobUID
|
|
99
|
+
};
|
|
100
|
+
worker.postMessage(cancelMessage);
|
|
101
|
+
}
|
|
102
|
+
cleanup();
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function prepareArguments(rawArgs) {
|
|
107
|
+
if (rawArgs.length === 0) {
|
|
108
|
+
// Exit early if possible
|
|
109
|
+
return {
|
|
110
|
+
args: [],
|
|
111
|
+
transferables: []
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const args = [];
|
|
115
|
+
const transferables = [];
|
|
116
|
+
for (const arg of rawArgs) {
|
|
117
|
+
if (isTransferDescriptor(arg)) {
|
|
118
|
+
args.push(serialize(arg.send));
|
|
119
|
+
transferables.push(...arg.transferables);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
args.push(serialize(arg));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
args,
|
|
127
|
+
transferables: transferables.length === 0 ? transferables : dedupe(transferables)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function createProxyFunction(worker, method) {
|
|
131
|
+
return ((...rawArgs) => {
|
|
132
|
+
const uid = nextJobUID++;
|
|
133
|
+
const { args, transferables } = prepareArguments(rawArgs);
|
|
134
|
+
const runMessage = {
|
|
135
|
+
type: MasterMessageType.run,
|
|
136
|
+
uid,
|
|
137
|
+
method,
|
|
138
|
+
args
|
|
139
|
+
};
|
|
140
|
+
debugMessages("Sending command to run function to worker:", runMessage);
|
|
141
|
+
try {
|
|
142
|
+
worker.postMessage(runMessage, transferables);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (isDataCloneError(error)) {
|
|
146
|
+
const cloneError = new 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 ObservablePromise.from(Promise.reject(cloneError));
|
|
150
|
+
}
|
|
151
|
+
return ObservablePromise.from(Promise.reject(error));
|
|
152
|
+
}
|
|
153
|
+
return ObservablePromise.from(multicast(createObservableForJob(worker, uid)));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function createProxyModule(worker, methodNames) {
|
|
157
|
+
const proxy = {};
|
|
158
|
+
for (const methodName of methodNames) {
|
|
159
|
+
proxy[methodName] = createProxyFunction(worker, methodName);
|
|
160
|
+
}
|
|
161
|
+
return proxy;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export { createProxyFunction, createProxyModule };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from "url";
|
|
3
|
+
import { dirname as __pathDirname } from "path";
|
|
4
|
+
const require = __createRequire(import.meta.url);
|
|
5
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __pathDirname(__filename);
|
|
7
|
+
/** Pool event type. Specifies the type of each `PoolEvent`. */
|
|
8
|
+
var PoolEventType;
|
|
9
|
+
(function (PoolEventType) {
|
|
10
|
+
PoolEventType["initialized"] = "initialized";
|
|
11
|
+
PoolEventType["taskCanceled"] = "taskCanceled";
|
|
12
|
+
PoolEventType["taskCompleted"] = "taskCompleted";
|
|
13
|
+
PoolEventType["taskFailed"] = "taskFailed";
|
|
14
|
+
PoolEventType["taskQueued"] = "taskQueued";
|
|
15
|
+
PoolEventType["taskQueueDrained"] = "taskQueueDrained";
|
|
16
|
+
PoolEventType["taskStart"] = "taskStart";
|
|
17
|
+
PoolEventType["terminated"] = "terminated";
|
|
18
|
+
})(PoolEventType || (PoolEventType = {}));
|
|
19
|
+
|
|
20
|
+
export { PoolEventType };
|