UNPKG

multiprocessor

Version:

Multiprocessing pool implementation for NodeJS and TypeScript

282 lines 13.3 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } }; var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i; function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } }; import * as path from 'path'; import { EventEmitter } from 'events'; import { fork } from 'child_process'; /** * A class representing a pool of workers for executing tasks concurrently using child processes. * Extends EventEmitter to allow task result events to be emitted. */ export class Pool extends EventEmitter { /** * Create a new pool with the specified number of workers. * * @param poolSize The number of workers to create in the pool. */ constructor(poolSize) { super(); /** * Array of child processes representing the workers in the pool. */ this.workers = []; /** * Array of child processes representing the available workers in the pool. */ this.availableWorkers = []; /** * Array of tasks to be processed in the pool. */ this.taskQueue = []; /** * Map of tasks currently being processed by workers, keyed by worker. */ this.tasksInProcess = new Map(); /** * Current task index, incremented for each task processed. */ this.currentTaskIndex = 0; this.initWorkers(poolSize); this.taskHandlers = { onTaskSuccess: this.createEmptyHandler(), onTaskError: this.createEmptyHandler(), }; } /** * Asynchronously processes tasks from the provided inputs in an ordered manner. * Tasks are executed concurrently using a pool of workers. * * @template TInput The type of the input elements. * @template TResult The type of the result elements. * * @param inputs An iterable or async iterable of input elements. * @param task The task to execute for each input element. * @param taskHandlers Optional handlers for task events (onTaskSuccess, onTaskError). * * @returns A promise that resolves to an array of task results in the order of the input elements. */ map(inputs, task, taskHandlers) { return __awaiter(this, void 0, void 0, function* () { var _a, e_1, _b, _c; const result = []; try { for (var _d = true, _e = __asyncValues(this.imapUnorderedExtended(inputs, task, taskHandlers)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) { _c = _f.value; _d = false; const item = _c; result.push(item); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) yield _b.call(_e); } finally { if (e_1) throw e_1.error; } } result.sort((lhs, rhs) => lhs[0] - rhs[0]); return result.map((item) => item[1]); }); } /** * Asynchronously processes tasks from the provided inputs in a lazy ordered manner. * Tasks are executed concurrently using a pool of workers. * * @template TInput The type of the input elements. * @template TResult The type of the result elements. * * @param inputs An iterable or async iterable of input elements. * @param task The task to execute for each input element. * @param taskHandlers Optional handlers for task events (onTaskSuccess, onTaskError). * * @returns An async generator yielding results of the tasks in the order of the input elements. */ imap(inputs, task, taskHandlers) { return __asyncGenerator(this, arguments, function* imap_1() { var _a, e_2, _b, _c; let lastYieldedIndex = -1; const bufferedResults = new Map(); try { for (var _d = true, _e = __asyncValues(this.imapUnorderedExtended(inputs, task, taskHandlers)), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) { _c = _f.value; _d = false; const [taskIndex, result] = _c; if (taskIndex !== lastYieldedIndex + 1) { bufferedResults.set(taskIndex, result); continue; } ++lastYieldedIndex; yield yield __await(result); while (bufferedResults.has(lastYieldedIndex + 1)) { yield yield __await(bufferedResults.get(lastYieldedIndex + 1)); bufferedResults.delete(lastYieldedIndex + 1); ++lastYieldedIndex; } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e)); } finally { if (e_2) throw e_2.error; } } }); } /** * Asynchronously processes tasks from the provided inputs in a lazy unordered manner. * Tasks are executed concurrently using a pool of workers. * * @template TInput The type of the input elements. * @template TResult The type of the result elements. * * @param inputs An iterable or async iterable of input elements. * @param task The task to execute for each input element. * @param taskHandlers Optional handlers for task events (onTaskSuccess, onTaskError). * * @returns An async generator yielding results of the tasks in completion order. */ imapUnordered(inputs, task, taskHandlers) { return __asyncGenerator(this, arguments, function* imapUnordered_1() { var _a, e_3, _b, _c; try { for (var _d = true, _e = __asyncValues(this.imapUnorderedExtended(inputs, task, taskHandlers)), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) { _c = _f.value; _d = false; const [_, result] = _c; yield yield __await(result); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e)); } finally { if (e_3) throw e_3.error; } } }); } /** * Asynchronously processes tasks from the provided inputs in a lazy unordered manner with extended information. * Tasks are executed concurrently using a pool of workers. * * @template TInput The type of the input elements. * @template TResult The type of the result elements. * * @param inputs An iterable or async iterable of input elements. * @param task The task to execute for each input element. * @param taskHandlers Optional handlers for task events (onTaskSuccess, onTaskError). * * @returns An async generator yielding task responses containing the index, result or error for each task. */ imapUnorderedExtended(inputs, task, taskHandlers) { return __asyncGenerator(this, arguments, function* imapUnorderedExtended_1() { var _a, e_4, _b, _c; var _d, _e; this.currentTaskIndex = 0; this.taskHandlers.onTaskSuccess = (_d = taskHandlers === null || taskHandlers === void 0 ? void 0 : taskHandlers.onTaskSuccess) !== null && _d !== void 0 ? _d : this.createEmptyHandler(); this.taskHandlers.onTaskError = (_e = taskHandlers === null || taskHandlers === void 0 ? void 0 : taskHandlers.onTaskError) !== null && _e !== void 0 ? _e : this.createEmptyHandler(); const taskFunctionString = task.toString(); let totalTasks = 0; try { // Enqueue all tasks for (var _f = true, inputs_1 = __asyncValues(inputs), inputs_1_1; inputs_1_1 = yield __await(inputs_1.next()), _a = inputs_1_1.done, !_a; _f = true) { _c = inputs_1_1.value; _f = false; const inputData = _c; this.taskQueue.push({ inputData, taskFunctionString }); ++totalTasks; } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (!_f && !_a && (_b = inputs_1.return)) yield __await(_b.call(inputs_1)); } finally { if (e_4) throw e_4.error; } } // Start processing this.processQueue(); let received = 0; while (received < totalTasks) { const result = yield __await(new Promise((resolve) => { this.once('result', resolve); })); received++; yield yield __await([result.taskIndex, result.result, result.error]); } }); } /** * Closes the worker pool by terminating all worker processes. * This method should be called when the pool is no longer needed * to ensure that all resources are properly released. */ close() { for (const worker of this.workers) { worker.kill(); } } processQueue() { while (this.availableWorkers.length > 0 && this.taskQueue.length > 0) { const worker = this.availableWorkers.shift(); const task = this.taskQueue.shift(); this.tasksInProcess.set(worker, task.inputData); worker.send({ taskFunctionString: task.taskFunctionString, inputData: task.inputData, taskIndex: this.currentTaskIndex++, }); } } initWorkers(poolSize) { for (let i = 0; i < poolSize; i++) { const worker = fork(path.resolve(__dirname, './worker.js')); worker.on('message', (message) => { const { result, error, inputData, taskIndex } = message; if (error) { this.taskHandlers.onTaskError(error, inputData, taskIndex); } else { this.taskHandlers.onTaskSuccess(result, inputData, taskIndex); } this.emit('result', { result, taskIndex, error }); this.tasksInProcess.delete(worker); this.availableWorkers.push(worker); this.processQueue(); }); this.workers.push(worker); this.availableWorkers.push(worker); } } createEmptyHandler() { return () => { }; } } //# sourceMappingURL=pool.js.map