UNPKG

@jjavery/worker-pool

Version:

A worker pool for Node.js applications

240 lines (239 loc) 9.02 kB
/// <reference types="node" /> import EventEmitter from 'events'; import Worker from './worker'; /** * Thrown when the worker pool is not started */ export declare class NotStartedError extends Error { constructor(); } export { NotReadyError, WorkerError, UnexpectedExitError } from './worker'; interface WorkerPoolOptions { cwd?: string; args?: string[]; env?: any; min?: number; max?: number; idleTimeout?: number; stopTimeout?: number; stopSignal?: 'SIGTERM' | 'SIGINT' | 'SIGHUP' | 'SIGKILL'; strategy?: 'fewest' | 'fill' | 'round-robin' | 'random'; full?: number; start?: boolean; } /** * Provides a load-balancing and (optionally) auto-scaling pool of worker * processes and the ability to request for worker processes to import modules, * call their exported functions, and reply with their return values and thrown * exceptions. Load balancing and auto-scaling are configurable via min/max * limits, strategies, and timeouts. * @extends EventEmitter */ export default class WorkerPool extends EventEmitter { /** * Emitted when an error is thrown in the constructor. * @event WorkerPool#error * @type {Error} - The error object that was thrown. */ /** * Emitted when the worker pool starts. * @event WorkerPool#start */ /** * Emitted when the worker pool recycles. * @event WorkerPool#recycle */ /** * Emitted when the worker pool stops. * @event WorkerPool#stop */ private _cwd?; private _args?; private _env?; private _min; private _max; private _idleTimeout; private _stopTimeout; private _stopSignal; private _strategy; private _full; private _workers; private _stopping; private _round; /** * The current working directory for worker processes. Takes effect after start/recycle. * * @example * * workerPool.cwd = `${versionPath}/workers`; * * await workerPool.recycle(); * * @type {string} */ get cwd(): string | undefined; set cwd(value: string | undefined); /** * Arguments to pass to worker processes. Takes effect after start/recycle. * * @example * * workerPool.args = [ '--verbose' ]; * * await workerPool.recycle(); * * @type {string[]} */ get args(): string[] | undefined; set args(value: string[] | undefined); /** * Environmental variables to set for worker processes. Takes effect after start/recycle. * * @example * * workerPool.env = { TOKEN: newToken }; * * await workerPool.recycle(); * * @type {Object} */ get env(): any; set env(value: any); /** * True if the worker pool is stopping * @type {boolean} */ get isStopping(): boolean; /** * True if the worker pool has stopped * @type {boolean} */ get isStopped(): boolean; /** * True if the worker pool has started * @type {boolean} */ get isStarted(): boolean; /** * Gets the current number of worker processes * @returns {Number} The current number of worker processes */ getProcessCount(): number; /** * @example * * const workerPool = new WorkerPool( * cwd: `${versionPath}/workers`, * args: [ '--verbose' ], * env: { TOKEN: token }, * min: 1, * max: 4, * idleTimeout: 30000, * stopTimeout: 1000, * stopSignal: 'SIGINT' * strategy: 'fill', * full: 100 * }); * * @param {Object} options={} - Optional parameters * @param {string} options.cwd - The current working directory for worker processes * @param {string[]} options.args - Arguments to pass to worker processes * @param {Object} options.env - Environmental variables to set for worker processes * @param {number} options.min=0 - The minimum number of worker processes in the pool * @param {number} options.max=3 - The maximum number of worker processes in the pool * @param {number} options.idleTimeout=10000 - Milliseconds before an idle worker process will be asked to stop via options.stopSignal * @param {number} options.stopTimeout=10000 - Milliseconds before a worker process will receive SIGKILL after it has been asked to stop * @param {'SIGTERM'|'SIGINT'|'SIGHUP'|'SIGKILL'} options.stopSignal='SIGTERM' - Initial signal to send when stopping worker processes * @param {'fewest'|'fill'|'round-robin'|'random'} options.strategy='fewest' - The strategy to use when routing calls to workers * @param {number} options.full=10 - The number of requests per worker used by the 'fill' strategy * @param {boolean} options.start=true - Whether to automatically start this worker pool */ constructor({ cwd, args, env, min, max, idleTimeout, stopTimeout, stopSignal, strategy, full, start }?: WorkerPoolOptions); /** * Starts the worker pool * @returns {Promise} * @resolves When the worker pool has started * @rejects {WorkerPool.NotReadyError | Error} When an error has been thrown */ start(): Promise<void>; /** * Stops the worker pool, gracefully shutting down each worker process * @returns {Promise} * @resolves When the worker pool has stopped * @rejects {Error} When an error has been thrown */ stop(): Promise<void>; /** * Recycle the worker pool, gracefully shutting down existing worker processes * and starting up new worker processes * @returns {Promise} * @resolves When the worker pool has recycled * @rejects {WorkerPool.NotReadyError | Error} When an error has been thrown */ recycle(): Promise<void>; _createWorkers(): void; _startMinWorkers(): Promise<void[]>; _stop(workers: Worker[]): Promise<void>; /** * Routes a request to a worker in the pool asking it to import a module and call a function with the provided arguments. * * **Note**: WorkerPool#call() uses JSON serialization to communicate with worker processes, so only types/objects that can survive JSON.stringify()/JSON.parse() will be passed through unchanged. * * @example * * const result = await workerPool.call('user-module', 'hashPassword', password, salt); * * @param {string} modulePath - The module path for the worker process to import * @param {string} functionName - The name of a function expored by the imported module * @param {...any} args - Arguments to pass when calling the function * @returns {Promise} * @resolves {any} The return value of the function call when the call returns * @rejects {WorkerPool.UnexpectedExitError | WorkerPool.WorkerError | Error} When an error has been thrown */ call(modulePath: string, functionName: string, ...args: any[]): Promise<unknown>; /** * Creates a proxy function that will call WorkerPool#call() with the provided module path, function name, and arguments. Provided as a convenience and minor performance improvement as the modulePath will only be resolved when creating the proxy, rather than with each call. * * **Note**: WorkerPool#proxy() uses JSON serialization to communicate with worker processes, so only types/objects that can survive JSON.stringify()/JSON.parse() will be passed through unchanged. * * @example * * const hashPassword = workerPool.proxy('user-module', 'hashPassword'); * * const hashedPassword = await hashPassword(password, salt); * * @param {string} modulePath - The module path for the worker process to import * @param {string} functionName - The name of a function expored by the imported module * @returns {Function} A function that calls WorkerPool#call() with the provided modulePath, functionName, and args, and returns its Promise */ proxy(modulePath: string, functionName: string): (...args: any[]) => Promise<unknown>; _call(resolvedModulePath: string, functionName: string, args: any[]): Promise<unknown>; _resolve(modulePath: string): string; _getWorker(): Worker; /** * Return the worker with the fewest number of waiting requests, favoring * workers that are already started * @private */ _fewestStrategy(): Worker; /** * Return the first worker that is not full, or if they are all full, the * worker with the fewest number of queued requests. This does not prevent * workers from overfilling. It will fill each worker before moving on to * the next, and will fall back to the "fewest" strategy when all workers * are full. * @private */ _fillStrategy(): Worker; /** * Return the next worker in the sequence * @private */ _roundRobinStrategy(): Worker; /** * Return a random worker * @private */ _randomStrategy(): Worker; _stopWhenIdle(): boolean; }