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
|
@@ -0,0 +1,324 @@
|
|
|
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 { Subject, multicast, Observable } from 'observable-fns';
|
|
9
|
+
import { allSettled } from '../ponyfills.mjs';
|
|
10
|
+
import { defaultPoolSize } from './implementation.mjs';
|
|
11
|
+
import { PoolEventType } from './pool-types.mjs';
|
|
12
|
+
import { Thread } from './thread.mjs';
|
|
13
|
+
|
|
14
|
+
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
let nextPoolID = 1;
|
|
24
|
+
function createArray(size) {
|
|
25
|
+
const array = [];
|
|
26
|
+
for (let index = 0; index < size; index++) {
|
|
27
|
+
array.push(index);
|
|
28
|
+
}
|
|
29
|
+
return array;
|
|
30
|
+
}
|
|
31
|
+
function delay(ms) {
|
|
32
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
33
|
+
}
|
|
34
|
+
function flatMap(array, mapper) {
|
|
35
|
+
return array.reduce((flattened, element) => [...flattened, ...mapper(element)], []);
|
|
36
|
+
}
|
|
37
|
+
function slugify(text) {
|
|
38
|
+
return text.replace(/\W/g, " ").trim().replace(/\s+/g, "-");
|
|
39
|
+
}
|
|
40
|
+
function spawnWorkers(spawnWorker, count) {
|
|
41
|
+
return createArray(count).map(() => ({
|
|
42
|
+
init: spawnWorker(),
|
|
43
|
+
runningTasks: []
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
class WorkerPool {
|
|
47
|
+
constructor(spawnWorker, optionsOrSize) {
|
|
48
|
+
this.eventSubject = new Subject();
|
|
49
|
+
this.initErrors = [];
|
|
50
|
+
this.isClosing = false;
|
|
51
|
+
this.nextTaskID = 1;
|
|
52
|
+
this.taskQueue = [];
|
|
53
|
+
const options = typeof optionsOrSize === "number"
|
|
54
|
+
? { size: optionsOrSize }
|
|
55
|
+
: optionsOrSize || {};
|
|
56
|
+
const { size = defaultPoolSize } = options;
|
|
57
|
+
this.debug = DebugLogger(`threads:pool:${slugify(options.name || String(nextPoolID++))}`);
|
|
58
|
+
this.options = options;
|
|
59
|
+
this.workers = spawnWorkers(spawnWorker, size);
|
|
60
|
+
this.eventObservable = multicast(Observable.from(this.eventSubject));
|
|
61
|
+
Promise.all(this.workers.map(worker => worker.init)).then(() => this.eventSubject.next({
|
|
62
|
+
type: PoolEventType.initialized,
|
|
63
|
+
size: this.workers.length
|
|
64
|
+
}), error => {
|
|
65
|
+
this.debug("Error while initializing pool worker:", error);
|
|
66
|
+
this.eventSubject.error(error);
|
|
67
|
+
this.initErrors.push(error);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
findIdlingWorker() {
|
|
71
|
+
const { concurrency = 1 } = this.options;
|
|
72
|
+
return this.workers.find(worker => worker.runningTasks.length < concurrency);
|
|
73
|
+
}
|
|
74
|
+
runPoolTask(worker, task) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
const workerID = this.workers.indexOf(worker) + 1;
|
|
77
|
+
this.debug(`Running task #${task.id} on worker #${workerID}...`);
|
|
78
|
+
this.eventSubject.next({
|
|
79
|
+
type: PoolEventType.taskStart,
|
|
80
|
+
taskID: task.id,
|
|
81
|
+
workerID
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
const returnValue = yield task.run(yield worker.init);
|
|
85
|
+
this.debug(`Task #${task.id} completed successfully`);
|
|
86
|
+
this.eventSubject.next({
|
|
87
|
+
type: PoolEventType.taskCompleted,
|
|
88
|
+
returnValue,
|
|
89
|
+
taskID: task.id,
|
|
90
|
+
workerID
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.debug(`Task #${task.id} failed`);
|
|
95
|
+
this.eventSubject.next({
|
|
96
|
+
type: PoolEventType.taskFailed,
|
|
97
|
+
taskID: task.id,
|
|
98
|
+
error: error,
|
|
99
|
+
workerID
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
run(worker, task) {
|
|
105
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
106
|
+
const runPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
const removeTaskFromWorkersRunningTasks = () => {
|
|
108
|
+
worker.runningTasks = worker.runningTasks.filter(someRunPromise => someRunPromise !== runPromise);
|
|
109
|
+
};
|
|
110
|
+
// Defer task execution by one tick to give handlers time to subscribe
|
|
111
|
+
yield delay(0);
|
|
112
|
+
try {
|
|
113
|
+
yield this.runPoolTask(worker, task);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
removeTaskFromWorkersRunningTasks();
|
|
117
|
+
if (!this.isClosing) {
|
|
118
|
+
this.scheduleWork();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}))();
|
|
122
|
+
worker.runningTasks.push(runPromise);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
scheduleWork() {
|
|
126
|
+
this.debug(`Attempt de-queueing a task in order to run it...`);
|
|
127
|
+
const availableWorker = this.findIdlingWorker();
|
|
128
|
+
if (!availableWorker)
|
|
129
|
+
return;
|
|
130
|
+
const nextTask = this.taskQueue.shift();
|
|
131
|
+
if (!nextTask) {
|
|
132
|
+
this.debug(`Task queue is empty`);
|
|
133
|
+
this.eventSubject.next({ type: PoolEventType.taskQueueDrained });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.run(availableWorker, nextTask);
|
|
137
|
+
}
|
|
138
|
+
taskCompletion(taskID) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const eventSubscription = this.events().subscribe(event => {
|
|
141
|
+
if (event.type === PoolEventType.taskCompleted && event.taskID === taskID) {
|
|
142
|
+
eventSubscription.unsubscribe();
|
|
143
|
+
resolve(event.returnValue);
|
|
144
|
+
}
|
|
145
|
+
else if (event.type === PoolEventType.taskFailed && event.taskID === taskID) {
|
|
146
|
+
eventSubscription.unsubscribe();
|
|
147
|
+
reject(event.error);
|
|
148
|
+
}
|
|
149
|
+
else if (event.type === PoolEventType.taskCanceled && event.taskID === taskID) {
|
|
150
|
+
// Without this branch a canceled task would keep its event
|
|
151
|
+
// subscription alive for the pool's whole lifetime and its promise
|
|
152
|
+
// would never settle.
|
|
153
|
+
eventSubscription.unsubscribe();
|
|
154
|
+
reject(Error("Task has been canceled."));
|
|
155
|
+
}
|
|
156
|
+
else if (event.type === PoolEventType.terminated) {
|
|
157
|
+
eventSubscription.unsubscribe();
|
|
158
|
+
reject(Error("Pool has been terminated before task was run."));
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
settled() {
|
|
164
|
+
return __awaiter(this, arguments, void 0, function* (allowResolvingImmediately = false) {
|
|
165
|
+
const getCurrentlyRunningTasks = () => flatMap(this.workers, worker => worker.runningTasks);
|
|
166
|
+
const taskFailures = [];
|
|
167
|
+
const failureSubscription = this.eventObservable.subscribe(event => {
|
|
168
|
+
if (event.type === PoolEventType.taskFailed) {
|
|
169
|
+
taskFailures.push(event.error);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (this.initErrors.length > 0) {
|
|
173
|
+
failureSubscription.unsubscribe();
|
|
174
|
+
return Promise.reject(this.initErrors[0]);
|
|
175
|
+
}
|
|
176
|
+
if (allowResolvingImmediately && this.taskQueue.length === 0) {
|
|
177
|
+
yield allSettled(getCurrentlyRunningTasks());
|
|
178
|
+
failureSubscription.unsubscribe();
|
|
179
|
+
return taskFailures;
|
|
180
|
+
}
|
|
181
|
+
yield new Promise((resolve, reject) => {
|
|
182
|
+
const subscription = this.eventObservable.subscribe({
|
|
183
|
+
next(event) {
|
|
184
|
+
if (event.type === PoolEventType.taskQueueDrained || event.type === PoolEventType.terminated) {
|
|
185
|
+
subscription.unsubscribe();
|
|
186
|
+
resolve(void 0);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
complete() {
|
|
190
|
+
// The pool was terminated while we were waiting; resolve instead of hanging.
|
|
191
|
+
subscription.unsubscribe();
|
|
192
|
+
resolve(void 0);
|
|
193
|
+
},
|
|
194
|
+
error: reject // make a pool-wide error reject the completed() result promise
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
yield allSettled(getCurrentlyRunningTasks());
|
|
198
|
+
failureSubscription.unsubscribe();
|
|
199
|
+
return taskFailures;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
completed() {
|
|
203
|
+
return __awaiter(this, arguments, void 0, function* (allowResolvingImmediately = false) {
|
|
204
|
+
const settlementPromise = this.settled(allowResolvingImmediately);
|
|
205
|
+
const earlyExitPromise = new Promise((resolve, reject) => {
|
|
206
|
+
const subscription = this.eventObservable.subscribe({
|
|
207
|
+
next(event) {
|
|
208
|
+
if (event.type === PoolEventType.taskQueueDrained || event.type === PoolEventType.terminated) {
|
|
209
|
+
subscription.unsubscribe();
|
|
210
|
+
resolve(settlementPromise);
|
|
211
|
+
}
|
|
212
|
+
else if (event.type === PoolEventType.taskFailed) {
|
|
213
|
+
subscription.unsubscribe();
|
|
214
|
+
reject(event.error);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
complete() {
|
|
218
|
+
// The pool was terminated while we were waiting; resolve instead of hanging.
|
|
219
|
+
subscription.unsubscribe();
|
|
220
|
+
resolve(settlementPromise);
|
|
221
|
+
},
|
|
222
|
+
error: reject // make a pool-wide error reject the completed() result promise
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
const errors = yield Promise.race([
|
|
226
|
+
settlementPromise,
|
|
227
|
+
earlyExitPromise
|
|
228
|
+
]);
|
|
229
|
+
if (errors.length > 0) {
|
|
230
|
+
throw errors[0];
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
events() {
|
|
235
|
+
return this.eventObservable;
|
|
236
|
+
}
|
|
237
|
+
queue(taskFunction) {
|
|
238
|
+
const { maxQueuedJobs = Infinity } = this.options;
|
|
239
|
+
if (this.isClosing) {
|
|
240
|
+
throw Error(`Cannot schedule pool tasks after terminate() has been called.`);
|
|
241
|
+
}
|
|
242
|
+
if (this.initErrors.length > 0) {
|
|
243
|
+
throw this.initErrors[0];
|
|
244
|
+
}
|
|
245
|
+
// Check the queue limit before taskCompletion() below subscribes to the
|
|
246
|
+
// pool events — throwing after would leak that subscription.
|
|
247
|
+
if (this.taskQueue.length >= maxQueuedJobs) {
|
|
248
|
+
throw Error("Maximum number of pool tasks queued. Refusing to queue another one.\n" +
|
|
249
|
+
"This usually happens for one of two reasons: We are either at peak " +
|
|
250
|
+
"workload right now or some tasks just won't finish, thus blocking the pool.");
|
|
251
|
+
}
|
|
252
|
+
const taskID = this.nextTaskID++;
|
|
253
|
+
const taskCompletion = this.taskCompletion(taskID);
|
|
254
|
+
taskCompletion.catch((error) => {
|
|
255
|
+
// Prevent unhandled rejections here as we assume the user will use
|
|
256
|
+
// `pool.completed()`, `pool.settled()` or `task.catch()` to handle errors
|
|
257
|
+
this.debug(`Task #${taskID} errored:`, error);
|
|
258
|
+
});
|
|
259
|
+
const task = {
|
|
260
|
+
id: taskID,
|
|
261
|
+
run: taskFunction,
|
|
262
|
+
cancel: () => {
|
|
263
|
+
if (this.taskQueue.indexOf(task) === -1)
|
|
264
|
+
return;
|
|
265
|
+
this.taskQueue = this.taskQueue.filter(someTask => someTask !== task);
|
|
266
|
+
this.eventSubject.next({
|
|
267
|
+
type: PoolEventType.taskCanceled,
|
|
268
|
+
taskID: task.id
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
then: taskCompletion.then.bind(taskCompletion)
|
|
272
|
+
};
|
|
273
|
+
this.debug(`Queueing task #${task.id}...`);
|
|
274
|
+
this.taskQueue.push(task);
|
|
275
|
+
this.eventSubject.next({
|
|
276
|
+
type: PoolEventType.taskQueued,
|
|
277
|
+
taskID: task.id
|
|
278
|
+
});
|
|
279
|
+
this.scheduleWork();
|
|
280
|
+
return task;
|
|
281
|
+
}
|
|
282
|
+
terminate(force) {
|
|
283
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
284
|
+
this.isClosing = true;
|
|
285
|
+
if (!force) {
|
|
286
|
+
yield this.completed(true);
|
|
287
|
+
}
|
|
288
|
+
this.eventSubject.next({
|
|
289
|
+
type: PoolEventType.terminated,
|
|
290
|
+
remainingQueue: [...this.taskQueue]
|
|
291
|
+
});
|
|
292
|
+
this.eventSubject.complete();
|
|
293
|
+
yield Promise.all(this.workers.map((worker) => __awaiter(this, void 0, void 0, function* () {
|
|
294
|
+
try {
|
|
295
|
+
yield Thread.terminate(yield worker.init);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
// The worker never finished initializing (e.g. it hit the init
|
|
299
|
+
// timeout), so `worker.init` rejected and there is no thread to
|
|
300
|
+
// terminate here — spawn() already tore down the underlying worker.
|
|
301
|
+
this.debug("Worker did not initialize; nothing to terminate:", error);
|
|
302
|
+
}
|
|
303
|
+
})));
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
WorkerPool.EventType = PoolEventType;
|
|
308
|
+
/**
|
|
309
|
+
* Thread pool constructor. Creates a new pool and spawns its worker threads.
|
|
310
|
+
*/
|
|
311
|
+
function PoolConstructor(spawnWorker, optionsOrSize) {
|
|
312
|
+
// The function exists only so we don't need to use `new` to create a pool (we still can, though).
|
|
313
|
+
// If the Pool is a class or not is an implementation detail that should not concern the user.
|
|
314
|
+
// The explicit `Pool<ThreadType>` return type keeps the private `WorkerPool` class out of the
|
|
315
|
+
// public type surface, so consumers can name/re-export the pool type (#417).
|
|
316
|
+
return new WorkerPool(spawnWorker, optionsOrSize);
|
|
317
|
+
}
|
|
318
|
+
PoolConstructor.EventType = PoolEventType;
|
|
319
|
+
/**
|
|
320
|
+
* Thread pool constructor. Creates a new pool and spawns its worker threads.
|
|
321
|
+
*/
|
|
322
|
+
const Pool = PoolConstructor;
|
|
323
|
+
|
|
324
|
+
export { Pool, PoolEventType, Thread };
|
|
@@ -0,0 +1,14 @@
|
|
|
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 { Worker } from './index.mjs';
|
|
8
|
+
|
|
9
|
+
if (typeof global !== "undefined") {
|
|
10
|
+
global.Worker = Worker;
|
|
11
|
+
}
|
|
12
|
+
else if (typeof window !== "undefined") {
|
|
13
|
+
window.Worker = Worker;
|
|
14
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
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 { Observable } from 'observable-fns';
|
|
9
|
+
import { deserialize } from '../common.mjs';
|
|
10
|
+
import { createPromiseWithResolver } from '../promise.mjs';
|
|
11
|
+
import { $worker, $terminate, $events, $errors } from '../symbols.mjs';
|
|
12
|
+
import { WorkerEventType } from '../types/master.mjs';
|
|
13
|
+
import { createProxyModule, createProxyFunction } from './invocation-proxy.mjs';
|
|
14
|
+
|
|
15
|
+
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
16
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
17
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
18
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
19
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
20
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
21
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
const debugMessages = DebugLogger("threads:master:messages");
|
|
25
|
+
const debugSpawn = DebugLogger("threads:master:spawn");
|
|
26
|
+
const debugThreadUtils = DebugLogger("threads:master:thread-utils");
|
|
27
|
+
const isInitMessage = (data) => data && data.type === "init";
|
|
28
|
+
const isUncaughtErrorMessage = (data) => data && data.type === "uncaughtError";
|
|
29
|
+
const initMessageTimeout = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.THREADS_WORKER_INIT_TIMEOUT
|
|
30
|
+
? Number.parseInt(process.env.THREADS_WORKER_INIT_TIMEOUT, 10)
|
|
31
|
+
: 10000;
|
|
32
|
+
function withTimeout(promise, timeoutInMs, errorMessage) {
|
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
let timeoutHandle;
|
|
35
|
+
const timeout = new Promise((resolve, reject) => {
|
|
36
|
+
timeoutHandle = setTimeout(() => reject(Error(errorMessage)), timeoutInMs);
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
return yield Promise.race([
|
|
40
|
+
promise,
|
|
41
|
+
timeout
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
// Always clear the timer, even when `promise` rejects. Otherwise a pending
|
|
46
|
+
// timeout keeps the event loop alive until it fires (which on a failed
|
|
47
|
+
// spawn showed up as ava "failed to exit" on slower runners).
|
|
48
|
+
clearTimeout(timeoutHandle);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function receiveInitMessage(worker) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const messageHandler = ((event) => {
|
|
55
|
+
debugMessages("Message from worker before finishing initialization:", event.data);
|
|
56
|
+
if (isInitMessage(event.data)) {
|
|
57
|
+
worker.removeEventListener("message", messageHandler);
|
|
58
|
+
resolve(event.data);
|
|
59
|
+
}
|
|
60
|
+
else if (isUncaughtErrorMessage(event.data)) {
|
|
61
|
+
worker.removeEventListener("message", messageHandler);
|
|
62
|
+
reject(deserialize(event.data.error));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
worker.addEventListener("message", messageHandler);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function createEventObservable(worker, workerTermination) {
|
|
69
|
+
return new Observable(observer => {
|
|
70
|
+
const messageHandler = ((messageEvent) => {
|
|
71
|
+
const workerEvent = {
|
|
72
|
+
type: WorkerEventType.message,
|
|
73
|
+
data: messageEvent.data
|
|
74
|
+
};
|
|
75
|
+
observer.next(workerEvent);
|
|
76
|
+
});
|
|
77
|
+
const rejectionHandler = ((errorEvent) => {
|
|
78
|
+
debugThreadUtils("Unhandled promise rejection event in thread:", errorEvent);
|
|
79
|
+
const workerEvent = {
|
|
80
|
+
type: WorkerEventType.internalError,
|
|
81
|
+
error: Error(errorEvent.reason)
|
|
82
|
+
};
|
|
83
|
+
observer.next(workerEvent);
|
|
84
|
+
});
|
|
85
|
+
let finished = false;
|
|
86
|
+
const removeListeners = () => {
|
|
87
|
+
worker.removeEventListener("message", messageHandler);
|
|
88
|
+
worker.removeEventListener("unhandledrejection", rejectionHandler);
|
|
89
|
+
worker.removeEventListener("exit", exitHandler);
|
|
90
|
+
};
|
|
91
|
+
const finish = () => {
|
|
92
|
+
if (finished)
|
|
93
|
+
return;
|
|
94
|
+
finished = true;
|
|
95
|
+
const terminationEvent = {
|
|
96
|
+
type: WorkerEventType.termination
|
|
97
|
+
};
|
|
98
|
+
removeListeners();
|
|
99
|
+
observer.next(terminationEvent);
|
|
100
|
+
observer.complete();
|
|
101
|
+
};
|
|
102
|
+
// A worker can also go away without Thread.terminate() being called (crash,
|
|
103
|
+
// process.exit() in the worker). Complete the observable and detach the
|
|
104
|
+
// listeners in that case, too, instead of leaving them behind forever.
|
|
105
|
+
const exitHandler = (() => finish());
|
|
106
|
+
worker.addEventListener("message", messageHandler);
|
|
107
|
+
worker.addEventListener("unhandledrejection", rejectionHandler);
|
|
108
|
+
worker.addEventListener("exit", exitHandler);
|
|
109
|
+
workerTermination.then(finish);
|
|
110
|
+
return removeListeners;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function createTerminator(worker) {
|
|
114
|
+
const [termination, resolver] = createPromiseWithResolver();
|
|
115
|
+
const terminate = () => __awaiter(this, void 0, void 0, function* () {
|
|
116
|
+
debugThreadUtils("Terminating worker");
|
|
117
|
+
// Newer versions of worker_threads workers return a promise
|
|
118
|
+
yield worker.terminate();
|
|
119
|
+
resolver();
|
|
120
|
+
});
|
|
121
|
+
return { terminate, termination };
|
|
122
|
+
}
|
|
123
|
+
function setPrivateThreadProps(raw, worker, workerEvents, terminate) {
|
|
124
|
+
const workerErrors = workerEvents
|
|
125
|
+
.filter(event => event.type === WorkerEventType.internalError)
|
|
126
|
+
.map(errorEvent => errorEvent.error);
|
|
127
|
+
return Object.assign(raw, {
|
|
128
|
+
[$errors]: workerErrors,
|
|
129
|
+
[$events]: workerEvents,
|
|
130
|
+
[$terminate]: terminate,
|
|
131
|
+
[$worker]: worker
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Spawn a new thread. Takes a fresh worker instance, wraps it in a thin
|
|
136
|
+
* abstraction layer to provide the transparent API and verifies that
|
|
137
|
+
* the worker has initialized successfully.
|
|
138
|
+
*
|
|
139
|
+
* @param worker Instance of `Worker`. Either a web worker or a `worker_threads` worker.
|
|
140
|
+
* @param [options]
|
|
141
|
+
* @param [options.timeout] Init message timeout. Default: 10000 or set by environment variable.
|
|
142
|
+
*/
|
|
143
|
+
function spawn(worker, options) {
|
|
144
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
145
|
+
var _a, _b, _c, _d;
|
|
146
|
+
debugSpawn("Initializing new thread");
|
|
147
|
+
const timeout = options && options.timeout ? options.timeout : initMessageTimeout;
|
|
148
|
+
let initMessage;
|
|
149
|
+
try {
|
|
150
|
+
initMessage = yield withTimeout(receiveInitMessage(worker), timeout, `Timeout: Did not receive an init message from worker after ${timeout}ms. Make sure the worker calls expose().`);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
// The worker failed to initialise (e.g. it threw before calling expose(),
|
|
154
|
+
// or never sent an init message). Tear it down so it does not leak a live
|
|
155
|
+
// worker handle and keep the process from exiting.
|
|
156
|
+
yield Promise.resolve((_b = (_a = worker).terminate) === null || _b === void 0 ? void 0 : _b.call(_a)).catch(() => undefined);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
const exposed = initMessage.exposed;
|
|
160
|
+
const { termination, terminate } = createTerminator(worker);
|
|
161
|
+
const events = createEventObservable(worker, termination);
|
|
162
|
+
if (exposed.type === "function") {
|
|
163
|
+
const proxy = createProxyFunction(worker);
|
|
164
|
+
return setPrivateThreadProps(proxy, worker, events, terminate);
|
|
165
|
+
}
|
|
166
|
+
else if (exposed.type === "module") {
|
|
167
|
+
const proxy = createProxyModule(worker, exposed.methods);
|
|
168
|
+
return setPrivateThreadProps(proxy, worker, events, terminate);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const type = exposed.type;
|
|
172
|
+
// Same teardown as a failed init: don't leak a live worker handle.
|
|
173
|
+
yield Promise.resolve((_d = (_c = worker).terminate) === null || _d === void 0 ? void 0 : _d.call(_c)).catch(() => undefined);
|
|
174
|
+
throw Error(`Worker init message states unexpected type of expose(): ${type}`);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { spawn };
|
|
@@ -0,0 +1,28 @@
|
|
|
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 { $terminate, $events, $errors } from '../symbols.mjs';
|
|
8
|
+
|
|
9
|
+
function fail(message) {
|
|
10
|
+
throw Error(message);
|
|
11
|
+
}
|
|
12
|
+
/** Thread utility functions. Use them to manage or inspect a `spawn()`-ed thread. */
|
|
13
|
+
const Thread = {
|
|
14
|
+
/** Return an observable that can be used to subscribe to all errors happening in the thread. */
|
|
15
|
+
errors(thread) {
|
|
16
|
+
return thread[$errors] || fail("Error observable not found. Make sure to pass a thread instance as returned by the spawn() promise.");
|
|
17
|
+
},
|
|
18
|
+
/** Return an observable that can be used to subscribe to internal events happening in the thread. Useful for debugging. */
|
|
19
|
+
events(thread) {
|
|
20
|
+
return thread[$events] || fail("Events observable not found. Make sure to pass a thread instance as returned by the spawn() promise.");
|
|
21
|
+
},
|
|
22
|
+
/** Terminate a thread. Remember to terminate every thread when you are done using it. */
|
|
23
|
+
terminate(thread) {
|
|
24
|
+
return thread[$terminate]();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { Thread };
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { createRequire } from 'module';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Runtime module/path resolution for the Node.js implementation.
|
|
12
|
+
*
|
|
13
|
+
* Spawning a worker needs real filesystem paths at runtime, but webpack
|
|
14
|
+
* rewrites `require`, `require.resolve` and `__dirname` at compile time.
|
|
15
|
+
* `__non_webpack_require__` is webpack's documented escape hatch: it compiles
|
|
16
|
+
* to the untouched `require` of the output bundle, so resolution happens at
|
|
17
|
+
* runtime against the real filesystem. Every other environment (plain Node,
|
|
18
|
+
* esbuild, rollup, Vite) keeps `createRequire` and `__dirname` intact, so no
|
|
19
|
+
* `eval()` tricks are needed there.
|
|
20
|
+
*
|
|
21
|
+
* This module is only ever loaded on Node targets: the package.json "browser"
|
|
22
|
+
* field maps its sole importers to `false` for browser bundles.
|
|
23
|
+
*/
|
|
24
|
+
/** Whether this code is running inside a webpack bundle. */
|
|
25
|
+
function isWebpackBundle() {
|
|
26
|
+
return typeof __non_webpack_require__ === "function";
|
|
27
|
+
}
|
|
28
|
+
let nodeRequire;
|
|
29
|
+
/**
|
|
30
|
+
* Returns a `require` that resolves against the real filesystem at runtime,
|
|
31
|
+
* even inside a bundle.
|
|
32
|
+
*/
|
|
33
|
+
function getNodeRequire() {
|
|
34
|
+
if (typeof __non_webpack_require__ === "function") {
|
|
35
|
+
return __non_webpack_require__;
|
|
36
|
+
}
|
|
37
|
+
if (!nodeRequire) {
|
|
38
|
+
// CJS build: `__filename` is this module's real path. ESM build (esm/):
|
|
39
|
+
// the build banner derives `__filename` from `import.meta.url`. In an
|
|
40
|
+
// environment that defines neither (dist-esm bundled straight to ESM by a
|
|
41
|
+
// non-webpack bundler), resolve from the current working directory.
|
|
42
|
+
const base = typeof __filename !== "undefined"
|
|
43
|
+
? __filename
|
|
44
|
+
: path.join(process.cwd(), "__threadsx_resolve__.js");
|
|
45
|
+
nodeRequire = createRequire(base);
|
|
46
|
+
}
|
|
47
|
+
return nodeRequire;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Directory to resolve relative worker paths against when no caller path and
|
|
51
|
+
* no base URL are available. Matches this module's directory where the runtime
|
|
52
|
+
* provides one, and falls back to the current working directory.
|
|
53
|
+
*/
|
|
54
|
+
function getModuleDirname() {
|
|
55
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { getModuleDirname, getNodeRequire, isWebpackBundle };
|