threadsx 2.0.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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +164 -0
  3. package/dist/common.d.ts +4 -0
  4. package/dist/common.js +16 -0
  5. package/dist/index.d.ts +7 -0
  6. package/dist/index.js +26 -0
  7. package/dist/master/get-bundle-url.browser.d.ts +3 -0
  8. package/dist/master/get-bundle-url.browser.js +28 -0
  9. package/dist/master/implementation.browser.d.ts +4 -0
  10. package/dist/master/implementation.browser.js +72 -0
  11. package/dist/master/implementation.d.ts +6 -0
  12. package/dist/master/implementation.js +51 -0
  13. package/dist/master/implementation.node.d.ts +4 -0
  14. package/dist/master/implementation.node.js +196 -0
  15. package/dist/master/index.d.ts +14 -0
  16. package/dist/master/index.js +15 -0
  17. package/dist/master/invocation-proxy.d.ts +3 -0
  18. package/dist/master/invocation-proxy.js +128 -0
  19. package/dist/master/pool-types.d.ts +65 -0
  20. package/dist/master/pool-types.js +15 -0
  21. package/dist/master/pool.d.ts +89 -0
  22. package/dist/master/pool.js +290 -0
  23. package/dist/master/register.d.ts +1 -0
  24. package/dist/master/register.js +9 -0
  25. package/dist/master/spawn.d.ts +20 -0
  26. package/dist/master/spawn.js +159 -0
  27. package/dist/master/thread.d.ts +12 -0
  28. package/dist/master/thread.js +22 -0
  29. package/dist/observable-promise.d.ts +38 -0
  30. package/dist/observable-promise.js +150 -0
  31. package/dist/observable.d.ts +19 -0
  32. package/dist/observable.js +40 -0
  33. package/dist/ponyfills.d.ts +8 -0
  34. package/dist/ponyfills.js +21 -0
  35. package/dist/promise.d.ts +5 -0
  36. package/dist/promise.js +27 -0
  37. package/dist/serializers.d.ts +16 -0
  38. package/dist/serializers.js +51 -0
  39. package/dist/symbols.d.ts +5 -0
  40. package/dist/symbols.js +8 -0
  41. package/dist/transferable.d.ts +42 -0
  42. package/dist/transferable.js +26 -0
  43. package/dist/types/master.d.ts +98 -0
  44. package/dist/types/master.js +12 -0
  45. package/dist/types/messages.d.ts +62 -0
  46. package/dist/types/messages.js +20 -0
  47. package/dist/types/worker.d.ts +11 -0
  48. package/dist/types/worker.js +2 -0
  49. package/dist/worker/bundle-entry.d.ts +1 -0
  50. package/dist/worker/bundle-entry.js +24 -0
  51. package/dist/worker/implementation.browser.d.ts +6 -0
  52. package/dist/worker/implementation.browser.js +25 -0
  53. package/dist/worker/implementation.d.ts +3 -0
  54. package/dist/worker/implementation.js +18 -0
  55. package/dist/worker/implementation.worker_threads.d.ts +8 -0
  56. package/dist/worker/implementation.worker_threads.js +42 -0
  57. package/dist/worker/index.d.ts +13 -0
  58. package/dist/worker/index.js +224 -0
  59. package/dist/worker_threads.d.ts +8 -0
  60. package/dist/worker_threads.js +16 -0
  61. package/dist-esm/common.js +11 -0
  62. package/dist-esm/index.js +5 -0
  63. package/dist-esm/master/get-bundle-url.browser.js +25 -0
  64. package/dist-esm/master/implementation.browser.js +67 -0
  65. package/dist-esm/master/implementation.js +15 -0
  66. package/dist-esm/master/implementation.node.js +155 -0
  67. package/dist-esm/master/index.js +9 -0
  68. package/dist-esm/master/invocation-proxy.js +121 -0
  69. package/dist-esm/master/pool-types.js +12 -0
  70. package/dist-esm/master/pool.js +283 -0
  71. package/dist-esm/master/register.js +7 -0
  72. package/dist-esm/master/spawn.js +153 -0
  73. package/dist-esm/master/thread.js +19 -0
  74. package/dist-esm/observable-promise.js +146 -0
  75. package/dist-esm/observable.js +36 -0
  76. package/dist-esm/ponyfills.js +18 -0
  77. package/dist-esm/promise.js +24 -0
  78. package/dist-esm/serializers.js +47 -0
  79. package/dist-esm/symbols.js +5 -0
  80. package/dist-esm/transferable.js +22 -0
  81. package/dist-esm/types/master.js +9 -0
  82. package/dist-esm/types/messages.js +17 -0
  83. package/dist-esm/types/worker.js +1 -0
  84. package/dist-esm/worker/bundle-entry.js +8 -0
  85. package/dist-esm/worker/implementation.browser.js +23 -0
  86. package/dist-esm/worker/implementation.js +13 -0
  87. package/dist-esm/worker/implementation.worker_threads.js +37 -0
  88. package/dist-esm/worker/index.js +215 -0
  89. package/dist-esm/worker_threads.js +13 -0
  90. package/index.mjs +4 -0
  91. package/observable.mjs +1 -0
  92. package/package.json +168 -0
  93. package/register.mjs +2 -0
  94. package/worker.mjs +1 -0
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /*
3
+ * This source file contains the code for proxying calls in the master thread to calls in the workers
4
+ * by `.postMessage()`-ing.
5
+ *
6
+ * Keep in mind that this code can make or break the program's performance! Need to optimize more…
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createProxyFunction = createProxyFunction;
13
+ exports.createProxyModule = createProxyModule;
14
+ const debug_1 = __importDefault(require("debug"));
15
+ const observable_fns_1 = require("observable-fns");
16
+ const common_1 = require("../common");
17
+ const observable_promise_1 = require("../observable-promise");
18
+ const transferable_1 = require("../transferable");
19
+ const messages_1 = require("../types/messages");
20
+ const debugMessages = (0, debug_1.default)("threads:master:messages");
21
+ let nextJobUID = 1;
22
+ const dedupe = (array) => Array.from(new Set(array));
23
+ const isJobErrorMessage = (data) => data && data.type === messages_1.WorkerMessageType.error;
24
+ const isJobResultMessage = (data) => data && data.type === messages_1.WorkerMessageType.result;
25
+ const isJobStartMessage = (data) => data && data.type === messages_1.WorkerMessageType.running;
26
+ function createObservableForJob(worker, jobUID) {
27
+ return new observable_fns_1.Observable(observer => {
28
+ let asyncType;
29
+ const messageHandler = ((event) => {
30
+ debugMessages("Message from worker:", event.data);
31
+ if (!event.data || event.data.uid !== jobUID)
32
+ return;
33
+ if (isJobStartMessage(event.data)) {
34
+ asyncType = event.data.resultType;
35
+ }
36
+ else if (isJobResultMessage(event.data)) {
37
+ if (asyncType === "promise") {
38
+ if (typeof event.data.payload !== "undefined") {
39
+ observer.next((0, common_1.deserialize)(event.data.payload));
40
+ }
41
+ observer.complete();
42
+ worker.removeEventListener("message", messageHandler);
43
+ }
44
+ else {
45
+ if (event.data.payload) {
46
+ observer.next((0, common_1.deserialize)(event.data.payload));
47
+ }
48
+ if (event.data.complete) {
49
+ observer.complete();
50
+ worker.removeEventListener("message", messageHandler);
51
+ }
52
+ }
53
+ }
54
+ else if (isJobErrorMessage(event.data)) {
55
+ const error = (0, common_1.deserialize)(event.data.error);
56
+ if (asyncType === "promise" || !asyncType) {
57
+ observer.error(error);
58
+ }
59
+ else {
60
+ observer.error(error);
61
+ }
62
+ worker.removeEventListener("message", messageHandler);
63
+ }
64
+ });
65
+ worker.addEventListener("message", messageHandler);
66
+ return () => {
67
+ if (asyncType === "observable" || !asyncType) {
68
+ const cancelMessage = {
69
+ type: messages_1.MasterMessageType.cancel,
70
+ uid: jobUID
71
+ };
72
+ worker.postMessage(cancelMessage);
73
+ }
74
+ worker.removeEventListener("message", messageHandler);
75
+ };
76
+ });
77
+ }
78
+ function prepareArguments(rawArgs) {
79
+ if (rawArgs.length === 0) {
80
+ // Exit early if possible
81
+ return {
82
+ args: [],
83
+ transferables: []
84
+ };
85
+ }
86
+ const args = [];
87
+ const transferables = [];
88
+ for (const arg of rawArgs) {
89
+ if ((0, transferable_1.isTransferDescriptor)(arg)) {
90
+ args.push((0, common_1.serialize)(arg.send));
91
+ transferables.push(...arg.transferables);
92
+ }
93
+ else {
94
+ args.push((0, common_1.serialize)(arg));
95
+ }
96
+ }
97
+ return {
98
+ args,
99
+ transferables: transferables.length === 0 ? transferables : dedupe(transferables)
100
+ };
101
+ }
102
+ function createProxyFunction(worker, method) {
103
+ return ((...rawArgs) => {
104
+ const uid = nextJobUID++;
105
+ const { args, transferables } = prepareArguments(rawArgs);
106
+ const runMessage = {
107
+ type: messages_1.MasterMessageType.run,
108
+ uid,
109
+ method,
110
+ args
111
+ };
112
+ debugMessages("Sending command to run function to worker:", runMessage);
113
+ try {
114
+ worker.postMessage(runMessage, transferables);
115
+ }
116
+ catch (error) {
117
+ return observable_promise_1.ObservablePromise.from(Promise.reject(error));
118
+ }
119
+ return observable_promise_1.ObservablePromise.from((0, observable_fns_1.multicast)(createObservableForJob(worker, uid)));
120
+ });
121
+ }
122
+ function createProxyModule(worker, methodNames) {
123
+ const proxy = {};
124
+ for (const methodName of methodNames) {
125
+ proxy[methodName] = createProxyFunction(worker, methodName);
126
+ }
127
+ return proxy;
128
+ }
@@ -0,0 +1,65 @@
1
+ import { Thread } from "./thread";
2
+ /** Pool event type. Specifies the type of each `PoolEvent`. */
3
+ export declare enum PoolEventType {
4
+ initialized = "initialized",
5
+ taskCanceled = "taskCanceled",
6
+ taskCompleted = "taskCompleted",
7
+ taskFailed = "taskFailed",
8
+ taskQueued = "taskQueued",
9
+ taskQueueDrained = "taskQueueDrained",
10
+ taskStart = "taskStart",
11
+ terminated = "terminated"
12
+ }
13
+ export type TaskRunFunction<ThreadType extends Thread, Return> = (worker: ThreadType) => Promise<Return>;
14
+ /** Pool event. Subscribe to those events using `pool.events()`. Useful for debugging. */
15
+ export type PoolEvent<ThreadType extends Thread> = {
16
+ type: PoolEventType.initialized;
17
+ size: number;
18
+ } | {
19
+ type: PoolEventType.taskQueued;
20
+ taskID: number;
21
+ } | {
22
+ type: PoolEventType.taskQueueDrained;
23
+ } | {
24
+ type: PoolEventType.taskStart;
25
+ taskID: number;
26
+ workerID: number;
27
+ } | {
28
+ type: PoolEventType.taskCompleted;
29
+ returnValue: any;
30
+ taskID: number;
31
+ workerID: number;
32
+ } | {
33
+ type: PoolEventType.taskFailed;
34
+ error: Error;
35
+ taskID: number;
36
+ workerID: number;
37
+ } | {
38
+ type: PoolEventType.taskCanceled;
39
+ taskID: number;
40
+ } | {
41
+ type: PoolEventType.terminated;
42
+ remainingQueue: Array<QueuedTask<ThreadType, any>>;
43
+ };
44
+ export interface WorkerDescriptor<ThreadType extends Thread> {
45
+ init: Promise<ThreadType>;
46
+ runningTasks: Array<Promise<any>>;
47
+ }
48
+ /**
49
+ * Task that has been `pool.queued()`-ed.
50
+ */
51
+ export interface QueuedTask<ThreadType extends Thread, Return> {
52
+ /** @private */
53
+ id: number;
54
+ /** @private */
55
+ run: TaskRunFunction<ThreadType, Return>;
56
+ /**
57
+ * Queued tasks can be cancelled until the pool starts running them on a worker thread.
58
+ */
59
+ cancel(): void;
60
+ /**
61
+ * `QueuedTask` is thenable, so you can `await` it.
62
+ * Resolves when the task has successfully been executed. Rejects if the task fails.
63
+ */
64
+ then: Promise<Return>["then"];
65
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PoolEventType = void 0;
4
+ /** Pool event type. Specifies the type of each `PoolEvent`. */
5
+ var PoolEventType;
6
+ (function (PoolEventType) {
7
+ PoolEventType["initialized"] = "initialized";
8
+ PoolEventType["taskCanceled"] = "taskCanceled";
9
+ PoolEventType["taskCompleted"] = "taskCompleted";
10
+ PoolEventType["taskFailed"] = "taskFailed";
11
+ PoolEventType["taskQueued"] = "taskQueued";
12
+ PoolEventType["taskQueueDrained"] = "taskQueueDrained";
13
+ PoolEventType["taskStart"] = "taskStart";
14
+ PoolEventType["terminated"] = "terminated";
15
+ })(PoolEventType || (exports.PoolEventType = PoolEventType = {}));
@@ -0,0 +1,89 @@
1
+ import { Observable } from "observable-fns";
2
+ import { PoolEvent, PoolEventType, QueuedTask, TaskRunFunction } from "./pool-types";
3
+ import { Thread } from "./thread";
4
+ export { PoolEvent, PoolEventType, QueuedTask, Thread };
5
+ export declare namespace Pool {
6
+ type Event<ThreadType extends Thread = any> = PoolEvent<ThreadType>;
7
+ type EventType = PoolEventType;
8
+ }
9
+ /**
10
+ * Thread pool managing a set of worker threads.
11
+ * Use it to queue tasks that are run on those threads with limited
12
+ * concurrency.
13
+ */
14
+ export interface Pool<ThreadType extends Thread> {
15
+ /**
16
+ * Returns a promise that resolves once the task queue is emptied.
17
+ * Promise will be rejected if any task fails.
18
+ *
19
+ * @param allowResolvingImmediately Set to `true` to resolve immediately if task queue is currently empty.
20
+ */
21
+ completed(allowResolvingImmediately?: boolean): Promise<any>;
22
+ /**
23
+ * Returns a promise that resolves once the task queue is emptied.
24
+ * Failing tasks will not cause the promise to be rejected.
25
+ *
26
+ * @param allowResolvingImmediately Set to `true` to resolve immediately if task queue is currently empty.
27
+ */
28
+ settled(allowResolvingImmediately?: boolean): Promise<Error[]>;
29
+ /**
30
+ * Returns an observable that yields pool events.
31
+ */
32
+ events(): Observable<PoolEvent<ThreadType>>;
33
+ /**
34
+ * Queue a task and return a promise that resolves once the task has been dequeued,
35
+ * started and finished.
36
+ *
37
+ * @param task An async function that takes a thread instance and invokes it.
38
+ */
39
+ queue<Return>(task: TaskRunFunction<ThreadType, Return>): QueuedTask<ThreadType, Return>;
40
+ /**
41
+ * Terminate all pool threads.
42
+ *
43
+ * @param force Set to `true` to kill the thread even if it cannot be stopped gracefully.
44
+ */
45
+ terminate(force?: boolean): Promise<void>;
46
+ }
47
+ export interface PoolOptions {
48
+ /** Maximum no. of tasks to run on one worker thread at a time. Defaults to one. */
49
+ concurrency?: number;
50
+ /** Maximum no. of jobs to be queued for execution before throwing an error. */
51
+ maxQueuedJobs?: number;
52
+ /** Gives that pool a name to be used for debug logging, letting you distinguish between log output of different pools. */
53
+ name?: string;
54
+ /** No. of worker threads to spawn and to be managed by the pool. */
55
+ size?: number;
56
+ }
57
+ declare class WorkerPool<ThreadType extends Thread> implements Pool<ThreadType> {
58
+ static EventType: typeof PoolEventType;
59
+ private readonly debug;
60
+ private readonly eventObservable;
61
+ private readonly options;
62
+ private readonly workers;
63
+ private readonly eventSubject;
64
+ private initErrors;
65
+ private isClosing;
66
+ private nextTaskID;
67
+ private taskQueue;
68
+ constructor(spawnWorker: () => Promise<ThreadType>, optionsOrSize?: number | PoolOptions);
69
+ private findIdlingWorker;
70
+ private runPoolTask;
71
+ private run;
72
+ private scheduleWork;
73
+ private taskCompletion;
74
+ settled(allowResolvingImmediately?: boolean): Promise<Error[]>;
75
+ completed(allowResolvingImmediately?: boolean): Promise<void>;
76
+ events(): Observable<PoolEvent<ThreadType>>;
77
+ queue(taskFunction: TaskRunFunction<ThreadType, any>): QueuedTask<ThreadType, any>;
78
+ terminate(force?: boolean): Promise<void>;
79
+ }
80
+ /**
81
+ * Thread pool constructor. Creates a new pool and spawns its worker threads.
82
+ */
83
+ declare function PoolConstructor<ThreadType extends Thread>(spawnWorker: () => Promise<ThreadType>, optionsOrSize?: number | PoolOptions): WorkerPool<ThreadType>;
84
+ /**
85
+ * Thread pool constructor. Creates a new pool and spawns its worker threads.
86
+ */
87
+ export declare const Pool: typeof PoolConstructor & {
88
+ EventType: typeof PoolEventType;
89
+ };
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.Pool = exports.Thread = exports.PoolEventType = void 0;
16
+ const debug_1 = __importDefault(require("debug"));
17
+ const observable_fns_1 = require("observable-fns");
18
+ const ponyfills_1 = require("../ponyfills");
19
+ const implementation_1 = require("./implementation");
20
+ const pool_types_1 = require("./pool-types");
21
+ Object.defineProperty(exports, "PoolEventType", { enumerable: true, get: function () { return pool_types_1.PoolEventType; } });
22
+ const thread_1 = require("./thread");
23
+ Object.defineProperty(exports, "Thread", { enumerable: true, get: function () { return thread_1.Thread; } });
24
+ let nextPoolID = 1;
25
+ function createArray(size) {
26
+ const array = [];
27
+ for (let index = 0; index < size; index++) {
28
+ array.push(index);
29
+ }
30
+ return array;
31
+ }
32
+ function delay(ms) {
33
+ return new Promise(resolve => setTimeout(resolve, ms));
34
+ }
35
+ function flatMap(array, mapper) {
36
+ return array.reduce((flattened, element) => [...flattened, ...mapper(element)], []);
37
+ }
38
+ function slugify(text) {
39
+ return text.replace(/\W/g, " ").trim().replace(/\s+/g, "-");
40
+ }
41
+ function spawnWorkers(spawnWorker, count) {
42
+ return createArray(count).map(() => ({
43
+ init: spawnWorker(),
44
+ runningTasks: []
45
+ }));
46
+ }
47
+ class WorkerPool {
48
+ constructor(spawnWorker, optionsOrSize) {
49
+ this.eventSubject = new observable_fns_1.Subject();
50
+ this.initErrors = [];
51
+ this.isClosing = false;
52
+ this.nextTaskID = 1;
53
+ this.taskQueue = [];
54
+ const options = typeof optionsOrSize === "number"
55
+ ? { size: optionsOrSize }
56
+ : optionsOrSize || {};
57
+ const { size = implementation_1.defaultPoolSize } = options;
58
+ this.debug = (0, debug_1.default)(`threads:pool:${slugify(options.name || String(nextPoolID++))}`);
59
+ this.options = options;
60
+ this.workers = spawnWorkers(spawnWorker, size);
61
+ this.eventObservable = (0, observable_fns_1.multicast)(observable_fns_1.Observable.from(this.eventSubject));
62
+ Promise.all(this.workers.map(worker => worker.init)).then(() => this.eventSubject.next({
63
+ type: pool_types_1.PoolEventType.initialized,
64
+ size: this.workers.length
65
+ }), error => {
66
+ this.debug("Error while initializing pool worker:", error);
67
+ this.eventSubject.error(error);
68
+ this.initErrors.push(error);
69
+ });
70
+ }
71
+ findIdlingWorker() {
72
+ const { concurrency = 1 } = this.options;
73
+ return this.workers.find(worker => worker.runningTasks.length < concurrency);
74
+ }
75
+ runPoolTask(worker, task) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ const workerID = this.workers.indexOf(worker) + 1;
78
+ this.debug(`Running task #${task.id} on worker #${workerID}...`);
79
+ this.eventSubject.next({
80
+ type: pool_types_1.PoolEventType.taskStart,
81
+ taskID: task.id,
82
+ workerID
83
+ });
84
+ try {
85
+ const returnValue = yield task.run(yield worker.init);
86
+ this.debug(`Task #${task.id} completed successfully`);
87
+ this.eventSubject.next({
88
+ type: pool_types_1.PoolEventType.taskCompleted,
89
+ returnValue,
90
+ taskID: task.id,
91
+ workerID
92
+ });
93
+ }
94
+ catch (error) {
95
+ this.debug(`Task #${task.id} failed`);
96
+ this.eventSubject.next({
97
+ type: pool_types_1.PoolEventType.taskFailed,
98
+ taskID: task.id,
99
+ error: error,
100
+ workerID
101
+ });
102
+ }
103
+ });
104
+ }
105
+ run(worker, task) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ const runPromise = (() => __awaiter(this, void 0, void 0, function* () {
108
+ const removeTaskFromWorkersRunningTasks = () => {
109
+ worker.runningTasks = worker.runningTasks.filter(someRunPromise => someRunPromise !== runPromise);
110
+ };
111
+ // Defer task execution by one tick to give handlers time to subscribe
112
+ yield delay(0);
113
+ try {
114
+ yield this.runPoolTask(worker, task);
115
+ }
116
+ finally {
117
+ removeTaskFromWorkersRunningTasks();
118
+ if (!this.isClosing) {
119
+ this.scheduleWork();
120
+ }
121
+ }
122
+ }))();
123
+ worker.runningTasks.push(runPromise);
124
+ });
125
+ }
126
+ scheduleWork() {
127
+ this.debug(`Attempt de-queueing a task in order to run it...`);
128
+ const availableWorker = this.findIdlingWorker();
129
+ if (!availableWorker)
130
+ return;
131
+ const nextTask = this.taskQueue.shift();
132
+ if (!nextTask) {
133
+ this.debug(`Task queue is empty`);
134
+ this.eventSubject.next({ type: pool_types_1.PoolEventType.taskQueueDrained });
135
+ return;
136
+ }
137
+ this.run(availableWorker, nextTask);
138
+ }
139
+ taskCompletion(taskID) {
140
+ return new Promise((resolve, reject) => {
141
+ const eventSubscription = this.events().subscribe(event => {
142
+ if (event.type === pool_types_1.PoolEventType.taskCompleted && event.taskID === taskID) {
143
+ eventSubscription.unsubscribe();
144
+ resolve(event.returnValue);
145
+ }
146
+ else if (event.type === pool_types_1.PoolEventType.taskFailed && event.taskID === taskID) {
147
+ eventSubscription.unsubscribe();
148
+ reject(event.error);
149
+ }
150
+ else if (event.type === pool_types_1.PoolEventType.terminated) {
151
+ eventSubscription.unsubscribe();
152
+ reject(Error("Pool has been terminated before task was run."));
153
+ }
154
+ });
155
+ });
156
+ }
157
+ settled() {
158
+ return __awaiter(this, arguments, void 0, function* (allowResolvingImmediately = false) {
159
+ const getCurrentlyRunningTasks = () => flatMap(this.workers, worker => worker.runningTasks);
160
+ const taskFailures = [];
161
+ const failureSubscription = this.eventObservable.subscribe(event => {
162
+ if (event.type === pool_types_1.PoolEventType.taskFailed) {
163
+ taskFailures.push(event.error);
164
+ }
165
+ });
166
+ if (this.initErrors.length > 0) {
167
+ return Promise.reject(this.initErrors[0]);
168
+ }
169
+ if (allowResolvingImmediately && this.taskQueue.length === 0) {
170
+ yield (0, ponyfills_1.allSettled)(getCurrentlyRunningTasks());
171
+ return taskFailures;
172
+ }
173
+ yield new Promise((resolve, reject) => {
174
+ const subscription = this.eventObservable.subscribe({
175
+ next(event) {
176
+ if (event.type === pool_types_1.PoolEventType.taskQueueDrained) {
177
+ subscription.unsubscribe();
178
+ resolve(void 0);
179
+ }
180
+ },
181
+ error: reject // make a pool-wide error reject the completed() result promise
182
+ });
183
+ });
184
+ yield (0, ponyfills_1.allSettled)(getCurrentlyRunningTasks());
185
+ failureSubscription.unsubscribe();
186
+ return taskFailures;
187
+ });
188
+ }
189
+ completed() {
190
+ return __awaiter(this, arguments, void 0, function* (allowResolvingImmediately = false) {
191
+ const settlementPromise = this.settled(allowResolvingImmediately);
192
+ const earlyExitPromise = new Promise((resolve, reject) => {
193
+ const subscription = this.eventObservable.subscribe({
194
+ next(event) {
195
+ if (event.type === pool_types_1.PoolEventType.taskQueueDrained) {
196
+ subscription.unsubscribe();
197
+ resolve(settlementPromise);
198
+ }
199
+ else if (event.type === pool_types_1.PoolEventType.taskFailed) {
200
+ subscription.unsubscribe();
201
+ reject(event.error);
202
+ }
203
+ },
204
+ error: reject // make a pool-wide error reject the completed() result promise
205
+ });
206
+ });
207
+ const errors = yield Promise.race([
208
+ settlementPromise,
209
+ earlyExitPromise
210
+ ]);
211
+ if (errors.length > 0) {
212
+ throw errors[0];
213
+ }
214
+ });
215
+ }
216
+ events() {
217
+ return this.eventObservable;
218
+ }
219
+ queue(taskFunction) {
220
+ const { maxQueuedJobs = Infinity } = this.options;
221
+ if (this.isClosing) {
222
+ throw Error(`Cannot schedule pool tasks after terminate() has been called.`);
223
+ }
224
+ if (this.initErrors.length > 0) {
225
+ throw this.initErrors[0];
226
+ }
227
+ const taskID = this.nextTaskID++;
228
+ const taskCompletion = this.taskCompletion(taskID);
229
+ taskCompletion.catch((error) => {
230
+ // Prevent unhandled rejections here as we assume the user will use
231
+ // `pool.completed()`, `pool.settled()` or `task.catch()` to handle errors
232
+ this.debug(`Task #${taskID} errored:`, error);
233
+ });
234
+ const task = {
235
+ id: taskID,
236
+ run: taskFunction,
237
+ cancel: () => {
238
+ if (this.taskQueue.indexOf(task) === -1)
239
+ return;
240
+ this.taskQueue = this.taskQueue.filter(someTask => someTask !== task);
241
+ this.eventSubject.next({
242
+ type: pool_types_1.PoolEventType.taskCanceled,
243
+ taskID: task.id
244
+ });
245
+ },
246
+ then: taskCompletion.then.bind(taskCompletion)
247
+ };
248
+ if (this.taskQueue.length >= maxQueuedJobs) {
249
+ throw Error("Maximum number of pool tasks queued. Refusing to queue another one.\n" +
250
+ "This usually happens for one of two reasons: We are either at peak " +
251
+ "workload right now or some tasks just won't finish, thus blocking the pool.");
252
+ }
253
+ this.debug(`Queueing task #${task.id}...`);
254
+ this.taskQueue.push(task);
255
+ this.eventSubject.next({
256
+ type: pool_types_1.PoolEventType.taskQueued,
257
+ taskID: task.id
258
+ });
259
+ this.scheduleWork();
260
+ return task;
261
+ }
262
+ terminate(force) {
263
+ return __awaiter(this, void 0, void 0, function* () {
264
+ this.isClosing = true;
265
+ if (!force) {
266
+ yield this.completed(true);
267
+ }
268
+ this.eventSubject.next({
269
+ type: pool_types_1.PoolEventType.terminated,
270
+ remainingQueue: [...this.taskQueue]
271
+ });
272
+ this.eventSubject.complete();
273
+ yield Promise.all(this.workers.map((worker) => __awaiter(this, void 0, void 0, function* () { return thread_1.Thread.terminate(yield worker.init); })));
274
+ });
275
+ }
276
+ }
277
+ WorkerPool.EventType = pool_types_1.PoolEventType;
278
+ /**
279
+ * Thread pool constructor. Creates a new pool and spawns its worker threads.
280
+ */
281
+ function PoolConstructor(spawnWorker, optionsOrSize) {
282
+ // The function exists only so we don't need to use `new` to create a pool (we still can, though).
283
+ // If the Pool is a class or not is an implementation detail that should not concern the user.
284
+ return new WorkerPool(spawnWorker, optionsOrSize);
285
+ }
286
+ PoolConstructor.EventType = pool_types_1.PoolEventType;
287
+ /**
288
+ * Thread pool constructor. Creates a new pool and spawns its worker threads.
289
+ */
290
+ exports.Pool = PoolConstructor;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("./index");
4
+ if (typeof global !== "undefined") {
5
+ global.Worker = index_1.Worker;
6
+ }
7
+ else if (typeof window !== "undefined") {
8
+ window.Worker = index_1.Worker;
9
+ }
@@ -0,0 +1,20 @@
1
+ import { FunctionThread, ModuleThread, StripAsync, Worker as WorkerType } from "../types/master";
2
+ import { WorkerFunction, WorkerModule } from "../types/worker";
3
+ type ArbitraryWorkerInterface = WorkerFunction & WorkerModule<string> & {
4
+ somekeythatisneverusedinproductioncode123: "magicmarker123";
5
+ };
6
+ type ArbitraryThreadType = FunctionThread<any, any> & ModuleThread<any>;
7
+ export type ExposedToThreadType<Exposed extends WorkerFunction | WorkerModule<any>> = Exposed extends ArbitraryWorkerInterface ? ArbitraryThreadType : Exposed extends WorkerFunction ? FunctionThread<Parameters<Exposed>, StripAsync<ReturnType<Exposed>>> : Exposed extends WorkerModule<any> ? ModuleThread<Exposed> : never;
8
+ /**
9
+ * Spawn a new thread. Takes a fresh worker instance, wraps it in a thin
10
+ * abstraction layer to provide the transparent API and verifies that
11
+ * the worker has initialized successfully.
12
+ *
13
+ * @param worker Instance of `Worker`. Either a web worker or a `worker_threads` worker.
14
+ * @param [options]
15
+ * @param [options.timeout] Init message timeout. Default: 10000 or set by environment variable.
16
+ */
17
+ export declare function spawn<Exposed extends WorkerFunction | WorkerModule<any> = ArbitraryWorkerInterface>(worker: WorkerType, options?: {
18
+ timeout?: number;
19
+ }): Promise<ExposedToThreadType<Exposed>>;
20
+ export {};