@jjavery/worker-pool
Version:
A worker pool for Node.js applications
240 lines (239 loc) • 9.02 kB
TypeScript
/// <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;
}