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.
Files changed (115) hide show
  1. package/dist/errors.d.ts +16 -0
  2. package/dist/errors.js +31 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +3 -1
  5. package/dist/master/implementation.node.js +34 -18
  6. package/dist/master/invocation-proxy.js +45 -11
  7. package/dist/master/pool.d.ts +1 -24
  8. package/dist/master/pool.js +41 -8
  9. package/dist/master/spawn.js +24 -7
  10. package/dist/node-require.d.ts +13 -0
  11. package/dist/node-require.js +87 -0
  12. package/dist/transferable.d.ts +1 -0
  13. package/dist/types/master.d.ts +22 -1
  14. package/dist/types/master.js +0 -1
  15. package/dist/types/worker.d.ts +1 -0
  16. package/dist/worker/implementation.browser.d.ts +1 -1
  17. package/dist/worker/implementation.worker_threads.d.ts +1 -1
  18. package/dist/worker_threads.d.ts +3 -7
  19. package/dist/worker_threads.js +35 -11
  20. package/dist-esm/common.js +1 -0
  21. package/dist-esm/common.js.map +1 -0
  22. package/dist-esm/errors.js +27 -0
  23. package/dist-esm/errors.js.map +1 -0
  24. package/dist-esm/index.js +2 -0
  25. package/dist-esm/index.js.map +1 -0
  26. package/dist-esm/master/get-bundle-url.browser.js +1 -0
  27. package/dist-esm/master/get-bundle-url.browser.js.map +1 -0
  28. package/dist-esm/master/implementation.browser.js +1 -0
  29. package/dist-esm/master/implementation.browser.js.map +1 -0
  30. package/dist-esm/master/implementation.js +1 -0
  31. package/dist-esm/master/implementation.js.map +1 -0
  32. package/dist-esm/master/implementation.node.js +35 -18
  33. package/dist-esm/master/implementation.node.js.map +1 -0
  34. package/dist-esm/master/index.js +1 -0
  35. package/dist-esm/master/index.js.map +1 -0
  36. package/dist-esm/master/invocation-proxy.js +46 -11
  37. package/dist-esm/master/invocation-proxy.js.map +1 -0
  38. package/dist-esm/master/pool-types.js +1 -0
  39. package/dist-esm/master/pool-types.js.map +1 -0
  40. package/dist-esm/master/pool.js +42 -8
  41. package/dist-esm/master/pool.js.map +1 -0
  42. package/dist-esm/master/register.js +1 -0
  43. package/dist-esm/master/register.js.map +1 -0
  44. package/dist-esm/master/spawn.js +25 -7
  45. package/dist-esm/master/spawn.js.map +1 -0
  46. package/dist-esm/master/thread.js +1 -0
  47. package/dist-esm/master/thread.js.map +1 -0
  48. package/dist-esm/node-require.js +50 -0
  49. package/dist-esm/node-require.js.map +1 -0
  50. package/dist-esm/observable-promise.js +1 -0
  51. package/dist-esm/observable-promise.js.map +1 -0
  52. package/dist-esm/observable.js +1 -0
  53. package/dist-esm/observable.js.map +1 -0
  54. package/dist-esm/ponyfills.js +1 -0
  55. package/dist-esm/ponyfills.js.map +1 -0
  56. package/dist-esm/promise.js +1 -0
  57. package/dist-esm/promise.js.map +1 -0
  58. package/dist-esm/serializers.js +1 -0
  59. package/dist-esm/serializers.js.map +1 -0
  60. package/dist-esm/symbols.js +1 -0
  61. package/dist-esm/symbols.js.map +1 -0
  62. package/dist-esm/transferable.js +1 -0
  63. package/dist-esm/transferable.js.map +1 -0
  64. package/dist-esm/types/master.js +1 -1
  65. package/dist-esm/types/master.js.map +1 -0
  66. package/dist-esm/types/messages.js +1 -0
  67. package/dist-esm/types/messages.js.map +1 -0
  68. package/dist-esm/types/worker.js +1 -0
  69. package/dist-esm/types/worker.js.map +1 -0
  70. package/dist-esm/worker/bundle-entry.js +1 -0
  71. package/dist-esm/worker/bundle-entry.js.map +1 -0
  72. package/dist-esm/worker/implementation.browser.js +1 -0
  73. package/dist-esm/worker/implementation.browser.js.map +1 -0
  74. package/dist-esm/worker/implementation.js +1 -0
  75. package/dist-esm/worker/implementation.js.map +1 -0
  76. package/dist-esm/worker/implementation.worker_threads.js +1 -0
  77. package/dist-esm/worker/implementation.worker_threads.js.map +1 -0
  78. package/dist-esm/worker/index.js +1 -0
  79. package/dist-esm/worker/index.js.map +1 -0
  80. package/dist-esm/worker_threads.js +3 -11
  81. package/dist-esm/worker_threads.js.map +1 -0
  82. package/esm/common.mjs +20 -0
  83. package/esm/errors.mjs +34 -0
  84. package/esm/index.mjs +16 -0
  85. package/esm/master/get-bundle-url.browser.mjs +32 -0
  86. package/esm/master/implementation.browser.mjs +76 -0
  87. package/esm/master/implementation.mjs +24 -0
  88. package/esm/master/implementation.node.mjs +186 -0
  89. package/esm/master/index.mjs +17 -0
  90. package/esm/master/invocation-proxy.mjs +164 -0
  91. package/esm/master/pool-types.mjs +20 -0
  92. package/esm/master/pool.mjs +324 -0
  93. package/esm/master/register.mjs +14 -0
  94. package/esm/master/spawn.mjs +179 -0
  95. package/esm/master/thread.mjs +28 -0
  96. package/esm/node-require.mjs +58 -0
  97. package/esm/observable-promise.mjs +155 -0
  98. package/esm/observable.mjs +45 -0
  99. package/esm/ponyfills.mjs +26 -0
  100. package/esm/promise.mjs +32 -0
  101. package/esm/serializers.mjs +55 -0
  102. package/esm/symbols.mjs +13 -0
  103. package/esm/transferable.mjs +31 -0
  104. package/esm/types/master.mjs +15 -0
  105. package/esm/types/messages.mjs +25 -0
  106. package/esm/worker/implementation.browser.mjs +31 -0
  107. package/esm/worker/implementation.mjs +21 -0
  108. package/esm/worker/implementation.worker_threads.mjs +44 -0
  109. package/esm/worker/index.mjs +224 -0
  110. package/esm/worker_threads.mjs +13 -0
  111. package/package.json +32 -24
  112. package/index.mjs +0 -4
  113. package/observable.mjs +0 -1
  114. package/register.mjs +0 -2
  115. 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 };