poolifier
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -1,15 +0,773 @@ | ||
export type { ErrorHandler, ExitHandler, IWorker, OnlineHandler, PoolOptions } from './pools/abstract-pool'; | ||
export { DynamicClusterPool } from './pools/cluster/dynamic'; | ||
export { FixedClusterPool } from './pools/cluster/fixed'; | ||
export type { ClusterPoolOptions } from './pools/cluster/fixed'; | ||
export type { IPool } from './pools/pool'; | ||
export { WorkerChoiceStrategies } from './pools/selection-strategies'; | ||
export type { WorkerChoiceStrategy } from './pools/selection-strategies'; | ||
export { DynamicThreadPool } from './pools/thread/dynamic'; | ||
export { FixedThreadPool } from './pools/thread/fixed'; | ||
export type { ThreadWorkerWithMessageChannel } from './pools/thread/fixed'; | ||
export { AbstractWorker } from './worker/abstract-worker'; | ||
export { ClusterWorker } from './worker/cluster-worker'; | ||
export { ThreadWorker } from './worker/thread-worker'; | ||
export { KillBehaviors } from './worker/worker-options'; | ||
export type { KillBehavior, WorkerOptions } from './worker/worker-options'; | ||
/// <reference types="node" /> | ||
import { Worker } from "cluster"; | ||
import { MessagePort, MessageChannel } from "worker_threads"; | ||
import { Worker as Worker$0 } from "worker_threads"; | ||
import EventEmitter from "events"; | ||
import { AsyncResource } from "async_hooks"; | ||
/** | ||
* Enumeration of kill behaviors. | ||
*/ | ||
declare const KillBehaviors: Readonly<{ | ||
readonly SOFT: "SOFT"; | ||
readonly HARD: "HARD"; | ||
}>; | ||
/** | ||
* Kill behavior. | ||
*/ | ||
type KillBehavior = keyof typeof KillBehaviors; | ||
/** | ||
* Options for workers. | ||
*/ | ||
interface WorkerOptions { | ||
/** | ||
* Maximum waiting time in milliseconds for tasks. | ||
* | ||
* After this time, newly created workers will be terminated. | ||
* The last active time of your worker unit will be updated when a task is submitted to a worker or when a worker terminate a task. | ||
* | ||
* - If `killBehavior` is set to `KillBehaviors.HARD` this value represents also the timeout for the tasks that you submit to the pool, | ||
* when this timeout expires your tasks is interrupted and the worker is killed if is not part of the minimum size of the pool. | ||
* - If `killBehavior` is set to `KillBehaviors.SOFT` your tasks have no timeout and your workers will not be terminated until your task is completed. | ||
* | ||
* @default 60.000 ms | ||
*/ | ||
maxInactiveTime?: number; | ||
/** | ||
* Whether your worker will perform asynchronous or not. | ||
* | ||
* @default false | ||
*/ | ||
async?: boolean; | ||
/** | ||
* `killBehavior` dictates if your async unit (worker/process) will be deleted in case that a task is active on it. | ||
* | ||
* - SOFT: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted. | ||
* - HARD: If `lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted. | ||
* | ||
* This option only apply to the newly created workers. | ||
* | ||
* @default KillBehaviors.SOFT | ||
*/ | ||
killBehavior?: KillBehavior; | ||
} | ||
/** | ||
* Make all properties in T non-readonly. | ||
*/ | ||
type Draft<T> = { | ||
-readonly [P in keyof T]?: T[P]; | ||
}; | ||
/** | ||
* Message object that is passed between worker and main worker. | ||
*/ | ||
interface MessageValue<Data = unknown, MainWorker extends Worker | MessagePort | unknown = unknown> { | ||
/** | ||
* Input data that will be passed to the worker. | ||
*/ | ||
readonly data?: Data; | ||
/** | ||
* Id of the message. | ||
*/ | ||
readonly id?: number; | ||
/** | ||
* Kill code. | ||
*/ | ||
readonly kill?: KillBehavior | 1; | ||
/** | ||
* Error. | ||
*/ | ||
readonly error?: string; | ||
/** | ||
* Reference to main worker. | ||
* | ||
* _Only for internal use_ | ||
*/ | ||
readonly parent?: MainWorker; | ||
} | ||
/** | ||
* An object holding the worker that will be used to resolve/rejects the promise later on. | ||
* | ||
* @template Worker Type of worker. | ||
* @template Response Type of response of execution. This can only be serializable data. | ||
*/ | ||
interface PromiseWorkerResponseWrapper<Worker extends IWorker, Response = unknown> { | ||
/** | ||
* Resolve callback to fulfill the promise. | ||
*/ | ||
readonly resolve: (value: Response) => void; | ||
/** | ||
* Reject callback to reject the promise. | ||
*/ | ||
readonly reject: (reason?: string) => void; | ||
/** | ||
* The worker that has the assigned task. | ||
*/ | ||
readonly worker: Worker; | ||
} | ||
/** | ||
* Enumeration of worker choice strategies. | ||
*/ | ||
declare const WorkerChoiceStrategies: Readonly<{ | ||
readonly ROUND_ROBIN: "ROUND_ROBIN"; | ||
readonly LESS_RECENTLY_USED: "LESS_RECENTLY_USED"; | ||
}>; | ||
/** | ||
* Worker choice strategy. | ||
*/ | ||
type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies; | ||
/** | ||
* The worker choice strategy context. | ||
* | ||
* @template Worker Type of worker. | ||
* @template Data Type of data sent to the worker. This can only be serializable data. | ||
* @template Response Type of response of execution. This can only be serializable data. | ||
*/ | ||
declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data, Response> { | ||
private readonly pool; | ||
private createDynamicallyWorkerCallback; | ||
// Will be set by setter in constructor | ||
private workerChoiceStrategy; | ||
/** | ||
* Worker choice strategy context constructor. | ||
* | ||
* @param pool The pool instance. | ||
* @param createDynamicallyWorkerCallback The worker creation callback for dynamic pool. | ||
* @param workerChoiceStrategy The worker choice strategy. | ||
*/ | ||
constructor(pool: IPoolInternal<Worker, Data, Response>, createDynamicallyWorkerCallback: () => Worker, workerChoiceStrategy?: WorkerChoiceStrategy); | ||
/** | ||
* Get the worker choice strategy instance specific to the pool type. | ||
* | ||
* @param workerChoiceStrategy The worker choice strategy. | ||
* @returns The worker choice strategy instance for the pool type. | ||
*/ | ||
private getPoolWorkerChoiceStrategy; | ||
/** | ||
* Set the worker choice strategy to use in the context. | ||
* | ||
* @param workerChoiceStrategy The worker choice strategy to set. | ||
*/ | ||
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void; | ||
/** | ||
* Choose a worker with the underlying selection strategy. | ||
* | ||
* @returns The chosen one. | ||
*/ | ||
execute(): Worker; | ||
} | ||
/** | ||
* Contract definition for a poolifier pool. | ||
* | ||
* @template Data Type of data sent to the worker. This can only be serializable data. | ||
* @template Response Type of response of execution. This can only be serializable data. | ||
*/ | ||
interface IPool<Data = unknown, Response = unknown> { | ||
/** | ||
* Perform the task specified in the constructor with the data parameter. | ||
* | ||
* @param data The input for the specified task. This can only be serializable data. | ||
* @returns Promise that will be resolved when the task is successfully completed. | ||
*/ | ||
execute(data: Data): Promise<Response>; | ||
/** | ||
* Shut down every current worker in this pool. | ||
*/ | ||
destroy(): Promise<void>; | ||
/** | ||
* Set the worker choice strategy in this pool. | ||
* | ||
* @param workerChoiceStrategy The worker choice strategy. | ||
*/ | ||
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void; | ||
} | ||
/** | ||
* Pool types. | ||
*/ | ||
declare enum PoolType { | ||
FIXED = "fixed", | ||
DYNAMIC = "dynamic" | ||
} | ||
/** | ||
* Internal poolifier pool emitter. | ||
*/ | ||
declare class PoolEmitter extends EventEmitter { | ||
} | ||
/** | ||
* Internal contract definition for a poolifier pool. | ||
* | ||
* @template Worker Type of worker which manages this pool. | ||
* @template Data Type of data sent to the worker. | ||
* @template Response Type of response of execution. | ||
*/ | ||
interface IPoolInternal<Worker extends IWorker, Data = unknown, Response = unknown> extends IPool<Data, Response> { | ||
/** | ||
* List of currently available workers. | ||
*/ | ||
readonly workers: Worker[]; | ||
/** | ||
* The tasks map. | ||
* | ||
* - `key`: The `Worker` | ||
* - `value`: Number of tasks currently in progress on the worker. | ||
*/ | ||
readonly tasks: Map<Worker, number>; | ||
/** | ||
* Emitter on which events can be listened to. | ||
* | ||
* Events that can currently be listened to: | ||
* | ||
* - `'busy'` | ||
*/ | ||
readonly emitter?: PoolEmitter; | ||
/** | ||
* Pool type. | ||
* | ||
* If it is `'dynamic'`, it provides the `max` property. | ||
*/ | ||
readonly type: PoolType; | ||
/** | ||
* Maximum number of workers that can be created by this pool. | ||
*/ | ||
readonly max?: number; | ||
/** | ||
* Whether the pool is busy or not. | ||
* | ||
* The pool busyness boolean status. | ||
*/ | ||
readonly busy: boolean; | ||
/** | ||
* Number of tasks currently concurrently running. | ||
*/ | ||
readonly numberOfRunningTasks: number; | ||
/** | ||
* Find a tasks map entry with a free worker based on the number of tasks the worker has applied. | ||
* | ||
* If an entry is found with a worker that has `0` tasks, it is detected as free. | ||
* | ||
* If no tasks map entry with a free worker was found, `false` will be returned. | ||
* | ||
* @returns A tasks map entry with a free worker if there was one, otherwise `false`. | ||
*/ | ||
findFreeTasksMapEntry(): [ | ||
Worker, | ||
number | ||
] | false; | ||
} | ||
/** | ||
* Callback invoked if the worker has received a message. | ||
*/ | ||
type MessageHandler<Worker> = (this: Worker, m: unknown) => void; | ||
/** | ||
* Callback invoked if the worker raised an error. | ||
*/ | ||
type ErrorHandler<Worker> = (this: Worker, e: Error) => void; | ||
/** | ||
* Callback invoked when the worker has started successfully. | ||
*/ | ||
type OnlineHandler<Worker> = (this: Worker) => void; | ||
/** | ||
* Callback invoked when the worker exits successfully. | ||
*/ | ||
type ExitHandler<Worker> = (this: Worker, code: number) => void; | ||
/** | ||
* Basic interface that describes the minimum required implementation of listener events for a pool-worker. | ||
*/ | ||
interface IWorker { | ||
/** | ||
* Register a listener to the message event. | ||
* | ||
* @param event `'message'`. | ||
* @param handler The message handler. | ||
*/ | ||
on(event: "message", handler: MessageHandler<this>): void; | ||
/** | ||
* Register a listener to the error event. | ||
* | ||
* @param event `'error'`. | ||
* @param handler The error handler. | ||
*/ | ||
on(event: "error", handler: ErrorHandler<this>): void; | ||
/** | ||
* Register a listener to the online event. | ||
* | ||
* @param event `'online'`. | ||
* @param handler The online handler. | ||
*/ | ||
on(event: "online", handler: OnlineHandler<this>): void; | ||
/** | ||
* Register a listener to the exit event. | ||
* | ||
* @param event `'exit'`. | ||
* @param handler The exit handler. | ||
*/ | ||
on(event: "exit", handler: ExitHandler<this>): void; | ||
/** | ||
* Register a listener to the exit event that will only performed once. | ||
* | ||
* @param event `'exit'`. | ||
* @param handler The exit handler. | ||
*/ | ||
once(event: "exit", handler: ExitHandler<this>): void; | ||
} | ||
/** | ||
* Options for a poolifier pool. | ||
*/ | ||
interface PoolOptions<Worker> { | ||
/** | ||
* A function that will listen for message event on each worker. | ||
*/ | ||
messageHandler?: MessageHandler<Worker>; | ||
/** | ||
* A function that will listen for error event on each worker. | ||
*/ | ||
errorHandler?: ErrorHandler<Worker>; | ||
/** | ||
* A function that will listen for online event on each worker. | ||
*/ | ||
onlineHandler?: OnlineHandler<Worker>; | ||
/** | ||
* A function that will listen for exit event on each worker. | ||
*/ | ||
exitHandler?: ExitHandler<Worker>; | ||
/** | ||
* The work choice strategy to use in this pool. | ||
*/ | ||
workerChoiceStrategy?: WorkerChoiceStrategy; | ||
/** | ||
* Pool events emission. | ||
* | ||
* @default true | ||
*/ | ||
enableEvents?: boolean; | ||
} | ||
/** | ||
* Base class containing some shared logic for all poolifier pools. | ||
* | ||
* @template Worker Type of worker which manages this pool. | ||
* @template Data Type of data sent to the worker. This can only be serializable data. | ||
* @template Response Type of response of execution. This can only be serializable data. | ||
*/ | ||
declare abstract class AbstractPool<Worker extends IWorker, Data = unknown, Response = unknown> implements IPoolInternal<Worker, Data, Response> { | ||
readonly numberOfWorkers: number; | ||
readonly filePath: string; | ||
readonly opts: PoolOptions<Worker>; | ||
/** @inheritdoc */ | ||
readonly workers: Worker[]; | ||
/** @inheritdoc */ | ||
readonly tasks: Map<Worker, number>; | ||
/** @inheritdoc */ | ||
readonly emitter?: PoolEmitter; | ||
/** @inheritdoc */ | ||
readonly max?: number; | ||
/** | ||
* The promise map. | ||
* | ||
* - `key`: This is the message Id of each submitted task. | ||
* - `value`: An object that contains the worker, the resolve function and the reject function. | ||
* | ||
* When we receive a message from the worker we get a map entry and resolve/reject the promise based on the message. | ||
*/ | ||
protected promiseMap: Map<number, PromiseWorkerResponseWrapper<Worker, Response>>; | ||
/** | ||
* Id of the next message. | ||
*/ | ||
protected nextMessageId: number; | ||
/** | ||
* Worker choice strategy instance implementing the worker choice algorithm. | ||
* | ||
* Default to a strategy implementing a round robin algorithm. | ||
*/ | ||
protected workerChoiceStrategyContext: WorkerChoiceStrategyContext<Worker, Data, Response>; | ||
/** | ||
* Constructs a new poolifier pool. | ||
* | ||
* @param numberOfWorkers Number of workers that this pool should manage. | ||
* @param filePath Path to the worker-file. | ||
* @param opts Options for the pool. | ||
*/ | ||
constructor(numberOfWorkers: number, filePath: string, opts: PoolOptions<Worker>); | ||
private checkFilePath; | ||
private checkNumberOfWorkers; | ||
private checkPoolOptions; | ||
/** @inheritdoc */ | ||
abstract get type(): PoolType; | ||
/** @inheritdoc */ | ||
get numberOfRunningTasks(): number; | ||
/** @inheritdoc */ | ||
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void; | ||
/** @inheritdoc */ | ||
abstract get busy(): boolean; | ||
protected internalGetBusyStatus(): boolean; | ||
/** @inheritdoc */ | ||
findFreeTasksMapEntry(): [ | ||
Worker, | ||
number | ||
] | false; | ||
/** @inheritdoc */ | ||
execute(data: Data): Promise<Response>; | ||
/** @inheritdoc */ | ||
destroy(): Promise<void>; | ||
/** | ||
* Shut down given worker. | ||
* | ||
* @param worker A worker within `workers`. | ||
*/ | ||
protected abstract destroyWorker(worker: Worker): void | Promise<void>; | ||
/** | ||
* Setup hook that can be overridden by a Poolifier pool implementation | ||
* to run code before workers are created in the abstract constructor. | ||
*/ | ||
protected setupHook(): void; | ||
/** | ||
* Should return whether the worker is the main worker or not. | ||
*/ | ||
protected abstract isMain(): boolean; | ||
/** | ||
* Increase the number of tasks that the given worker has applied. | ||
* | ||
* @param worker Worker whose tasks are increased. | ||
*/ | ||
protected increaseWorkersTask(worker: Worker): void; | ||
/** | ||
* Decrease the number of tasks that the given worker has applied. | ||
* | ||
* @param worker Worker whose tasks are decreased. | ||
*/ | ||
protected decreaseWorkersTasks(worker: Worker): void; | ||
/** | ||
* Step the number of tasks that the given worker has applied. | ||
* | ||
* @param worker Worker whose tasks are set. | ||
* @param step Worker number of tasks step. | ||
*/ | ||
private stepWorkerNumberOfTasks; | ||
/** | ||
* Removes the given worker from the pool. | ||
* | ||
* @param worker Worker that will be removed. | ||
*/ | ||
protected removeWorker(worker: Worker): void; | ||
/** | ||
* Choose a worker for the next task. | ||
* | ||
* The default implementation uses a round robin algorithm to distribute the load. | ||
* | ||
* @returns Worker. | ||
*/ | ||
protected chooseWorker(): Worker; | ||
/** | ||
* Send a message to the given worker. | ||
* | ||
* @param worker The worker which should receive the message. | ||
* @param message The message. | ||
*/ | ||
protected abstract sendToWorker(worker: Worker, message: MessageValue<Data>): void; | ||
/** | ||
* Register a listener callback on a given worker. | ||
* | ||
* @param worker A worker. | ||
* @param listener A message listener callback. | ||
*/ | ||
protected abstract registerWorkerMessageListener<Message extends Data | Response>(worker: Worker, listener: (message: MessageValue<Message>) => void): void; | ||
protected internalExecute(worker: Worker, messageId: number): Promise<Response>; | ||
/** | ||
* Returns a newly created worker. | ||
*/ | ||
protected abstract createWorker(): Worker; | ||
/** | ||
* Function that can be hooked up when a worker has been newly created and moved to the workers registry. | ||
* | ||
* Can be used to update the `maxListeners` or binding the `main-worker`<->`worker` connection if not bind by default. | ||
* | ||
* @param worker The newly created worker. | ||
*/ | ||
protected abstract afterWorkerSetup(worker: Worker): void; | ||
/** | ||
* Creates a new worker for this pool and sets it up completely. | ||
* | ||
* @returns New, completely set up worker. | ||
*/ | ||
protected createAndSetupWorker(): Worker; | ||
/** | ||
* This function is the listener registered for each worker. | ||
* | ||
* @returns The listener function to execute when a message is sent from a worker. | ||
*/ | ||
protected workerListener(): (message: MessageValue<Response>) => void; | ||
private checkAndEmitBusy; | ||
} | ||
/** | ||
* Options for a poolifier cluster pool. | ||
*/ | ||
interface ClusterPoolOptions extends PoolOptions<Worker> { | ||
/** | ||
* Key/value pairs to add to worker process environment. | ||
* | ||
* @see https://nodejs.org/api/cluster.html#cluster_cluster_fork_env | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
env?: any; | ||
} | ||
/** | ||
* A cluster pool with a fixed number of workers. | ||
* | ||
* It is possible to perform tasks in sync or asynchronous mode as you prefer. | ||
* | ||
* This pool selects the workers in a round robin fashion. | ||
* | ||
* @template DataType of data sent to the worker. This can only be serializable data. | ||
* @template ResponseType of response of execution. This can only be serializable data. | ||
* @author [Christopher Quadflieg](https://github.com/Shinigami92) | ||
* @since 2.0.0 | ||
*/ | ||
declare class FixedClusterPool<Data = unknown, Response = unknown> extends AbstractPool<Worker, Data, Response> { | ||
readonly opts: ClusterPoolOptions; | ||
/** | ||
* Constructs a new poolifier fixed cluster pool. | ||
* | ||
* @param numberOfWorkers Number of workers for this pool. | ||
* @param filePath Path to an implementation of a `ClusterWorker` file, which can be relative or absolute. | ||
* @param [opts={}] Options for this fixed cluster pool. | ||
*/ | ||
constructor(numberOfWorkers: number, filePath: string, opts?: ClusterPoolOptions); | ||
/** @inheritdoc */ | ||
protected setupHook(): void; | ||
/** @inheritdoc */ | ||
protected isMain(): boolean; | ||
/** @inheritdoc */ | ||
destroyWorker(worker: Worker): void; | ||
/** @inheritdoc */ | ||
protected sendToWorker(worker: Worker, message: MessageValue<Data>): void; | ||
/** @inheritdoc */ | ||
registerWorkerMessageListener<Message extends Data | Response>(worker: Worker, listener: (message: MessageValue<Message>) => void): void; | ||
/** @inheritdoc */ | ||
protected createWorker(): Worker; | ||
/** @inheritdoc */ | ||
protected afterWorkerSetup(worker: Worker): void; | ||
/** @inheritdoc */ | ||
get type(): PoolType; | ||
/** @inheritdoc */ | ||
get busy(): boolean; | ||
} | ||
/** | ||
* A cluster pool with a dynamic number of workers, but a guaranteed minimum number of workers. | ||
* | ||
* This cluster pool creates new workers when the others are busy, up to the maximum number of workers. | ||
* When the maximum number of workers is reached, an event is emitted. If you want to listen to this event, use the pool's `emitter`. | ||
* | ||
* @template DataType of data sent to the worker. This can only be serializable data. | ||
* @template ResponseType of response of execution. This can only be serializable data. | ||
* @author [Christopher Quadflieg](https://github.com/Shinigami92) | ||
* @since 2.0.0 | ||
*/ | ||
declare class DynamicClusterPool<Data = unknown, Response = unknown> extends FixedClusterPool<Data, Response> { | ||
readonly max: number; | ||
/** | ||
* Constructs a new poolifier dynamic cluster pool. | ||
* | ||
* @param min Minimum number of workers which are always active. | ||
* @param max Maximum number of workers that can be created by this pool. | ||
* @param filePath Path to an implementation of a `ClusterWorker` file, which can be relative or absolute. | ||
* @param [opts={}] Options for this dynamic cluster pool. | ||
*/ | ||
constructor(min: number, max: number, filePath: string, opts?: ClusterPoolOptions); | ||
/** @inheritdoc */ | ||
get type(): PoolType; | ||
/** @inheritdoc */ | ||
get busy(): boolean; | ||
} | ||
/** | ||
* A thread worker with message channels for communication between main thread and thread worker. | ||
*/ | ||
type ThreadWorkerWithMessageChannel = Worker$0 & Draft<MessageChannel>; | ||
/** | ||
* A thread pool with a fixed number of threads. | ||
* | ||
* It is possible to perform tasks in sync or asynchronous mode as you prefer. | ||
* | ||
* This pool selects the threads in a round robin fashion. | ||
* | ||
* @template DataType of data sent to the worker. This can only be serializable data. | ||
* @template ResponseType of response of execution. This can only be serializable data. | ||
* @author [Alessandro Pio Ardizio](https://github.com/pioardi) | ||
* @since 0.0.1 | ||
*/ | ||
declare class FixedThreadPool<Data = unknown, Response = unknown> extends AbstractPool<ThreadWorkerWithMessageChannel, Data, Response> { | ||
/** | ||
* Constructs a new poolifier fixed thread pool. | ||
* | ||
* @param numberOfThreads Number of threads for this pool. | ||
* @param filePath Path to an implementation of a `ThreadWorker` file, which can be relative or absolute. | ||
* @param [opts={}] Options for this fixed thread pool. | ||
*/ | ||
constructor(numberOfThreads: number, filePath: string, opts?: PoolOptions<ThreadWorkerWithMessageChannel>); | ||
/** @inheritdoc */ | ||
protected isMain(): boolean; | ||
/** @inheritdoc */ | ||
destroyWorker(worker: ThreadWorkerWithMessageChannel): Promise<void>; | ||
/** @inheritdoc */ | ||
protected sendToWorker(worker: ThreadWorkerWithMessageChannel, message: MessageValue<Data>): void; | ||
/** @inheritdoc */ | ||
registerWorkerMessageListener<Message extends Data | Response>(messageChannel: ThreadWorkerWithMessageChannel, listener: (message: MessageValue<Message>) => void): void; | ||
/** @inheritdoc */ | ||
protected createWorker(): ThreadWorkerWithMessageChannel; | ||
/** @inheritdoc */ | ||
protected afterWorkerSetup(worker: ThreadWorkerWithMessageChannel): void; | ||
/** @inheritdoc */ | ||
get type(): PoolType; | ||
/** @inheritdoc */ | ||
get busy(): boolean; | ||
} | ||
/** | ||
* A thread pool with a dynamic number of threads, but a guaranteed minimum number of threads. | ||
* | ||
* This thread pool creates new threads when the others are busy, up to the maximum number of threads. | ||
* When the maximum number of threads is reached, an event is emitted. If you want to listen to this event, use the pool's `emitter`. | ||
* | ||
* @template DataType of data sent to the worker. This can only be serializable data. | ||
* @template ResponseType of response of execution. This can only be serializable data. | ||
* @author [Alessandro Pio Ardizio](https://github.com/pioardi) | ||
* @since 0.0.1 | ||
*/ | ||
declare class DynamicThreadPool<Data = unknown, Response = unknown> extends FixedThreadPool<Data, Response> { | ||
readonly max: number; | ||
/** | ||
* Constructs a new poolifier dynamic thread pool. | ||
* | ||
* @param min Minimum number of threads which are always active. | ||
* @param max Maximum number of threads that can be created by this pool. | ||
* @param filePath Path to an implementation of a `ThreadWorker` file, which can be relative or absolute. | ||
* @param [opts={}] Options for this dynamic thread pool. | ||
*/ | ||
constructor(min: number, max: number, filePath: string, opts?: PoolOptions<ThreadWorkerWithMessageChannel>); | ||
/** @inheritdoc */ | ||
get type(): PoolType; | ||
/** @inheritdoc */ | ||
get busy(): boolean; | ||
} | ||
/** | ||
* Base class containing some shared logic for all poolifier workers. | ||
* | ||
* @template MainWorker Type of main worker. | ||
* @template Data Type of data this worker receives from pool's execution. This can only be serializable data. | ||
* @template Response Type of response the worker sends back to the main worker. This can only be serializable data. | ||
*/ | ||
declare abstract class AbstractWorker<MainWorker extends Worker | MessagePort, Data = unknown, Response = unknown> extends AsyncResource { | ||
protected mainWorker: MainWorker | undefined | null; | ||
readonly opts: WorkerOptions; | ||
/** | ||
* Timestamp of the last task processed by this worker. | ||
*/ | ||
protected lastTaskTimestamp: number; | ||
/** | ||
* Handler Id of the `aliveInterval` worker alive check. | ||
*/ | ||
protected readonly aliveInterval?: NodeJS.Timeout; | ||
/** | ||
* Constructs a new poolifier worker. | ||
* | ||
* @param type The type of async event. | ||
* @param isMain Whether this is the main worker or not. | ||
* @param fn Function processed by the worker when the pool's `execution` function is invoked. | ||
* @param mainWorker Reference to main worker. | ||
* @param opts Options for the worker. | ||
*/ | ||
constructor(type: string, isMain: boolean, fn: (data: Data) => Response, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions); | ||
private checkWorkerOptions; | ||
/** | ||
* Check if the `fn` parameter is passed to the constructor. | ||
* | ||
* @param fn The function that should be defined. | ||
*/ | ||
private checkFunctionInput; | ||
/** | ||
* Returns the main worker. | ||
* | ||
* @returns Reference to the main worker. | ||
*/ | ||
protected getMainWorker(): MainWorker; | ||
/** | ||
* Send a message to the main worker. | ||
* | ||
* @param message The response message. | ||
*/ | ||
protected abstract sendToMainWorker(message: MessageValue<Response>): void; | ||
/** | ||
* Check to see if the worker should be terminated, because its living too long. | ||
*/ | ||
protected checkAlive(): void; | ||
/** | ||
* Handle an error and convert it to a string so it can be sent back to the main worker. | ||
* | ||
* @param e The error raised by the worker. | ||
* @returns Message of the error. | ||
*/ | ||
protected handleError(e: Error | string): string; | ||
/** | ||
* Run the given function synchronously. | ||
* | ||
* @param fn Function that will be executed. | ||
* @param value Input data for the given function. | ||
*/ | ||
protected run(fn: (data?: Data) => Response, value: MessageValue<Data>): void; | ||
/** | ||
* Run the given function asynchronously. | ||
* | ||
* @param fn Function that will be executed. | ||
* @param value Input data for the given function. | ||
*/ | ||
protected runAsync(fn: (data?: Data) => Promise<Response>, value: MessageValue<Data>): void; | ||
} | ||
/** | ||
* A cluster worker used by a poolifier `ClusterPool`. | ||
* | ||
* When this worker is inactive for more than the given `maxInactiveTime`, | ||
* it will send a termination request to its main worker. | ||
* | ||
* If you use a `DynamicClusterPool` the extra workers that were created will be terminated, | ||
* but the minimum number of workers will be guaranteed. | ||
* | ||
* @template DataType of data this worker receives from pool's execution. This can only be serializable data. | ||
* @template ResponseType of response the worker sends back to the main worker. This can only be serializable data. | ||
* @author [Christopher Quadflieg](https://github.com/Shinigami92) | ||
* @since 2.0.0 | ||
*/ | ||
declare class ClusterWorker<Data = unknown, Response = unknown> extends AbstractWorker<Worker, Data, Response> { | ||
/** | ||
* Constructs a new poolifier cluster worker. | ||
* | ||
* @param fn Function processed by the worker when the pool's `execution` function is invoked. | ||
* @param opts Options for the worker. | ||
*/ | ||
constructor(fn: (data: Data) => Response, opts?: WorkerOptions); | ||
/** @inheritdoc */ | ||
protected sendToMainWorker(message: MessageValue<Response>): void; | ||
/** @inheritdoc */ | ||
protected handleError(e: Error | string): string; | ||
} | ||
/** | ||
* A thread worker used by a poolifier `ThreadPool`. | ||
* | ||
* When this worker is inactive for more than the given `maxInactiveTime`, | ||
* it will send a termination request to its main thread. | ||
* | ||
* If you use a `DynamicThreadPool` the extra workers that were created will be terminated, | ||
* but the minimum number of workers will be guaranteed. | ||
* | ||
* @template DataType of data this worker receives from pool's execution. This can only be serializable data. | ||
* @template ResponseType of response the worker sends back to the main thread. This can only be serializable data. | ||
* @author [Alessandro Pio Ardizio](https://github.com/pioardi) | ||
* @since 0.0.1 | ||
*/ | ||
declare class ThreadWorker<Data = unknown, Response = unknown> extends AbstractWorker<MessagePort, Data, Response> { | ||
/** | ||
* Constructs a new poolifier thread worker. | ||
* | ||
* @param fn Function processed by the worker when the pool's `execution` function is invoked. | ||
* @param opts Options for the worker. | ||
*/ | ||
constructor(fn: (data: Data) => Response, opts?: WorkerOptions); | ||
/** @inheritdoc */ | ||
protected sendToMainWorker(message: MessageValue<Response>): void; | ||
} | ||
export type { ErrorHandler, ExitHandler, IWorker, OnlineHandler, PoolOptions, ClusterPoolOptions, IPool, WorkerChoiceStrategy, ThreadWorkerWithMessageChannel, KillBehavior, WorkerOptions }; | ||
export { DynamicClusterPool, FixedClusterPool, WorkerChoiceStrategies, DynamicThreadPool, FixedThreadPool, AbstractWorker, ClusterWorker, ThreadWorker, KillBehaviors }; |
@@ -1,1 +0,1 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("events"),t=require("cluster"),r=require("worker_threads"),s=require("async_hooks");function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i,n=o(e);!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(i||(i={}));class a extends n.default{}const h=()=>{},c=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const l=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED"});class k{constructor(e){this.pool=e,this.nextWorkerIndex=0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.pool.workers.length-1===this.nextWorkerIndex?0:this.nextWorkerIndex+1,e}}class u{constructor(e){this.pool=e}choose(){const e=this.pool.type===i.DYNAMIC;let t,r=1/0;for(const[s,o]of this.pool.tasks){if(!e&&0===o)return s;o<r&&(t=s,r=o)}return t}}class p{constructor(e,t,r=l.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=t,this.workerChoiceStrategy=W.getWorkerChoiceStrategy(this.pool,r)}choose(){const e=this.pool.findFreeTasksMapEntry();return e?e[0]:this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback()}}class d{constructor(e,t,r=l.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=t,this.setWorkerChoiceStrategy(r)}getPoolWorkerChoiceStrategy(e=l.ROUND_ROBIN){return this.pool.type===i.DYNAMIC?new p(this.pool,this.createDynamicallyWorkerCallback,e):W.getWorkerChoiceStrategy(this.pool,e)}setWorkerChoiceStrategy(e){this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class W{static getWorkerChoiceStrategy(e,t=l.ROUND_ROBIN){switch(t){case l.ROUND_ROBIN:return new k(e);case l.LESS_RECENTLY_USED:return new u(e);default:throw new Error(`Worker choice strategy '${t}' not found`)}}}class y{constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,this.workers=[],this.tasks=new Map,this.promiseMap=new Map,this.nextMessageId=0,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();this.opts.enableEvents&&(this.emitter=new a),this.workerChoiceStrategyContext=new d(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(async t=>{const r=this.tasks.get(e);var s;s=c.HARD,(t.kill===s||0===r)&&await this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(!e)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new Error("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new Error("Cannot instantiate a pool with a negative number of workers");if(this.type===i.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){var t,r;this.opts.workerChoiceStrategy=null!==(t=e.workerChoiceStrategy)&&void 0!==t?t:l.ROUND_ROBIN,this.opts.enableEvents=null===(r=e.enableEvents)||void 0===r||r}get numberOfRunningTasks(){return this.promiseMap.size}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeTasksMapEntry()}findFreeTasksMapEntry(){for(const[e,t]of this.tasks)if(0===t)return[e,t];return!1}execute(e){const t=this.chooseWorker(),r=++this.nextMessageId,s=this.internalExecute(t,r);return this.checkAndEmitBusy(),this.sendToWorker(t,{data:e||{},id:r}),s}async destroy(){await Promise.all(this.workers.map((e=>this.destroyWorker(e))))}setupHook(){}increaseWorkersTask(e){this.stepWorkerNumberOfTasks(e,1)}decreaseWorkersTasks(e){this.stepWorkerNumberOfTasks(e,-1)}stepWorkerNumberOfTasks(e,t){const r=this.tasks.get(e);if(void 0===r)throw Error("Worker could not be found in tasks map");this.tasks.set(e,r+t)}removeWorker(e){const t=this.workers.indexOf(e);this.workers.splice(t,1),this.tasks.delete(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}internalExecute(e,t){return this.increaseWorkersTask(e),new Promise(((r,s)=>{this.promiseMap.set(t,{resolve:r,reject:s,worker:e})}))}createAndSetupWorker(){var e,t,r,s;const o=this.createWorker();return o.on("message",null!==(e=this.opts.messageHandler)&&void 0!==e?e:h),o.on("error",null!==(t=this.opts.errorHandler)&&void 0!==t?t:h),o.on("online",null!==(r=this.opts.onlineHandler)&&void 0!==r?r:h),o.on("exit",null!==(s=this.opts.exitHandler)&&void 0!==s?s:h),o.once("exit",(()=>this.removeWorker(o))),this.workers.push(o),this.tasks.set(o,0),this.afterWorkerSetup(o),o}workerListener(){return e=>{if(e.id){const t=this.promiseMap.get(e.id);t&&(this.decreaseWorkersTasks(t.worker),e.error?t.reject(e.error):t.resolve(e.data),this.promiseMap.delete(e.id))}}}checkAndEmitBusy(){var e;this.opts.enableEvents&&this.busy&&(null===(e=this.emitter)||void 0===e||e.emit("busy"))}}class w extends y{constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupMaster({exec:this.filePath})}isMain(){return t.isMaster}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return i.FIXED}get busy(){return this.internalGetBusyStatus()}}class m extends y{constructor(e,t,r={}){super(e,t,r)}isMain(){return r.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){var r;null===(r=e.port2)||void 0===r||r.on("message",t)}createWorker(){return new r.Worker(this.filePath,{env:r.SHARE_ENV})}afterWorkerSetup(e){const{port1:t,port2:s}=new r.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return i.FIXED}get busy(){return this.internalGetBusyStatus()}}const g=c.SOFT;class f extends s.AsyncResource{constructor(e,t,r,s,o={killBehavior:g,maxInactiveTime:6e4}){var i,n;super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(r),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),t||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(null!==(i=this.opts.maxInactiveTime)&&void 0!==i?i:6e4)/2),this.checkAlive.bind(this)()),null===(n=this.mainWorker)||void 0===n||n.on("message",(e=>{(null==e?void 0:e.data)&&e.id?this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):e.parent?this.mainWorker=e.parent:e.kill&&(this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}))}checkWorkerOptions(e){var t,r;this.opts.killBehavior=null!==(t=e.killBehavior)&&void 0!==t?t:g,this.opts.maxInactiveTime=null!==(r=e.maxInactiveTime)&&void 0!==r?r:6e4,this.opts.async=!!e.async}checkFunctionInput(e){if(!e)throw new Error("fn parameter is mandatory")}getMainWorker(){if(!this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){var e;Date.now()-this.lastTaskTimestamp>(null!==(e=this.opts.maxInactiveTime)&&void 0!==e?e:6e4)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const r=e(t.data);this.sendToMainWorker({data:r,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,t){e(t.data).then((e=>(this.sendToMainWorker({data:e,id:t.id}),null))).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(h)}}exports.AbstractWorker=f,exports.ClusterWorker=class extends f{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isMaster,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends w{constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return i.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends m{constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return i.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=w,exports.FixedThreadPool=m,exports.KillBehaviors=c,exports.ThreadWorker=class extends f{constructor(e,t={}){super("worker-thread-pool:poolifier",r.isMainThread,e,r.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=l; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("events"),t=require("cluster"),r=require("worker_threads"),s=require("async_hooks");function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i,n=o(e),a=o(t);!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(i||(i={}));class h extends n.default{}const c=()=>{},l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const k=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED"});class u{constructor(e){this.pool=e,this.nextWorkerIndex=0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.pool.workers.length-1===this.nextWorkerIndex?0:this.nextWorkerIndex+1,e}}class p{constructor(e){this.pool=e}choose(){const e=this.pool.type===i.DYNAMIC;let t,r=1/0;for(const[s,o]of this.pool.tasks){if(!e&&0===o)return s;o<r&&(t=s,r=o)}return t}}class d{constructor(e,t,r=k.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=t,this.workerChoiceStrategy=W.getWorkerChoiceStrategy(this.pool,r)}choose(){const e=this.pool.findFreeTasksMapEntry();return e?e[0]:this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback()}}class y{constructor(e,t,r=k.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=t,this.setWorkerChoiceStrategy(r)}getPoolWorkerChoiceStrategy(e=k.ROUND_ROBIN){return this.pool.type===i.DYNAMIC?new d(this.pool,this.createDynamicallyWorkerCallback,e):W.getWorkerChoiceStrategy(this.pool,e)}setWorkerChoiceStrategy(e){this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class W{static getWorkerChoiceStrategy(e,t=k.ROUND_ROBIN){switch(t){case k.ROUND_ROBIN:return new u(e);case k.LESS_RECENTLY_USED:return new p(e);default:throw new Error(`Worker choice strategy '${t}' not found`)}}}class m{constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,this.workers=[],this.tasks=new Map,this.promiseMap=new Map,this.nextMessageId=0,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();this.opts.enableEvents&&(this.emitter=new h),this.workerChoiceStrategyContext=new y(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(async t=>{const r=this.tasks.get(e);var s;s=l.HARD,(t.kill===s||0===r)&&await this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(!e)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new Error("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new Error("Cannot instantiate a pool with a negative number of workers");if(this.type===i.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){var t,r;this.opts.workerChoiceStrategy=null!==(t=e.workerChoiceStrategy)&&void 0!==t?t:k.ROUND_ROBIN,this.opts.enableEvents=null===(r=e.enableEvents)||void 0===r||r}get numberOfRunningTasks(){return this.promiseMap.size}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeTasksMapEntry()}findFreeTasksMapEntry(){for(const[e,t]of this.tasks)if(0===t)return[e,t];return!1}execute(e){const t=this.chooseWorker(),r=++this.nextMessageId,s=this.internalExecute(t,r);return this.checkAndEmitBusy(),this.sendToWorker(t,{data:e||{},id:r}),s}async destroy(){await Promise.all(this.workers.map((e=>this.destroyWorker(e))))}setupHook(){}increaseWorkersTask(e){this.stepWorkerNumberOfTasks(e,1)}decreaseWorkersTasks(e){this.stepWorkerNumberOfTasks(e,-1)}stepWorkerNumberOfTasks(e,t){const r=this.tasks.get(e);if(void 0===r)throw Error("Worker could not be found in tasks map");this.tasks.set(e,r+t)}removeWorker(e){const t=this.workers.indexOf(e);this.workers.splice(t,1),this.tasks.delete(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}internalExecute(e,t){return this.increaseWorkersTask(e),new Promise(((r,s)=>{this.promiseMap.set(t,{resolve:r,reject:s,worker:e})}))}createAndSetupWorker(){var e,t,r,s;const o=this.createWorker();return o.on("message",null!==(e=this.opts.messageHandler)&&void 0!==e?e:c),o.on("error",null!==(t=this.opts.errorHandler)&&void 0!==t?t:c),o.on("online",null!==(r=this.opts.onlineHandler)&&void 0!==r?r:c),o.on("exit",null!==(s=this.opts.exitHandler)&&void 0!==s?s:c),o.once("exit",(()=>this.removeWorker(o))),this.workers.push(o),this.tasks.set(o,0),this.afterWorkerSetup(o),o}workerListener(){return e=>{if(e.id){const t=this.promiseMap.get(e.id);t&&(this.decreaseWorkersTasks(t.worker),e.error?t.reject(e.error):t.resolve(e.data),this.promiseMap.delete(e.id))}}}checkAndEmitBusy(){var e;this.opts.enableEvents&&this.busy&&(null===(e=this.emitter)||void 0===e||e.emit("busy"))}}class w extends m{constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){a.default.setupPrimary({exec:this.filePath})}isMain(){return a.default.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return a.default.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return i.FIXED}get busy(){return this.internalGetBusyStatus()}}class f extends m{constructor(e,t,r={}){super(e,t,r)}isMain(){return r.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){var r;null===(r=e.port2)||void 0===r||r.on("message",t)}createWorker(){return new r.Worker(this.filePath,{env:r.SHARE_ENV})}afterWorkerSetup(e){const{port1:t,port2:s}=new r.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return i.FIXED}get busy(){return this.internalGetBusyStatus()}}const g=l.SOFT;class v extends s.AsyncResource{constructor(e,t,r,s,o={killBehavior:g,maxInactiveTime:6e4}){var i,n;super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(r),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),t||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(null!==(i=this.opts.maxInactiveTime)&&void 0!==i?i:6e4)/2),this.checkAlive.bind(this)()),null===(n=this.mainWorker)||void 0===n||n.on("message",(e=>{(null==e?void 0:e.data)&&e.id?this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):e.parent?this.mainWorker=e.parent:e.kill&&(this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}))}checkWorkerOptions(e){var t,r;this.opts.killBehavior=null!==(t=e.killBehavior)&&void 0!==t?t:g,this.opts.maxInactiveTime=null!==(r=e.maxInactiveTime)&&void 0!==r?r:6e4,this.opts.async=!!e.async}checkFunctionInput(e){if(!e)throw new Error("fn parameter is mandatory")}getMainWorker(){if(!this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){var e;Date.now()-this.lastTaskTimestamp>(null!==(e=this.opts.maxInactiveTime)&&void 0!==e?e:6e4)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const r=e(t.data);this.sendToMainWorker({data:r,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,t){e(t.data).then((e=>(this.sendToMainWorker({data:e,id:t.id}),null))).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(c)}}exports.AbstractWorker=v,exports.ClusterWorker=class extends v{constructor(e,t={}){super("worker-cluster-pool:poolifier",a.default.isPrimary,e,a.default.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends w{constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return i.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends f{constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return i.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=w,exports.FixedThreadPool=f,exports.KillBehaviors=l,exports.ThreadWorker=class extends v{constructor(e,t={}){super("worker-thread-pool:poolifier",r.isMainThread,e,r.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=k; |
{ | ||
"name": "poolifier", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation", | ||
@@ -65,24 +65,24 @@ "main": "lib/index.js", | ||
"devDependencies": { | ||
"@types/node": "^14.17.12", | ||
"@typescript-eslint/eslint-plugin": "^4.29.3", | ||
"@typescript-eslint/parser": "^4.29.3", | ||
"@types/node": "^16.11.19", | ||
"@typescript-eslint/eslint-plugin": "^5.9.0", | ||
"@typescript-eslint/parser": "^5.9.0", | ||
"benchmark": "^2.1.4", | ||
"eslint": "^7.32.0", | ||
"eslint": "^8.6.0", | ||
"eslint-config-standard": "^16.0.3", | ||
"eslint-define-config": "^1.0.9", | ||
"eslint-plugin-import": "^2.24.2", | ||
"eslint-plugin-jsdoc": "^36.0.8", | ||
"eslint-define-config": "^1.2.1", | ||
"eslint-plugin-import": "^2.25.4", | ||
"eslint-plugin-jsdoc": "^37.5.1", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettierx": "^0.18.0", | ||
"eslint-plugin-promise": "^5.1.0", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"eslint-plugin-spellcheck": "0.0.19", | ||
"expect": "^27.1.0", | ||
"expect": "^27.4.6", | ||
"microtime": "^3.0.0", | ||
"mocha": "^9.1.1", | ||
"mochawesome": "^6.2.2", | ||
"mocha": "^9.1.3", | ||
"mochawesome": "^7.0.1", | ||
"nyc": "^15.1.0", | ||
"prettier": "^2.3.2", | ||
"prettier-plugin-organize-imports": "^2.3.3", | ||
"prettierx": "^0.19.0", | ||
"rollup": "^2.56.3", | ||
"prettier": "^2.5.1", | ||
"prettier-plugin-organize-imports": "^2.3.4", | ||
"prettierx": "^0.18.3", | ||
"rollup": "^2.63.0", | ||
"rollup-plugin-analyzer": "^4.0.0", | ||
@@ -93,11 +93,11 @@ "rollup-plugin-command": "^1.1.3", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"rollup-plugin-typescript2": "^0.30.0", | ||
"source-map-support": "^0.5.19", | ||
"typedoc": "^0.21.8", | ||
"typescript": "^4.4.2" | ||
"rollup-plugin-ts": "^2.0.5", | ||
"source-map-support": "^0.5.21", | ||
"typedoc": "^0.22.10", | ||
"typescript": "^4.5.4" | ||
}, | ||
"engines": { | ||
"node": ">=12.11.0", | ||
"npm": ">=6.0.0 <7" | ||
"node": ">=16.0.0", | ||
"npm": ">=8.0.0" | ||
} | ||
} |
@@ -68,3 +68,3 @@ <div align="center"> | ||
<span> · </span> | ||
<a href="#node-versions"> Node versions</a> | ||
<a href="#node-versions">Node versions</a> | ||
<span> · </span> | ||
@@ -71,0 +71,0 @@ <a href="#api">API</a> |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56737
5
807
1