multiprocessor
Version:
Multiprocessing pool implementation for NodeJS and TypeScript
282 lines • 13.3 kB
JavaScript
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