poolifier 2.4.3 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/lib/circular-array.d.ts +15 -0
- package/lib/index.d.ts +4 -3
- package/lib/index.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/pools/abstract-pool.d.ts +48 -39
- package/lib/pools/cluster/dynamic.d.ts +1 -1
- package/lib/pools/pool-internal.d.ts +9 -28
- package/lib/pools/pool.d.ts +24 -2
- package/lib/pools/selection-strategies/abstract-worker-choice-strategy.d.ts +11 -8
- package/lib/pools/selection-strategies/fair-share-worker-choice-strategy.d.ts +5 -5
- package/lib/pools/selection-strategies/less-busy-worker-choice-strategy.d.ts +3 -3
- package/lib/pools/selection-strategies/less-used-worker-choice-strategy.d.ts +3 -3
- package/lib/pools/selection-strategies/round-robin-worker-choice-strategy.d.ts +5 -5
- package/lib/pools/selection-strategies/selection-strategies-types.d.ts +16 -16
- package/lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.d.ts +9 -8
- package/lib/pools/selection-strategies/worker-choice-strategy-context.d.ts +10 -11
- package/lib/pools/thread/dynamic.d.ts +1 -1
- package/lib/pools/{pool-worker.d.ts → worker.d.ts} +30 -2
- package/lib/utility-types.d.ts +5 -5
- package/lib/utils.d.ts +7 -0
- package/lib/worker/abstract-worker.d.ts +12 -6
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -113,22 +113,22 @@ Instantiate your pool based on your needed :
|
|
|
113
113
|
|
|
114
114
|
```js
|
|
115
115
|
'use strict'
|
|
116
|
-
const { FixedThreadPool,
|
|
116
|
+
const { DynamicThreadPool, FixedThreadPool, PoolEvents } = require('poolifier')
|
|
117
117
|
|
|
118
118
|
// a fixed worker-threads pool
|
|
119
119
|
const pool = new FixedThreadPool(15,
|
|
120
120
|
'./yourWorker.js',
|
|
121
121
|
{ errorHandler: (e) => console.error(e), onlineHandler: () => console.log('worker is online') })
|
|
122
122
|
|
|
123
|
-
pool.emitter.on(
|
|
123
|
+
pool.emitter.on(PoolEvents.busy, () => console.log('Pool is busy'))
|
|
124
124
|
|
|
125
125
|
// or a dynamic worker-threads pool
|
|
126
126
|
const pool = new DynamicThreadPool(10, 100,
|
|
127
127
|
'./yourWorker.js',
|
|
128
128
|
{ errorHandler: (e) => console.error(e), onlineHandler: () => console.log('worker is online') })
|
|
129
129
|
|
|
130
|
-
pool.emitter.on(
|
|
131
|
-
pool.emitter.on(
|
|
130
|
+
pool.emitter.on(PoolEvents.full, () => console.log('Pool is full'))
|
|
131
|
+
pool.emitter.on(PoolEvents.busy, () => console.log('Pool is busy'))
|
|
132
132
|
|
|
133
133
|
// the execute method signature is the same for both implementations,
|
|
134
134
|
// so you can easy switch from one to another
|
|
@@ -174,7 +174,14 @@ Node versions >= 16.x are supported.
|
|
|
174
174
|
`WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks
|
|
175
175
|
Default: `WorkerChoiceStrategies.ROUND_ROBIN`
|
|
176
176
|
|
|
177
|
+
- `workerChoiceStrategyOptions` (optional) - The worker choice strategy options object to use in this pool.
|
|
178
|
+
Properties:
|
|
179
|
+
|
|
180
|
+
- `medRunTime` (optional) - Use the tasks median run time instead of the tasks average run time in worker choice strategies.
|
|
181
|
+
Default: { medRunTime: false }
|
|
182
|
+
|
|
177
183
|
- `enableEvents` (optional) - Events emission enablement in this pool. Default: true
|
|
184
|
+
- `enableTasksQueue` (optional, experimental) - Tasks queue per worker enablement in this pool. Default: false
|
|
178
185
|
|
|
179
186
|
### `pool = new DynamicThreadPool/DynamicClusterPool(min, max, filePath, opts)`
|
|
180
187
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array with a maximum length shifting items when full.
|
|
3
|
+
*/
|
|
4
|
+
export declare class CircularArray<T> extends Array<T> {
|
|
5
|
+
size: number;
|
|
6
|
+
constructor(size?: number, ...items: T[]);
|
|
7
|
+
push(...items: T[]): number;
|
|
8
|
+
unshift(...items: T[]): number;
|
|
9
|
+
concat(...items: Array<T | ConcatArray<T>>): CircularArray<T>;
|
|
10
|
+
splice(start: number, deleteCount?: number, ...items: T[]): T[];
|
|
11
|
+
resize(size: number): void;
|
|
12
|
+
empty(): boolean;
|
|
13
|
+
full(): boolean;
|
|
14
|
+
private checkSize;
|
|
15
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { DynamicClusterPool } from './pools/cluster/dynamic';
|
|
2
2
|
export { FixedClusterPool } from './pools/cluster/fixed';
|
|
3
3
|
export type { ClusterPoolOptions } from './pools/cluster/fixed';
|
|
4
|
-
export
|
|
5
|
-
export type {
|
|
4
|
+
export { PoolEvents } from './pools/pool';
|
|
5
|
+
export type { IPool, PoolEmitter, PoolOptions, PoolEvent } from './pools/pool';
|
|
6
|
+
export type { ErrorHandler, ExitHandler, MessageHandler, OnlineHandler } from './pools/worker';
|
|
6
7
|
export { WorkerChoiceStrategies } from './pools/selection-strategies/selection-strategies-types';
|
|
7
|
-
export type { WorkerChoiceStrategy } from './pools/selection-strategies/selection-strategies-types';
|
|
8
|
+
export type { WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types';
|
|
8
9
|
export { DynamicThreadPool } from './pools/thread/dynamic';
|
|
9
10
|
export { FixedThreadPool } from './pools/thread/fixed';
|
|
10
11
|
export type { ThreadWorkerWithMessageChannel } from './pools/thread/fixed';
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,r=require("node:cluster"),t=require("node:crypto"),s=require("node:events"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));const a=Object.freeze((()=>{})),h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class k extends s{}const u=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class c{pool;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1};constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose.bind(this)}}class l extends c{requiredStatistics={runTime:!0,avgRunTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workers.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.workers[e].tasksUsage.avgRunTime??0)})}}class p extends c{requiredStatistics={runTime:!0,avgRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class m extends c{reset(){return!0}choose(){const e=this.pool.findFreeWorkerKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class d extends c{nextWorkerId=0;reset(){return this.nextWorkerId=0,!0}choose(){const e=this.nextWorkerId;return this.nextWorkerId=this.nextWorkerId===this.pool.workers.length-1?0:this.nextWorkerId+1,e}remove(e){return this.nextWorkerId===e&&(0===this.pool.workers.length?this.nextWorkerId=0:this.nextWorkerId=this.nextWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.nextWorkerId),!0}}class w extends c{requiredStatistics={runTime:!0,avgRunTime:!0};currentWorkerId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerId=this.currentWorkerId===this.pool.workers.length-1?0:this.currentWorkerId+1,this.setWorkerTaskRunTime(this.currentWorkerId,t,0)),e}remove(e){this.currentWorkerId===e&&(0===this.pool.workers.length?this.currentWorkerId=0:this.currentWorkerId=this.currentWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.currentWorkerId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workers.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.workers[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of i.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/i.cpus().length)}}class g{createWorkerCallback;workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r,t=u.ROUND_ROBIN){this.createWorkerCallback=r,this.workerChoiceStrategyType=t,this.execute.bind(this),this.workerChoiceStrategies=new Map([[u.ROUND_ROBIN,new d(e)],[u.LESS_USED,new m(e)],[u.LESS_BUSY,new p(e)],[u.FAIR_SHARE,new l(e)],[u.WEIGHTED_ROUND_ROBIN,new w(e)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType===e?this.workerChoiceStrategies.get(e)?.reset():this.workerChoiceStrategyType=e}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategyType);return e.isDynamicPool&&!e.pool.full&&-1===e.pool.findFreeWorkerKey()?this.createWorkerCallback():e.choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class T{numberOfWorkers;filePath;opts;workers=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!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.chooseWorker.bind(this),this.internalExecute.bind(this),this.checkAndEmitFull.bind(this),this.checkAndEmitBusy.bind(this),this.sendToWorker.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new k),this.workerChoiceStrategyContext=new g(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=h.HARD,(r.kill===t||0===this.getWorkerTasksUsage(e)?.running)&&this.destroyWorker(e)})),this.getWorkerKey(e)}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(this.opts.workerChoiceStrategy=e.workerChoiceStrategy??u.ROUND_ROBIN,!Object.values(u).includes(this.opts.workerChoiceStrategy))throw new Error(`Invalid worker choice strategy '${this.opts.workerChoiceStrategy}'`);this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseResponseMap.size}getWorkerKey(e){return this.workers.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const[e,r]of this.workers.entries())this.setWorker(e,r.worker,{run:0,running:0,runTime:0,avgRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return this.numberOfRunningTasks>=this.numberOfWorkers&&-1===this.findFreeWorkerKey()}findFreeWorkerKey(){return this.workers.findIndex((e=>0===e.tasksUsage.running))}async execute(e){const[r,s]=this.chooseWorker(),i=t.randomUUID(),o=this.internalExecute(r,s,i);return this.checkAndEmitFull(),this.checkAndEmitBusy(),this.sendToWorker(s,{data:e??{},id:i}),o}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e.worker)})))}setupHook(){}beforePromiseResponseHook(e){++this.workers[e].tasksUsage.running}afterPromiseResponseHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.taskRunTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run))}chooseWorker(){const e=this.workerChoiceStrategyContext.execute();return[e,this.workers[e].worker]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??a),e.on("error",this.opts.errorHandler??a),e.on("online",this.opts.onlineHandler??a),e.on("exit",this.opts.exitHandler??a),e.once("exit",(()=>{this.removeWorker(e)})),this.pushWorker(e,{run:0,running:0,runTime:0,avgRunTime:0,error:0}),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);null!=r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseResponseHook(r.worker,e),this.promiseResponseMap.delete(e.id))}}}async internalExecute(e,r,t){return this.beforePromiseResponseHook(e),await new Promise(((e,s)=>{this.promiseResponseMap.set(t,{resolve:e,reject:s,worker:r})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}checkAndEmitFull(){this.type===e.DYNAMIC&&!0===this.opts.enableEvents&&this.full&&this.emitter?.emit("full")}getWorkerTasksUsage(e){const r=this.getWorkerKey(e);if(-1!==r)return this.workers[r].tasksUsage;throw new Error("Worker could not be found in the pool")}pushWorker(e,r){this.workers.push({worker:e,tasksUsage:r})}setWorker(e,r,t){this.workers[e]={worker:r,tasksUsage:t}}removeWorker(e){const r=this.getWorkerKey(e);this.workers.splice(r,1),this.workerChoiceStrategyContext.remove(r)}}class W extends T{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workers.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class f extends T{constructor(e,r,t={}){super(e,r,t)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new o.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workers.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const y=6e4,R=h.SOFT;class S extends n.AsyncResource{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:R,maxInactiveTime:y}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=Date.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??y)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){null!=e.data&&null!=e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??R,this.opts.maxInactiveTime=e.maxInactiveTime??y,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??y)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),i=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=Date.now())}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=Date.now())})).catch(a)}}exports.ClusterWorker=class extends S{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends W{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workers.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerKey()}},exports.DynamicThreadPool=class extends f{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workers.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerKey()}},exports.FixedClusterPool=W,exports.FixedThreadPool=f,exports.KillBehaviors=h,exports.ThreadWorker=class extends S{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=u;
|
|
1
|
+
"use strict";var e,r=require("node:cluster"),t=require("node:crypto"),s=require("node:events"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));const a=Object.freeze((()=>{})),h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class u extends s{}const k=Object.freeze({full:"full",busy:"busy"}),c=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class l{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(r,t={medRunTime:!1}){this.pool=r,this.opts=t,this.checkOptions(),this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose.bind(this)}checkOptions(){this.requiredStatistics.avgRunTime&&!0===this.opts.medRunTime&&(this.requiredStatistics.medRunTime=!0)}}class d extends l{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),t=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(t??0)})}}class p extends l{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class m extends l{reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class g extends l{nextWorkerNodeId=0;reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class T extends l{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,r){super(e,r),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,t,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of i.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/i.cpus().length)}}class w{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=c.ROUND_ROBIN,t={medRunTime:!1}){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[c.ROUND_ROBIN,new g(e,t)],[c.LESS_USED,new m(e,t)],[c.LESS_BUSY,new p(e,t)],[c.FAIR_SHARE,new d(e,t)],[c.WEIGHTED_ROUND_ROBIN,new T(e,t)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType!==e&&(this.workerChoiceStrategyType=e),this.workerChoiceStrategies.get(this.workerChoiceStrategyType)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class W extends Array{size;constructor(e=1024,...r){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...r)}push(...e){const r=super.push(...e);return r>this.size&&super.splice(0,r-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const r=super.concat(e);return r.size=this.size,r.length>r.size&&r.splice(0,r.length-r.size),r}splice(e,r,...t){let s;return arguments.length>=3&&void 0!==r?(s=super.splice(e,r),this.push(...t)):s=2===arguments.length?super.splice(e,r):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let r=e;r<this.size;r++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class f{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!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.chooseWorkerNode.bind(this),this.internalExecute.bind(this),this.checkAndEmitEvents.bind(this),this.sendToWorker.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new w(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??c.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??{medRunTime:!1},this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1}checkValidWorkerChoiceStrategy(e){if(!Object.values(c).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,r)=>e+r.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,r)=>e+r.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const[e,r]of this.workerNodes.entries())this.setWorkerNode(e,r.worker,{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0},r.tasksQueue);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return this.numberOfRunningTasks>=this.numberOfWorkers&&-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[r,s]=this.chooseWorkerNode(),i={data:e??{},id:t.randomUUID()},o=this.internalExecute(r,s,i);let n=i;return!0===this.opts.enableTasksQueue&&(this.busy||this.tasksQueueSize(r)>0)&&(n=this.enqueueDequeueTask(r,i)),this.sendToWorker(s.worker,n),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async e=>{this.flushTasksQueueByWorker(e.worker),await this.destroyWorker(e.worker)})))}setupHook(){}beforePromiseResponseHook(e){++this.workerNodes[e].tasksUsage.running}afterPromiseResponseHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(t.runTimeHistory.push(r.runTime??0),t.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const r=e.slice().sort(((e,r)=>e-r)),t=Math.floor(r.length/2);return r.length%2==0?r[t/2]:(r[t-1]+r[t])/2})(t.runTimeHistory)))}chooseWorkerNode(){let r;if(this.type!==e.DYNAMIC||this.full||-1!==this.findFreeWorkerNodeKey())r=this.workerChoiceStrategyContext.execute();else{const e=this.createAndSetupWorker();this.registerWorkerMessageListener(e,(r=>{var t;t=h.HARD,(r.kill===t||null!=r.kill&&0===this.getWorkerTasksUsage(e)?.running)&&(this.flushTasksQueueByWorker(e),this.destroyWorker(e))})),r=this.getWorkerNodeKey(e)}return[r,this.workerNodes[r]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??a),e.on("error",this.opts.errorHandler??a),e.on("online",this.opts.onlineHandler??a),e.on("exit",this.opts.exitHandler??a),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);if(null!=r){null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseResponseHook(r.worker,e),this.promiseResponseMap.delete(e.id);const t=this.getWorkerNodeKey(r.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(t)>0&&this.sendToWorker(r.worker,this.dequeueTask(t))}}}}async internalExecute(e,r,t){return this.beforePromiseResponseHook(e),await new Promise(((e,s)=>{this.promiseResponseMap.set(t.id,{resolve:e,reject:s,worker:r.worker})}))}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(k.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(k.full))}getWorkerTasksUsage(e){const r=this.getWorkerNodeKey(e);if(-1!==r)return this.workerNodes[r].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,r,t,s){this.workerNodes[e]={worker:r,tasksUsage:t,tasksQueue:s}}removeWorkerNode(e){const r=this.getWorkerNodeKey(e);this.workerNodes.splice(r,1),this.workerChoiceStrategyContext.remove(r)}enqueueDequeueTask(e,r){return this.enqueueTask(e,r),this.dequeueTask(e)}enqueueTask(e,r){this.workerNodes[e].tasksQueue.push(r)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0){for(const r of this.workerNodes[e].tasksQueue)this.sendToWorker(this.workerNodes[e].worker,r);this.workerNodes[e].tasksQueue=[]}}flushTasksQueueByWorker(e){const r=this.getWorkerNodeKey(e);this.flushTasksQueue(r)}}class y extends f{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class N extends f{constructor(e,r,t={}){super(e,r,t)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new o.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const S=6e4,R=h.SOFT;class x extends n.AsyncResource{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:R,maxInactiveTime:S}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??S)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){null!=e.data&&null!=e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??R,this.opts.maxInactiveTime=e.maxInactiveTime??S,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??S)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=performance.now(),s=e(r.data),i=performance.now()-t;this.sendToMainWorker({data:s,id:r.id,runTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,r){const t=performance.now();e(r.data).then((e=>{const s=performance.now()-t;return this.sendToMainWorker({data:e,id:r.id,runTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(a)}}exports.ClusterWorker=class extends x{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends y{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerNodeKey()}},exports.DynamicThreadPool=class extends N{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerNodeKey()}},exports.FixedClusterPool=y,exports.FixedThreadPool=N,exports.KillBehaviors=h,exports.PoolEvents=k,exports.ThreadWorker=class extends x{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=c;
|
package/lib/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:cluster";import r from"node:crypto";import t from"node:events";import{cpus as s}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as a,parentPort as h}from"node:worker_threads";import{AsyncResource as k}from"node:async_hooks";var u;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(u||(u={}));const c=Object.freeze((()=>{})),l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class m extends t{}const p=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class w{pool;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1};constructor(e){this.pool=e,this.isDynamicPool=this.pool.type===u.DYNAMIC,this.choose.bind(this)}}class d extends w{requiredStatistics={runTime:!0,avgRunTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workers.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.workers[e].tasksUsage.avgRunTime??0)})}}class g extends w{requiredStatistics={runTime:!0,avgRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class T extends w{reset(){return!0}choose(){const e=this.pool.findFreeWorkerKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class W extends w{nextWorkerId=0;reset(){return this.nextWorkerId=0,!0}choose(){const e=this.nextWorkerId;return this.nextWorkerId=this.nextWorkerId===this.pool.workers.length-1?0:this.nextWorkerId+1,e}remove(e){return this.nextWorkerId===e&&(0===this.pool.workers.length?this.nextWorkerId=0:this.nextWorkerId=this.nextWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.nextWorkerId),!0}}class f extends w{requiredStatistics={runTime:!0,avgRunTime:!0};currentWorkerId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerId=this.currentWorkerId===this.pool.workers.length-1?0:this.currentWorkerId+1,this.setWorkerTaskRunTime(this.currentWorkerId,t,0)),e}remove(e){this.currentWorkerId===e&&(0===this.pool.workers.length?this.currentWorkerId=0:this.currentWorkerId=this.currentWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.currentWorkerId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workers.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.workers[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of s()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s().length)}}class y{createWorkerCallback;workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r,t=p.ROUND_ROBIN){this.createWorkerCallback=r,this.workerChoiceStrategyType=t,this.execute.bind(this),this.workerChoiceStrategies=new Map([[p.ROUND_ROBIN,new W(e)],[p.LESS_USED,new T(e)],[p.LESS_BUSY,new g(e)],[p.FAIR_SHARE,new d(e)],[p.WEIGHTED_ROUND_ROBIN,new f(e)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType===e?this.workerChoiceStrategies.get(e)?.reset():this.workerChoiceStrategyType=e}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategyType);return e.isDynamicPool&&!e.pool.full&&-1===e.pool.findFreeWorkerKey()?this.createWorkerCallback():e.choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class R{numberOfWorkers;filePath;opts;workers=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!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.chooseWorker.bind(this),this.internalExecute.bind(this),this.checkAndEmitFull.bind(this),this.checkAndEmitBusy.bind(this),this.sendToWorker.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new m),this.workerChoiceStrategyContext=new y(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=l.HARD,(r.kill===t||0===this.getWorkerTasksUsage(e)?.running)&&this.destroyWorker(e)})),this.getWorkerKey(e)}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)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 TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===u.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(this.opts.workerChoiceStrategy=e.workerChoiceStrategy??p.ROUND_ROBIN,!Object.values(p).includes(this.opts.workerChoiceStrategy))throw new Error(`Invalid worker choice strategy '${this.opts.workerChoiceStrategy}'`);this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseResponseMap.size}getWorkerKey(e){return this.workers.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const[e,r]of this.workers.entries())this.setWorker(e,r.worker,{run:0,running:0,runTime:0,avgRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return this.numberOfRunningTasks>=this.numberOfWorkers&&-1===this.findFreeWorkerKey()}findFreeWorkerKey(){return this.workers.findIndex((e=>0===e.tasksUsage.running))}async execute(e){const[t,s]=this.chooseWorker(),i=r.randomUUID(),o=this.internalExecute(t,s,i);return this.checkAndEmitFull(),this.checkAndEmitBusy(),this.sendToWorker(s,{data:e??{},id:i}),o}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e.worker)})))}setupHook(){}beforePromiseResponseHook(e){++this.workers[e].tasksUsage.running}afterPromiseResponseHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.taskRunTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run))}chooseWorker(){const e=this.workerChoiceStrategyContext.execute();return[e,this.workers[e].worker]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorker(e)})),this.pushWorker(e,{run:0,running:0,runTime:0,avgRunTime:0,error:0}),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);null!=r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseResponseHook(r.worker,e),this.promiseResponseMap.delete(e.id))}}}async internalExecute(e,r,t){return this.beforePromiseResponseHook(e),await new Promise(((e,s)=>{this.promiseResponseMap.set(t,{resolve:e,reject:s,worker:r})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}checkAndEmitFull(){this.type===u.DYNAMIC&&!0===this.opts.enableEvents&&this.full&&this.emitter?.emit("full")}getWorkerTasksUsage(e){const r=this.getWorkerKey(e);if(-1!==r)return this.workers[r].tasksUsage;throw new Error("Worker could not be found in the pool")}pushWorker(e,r){this.workers.push({worker:e,tasksUsage:r})}setWorker(e,r,t){this.workers[e]={worker:r,tasksUsage:t}}removeWorker(e){const r=this.getWorkerKey(e);this.workers.splice(r,1),this.workerChoiceStrategyContext.remove(r)}}class S extends R{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){e.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return e.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return e.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return u.FIXED}get full(){return this.workers.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class I extends S{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return u.DYNAMIC}get full(){return this.workers.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerKey()}}class x extends R{constructor(e,r,t={}){super(e,r,t)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:r,port2:t}=new a;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return u.FIXED}get full(){return this.workers.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class v extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return u.DYNAMIC}get full(){return this.workers.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerKey()}}const E=6e4,C=l.SOFT;class b extends k{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:C,maxInactiveTime:E}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=Date.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??E)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){null!=e.data&&null!=e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??C,this.opts.maxInactiveTime=e.maxInactiveTime??E,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??E)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),i=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=Date.now())}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=Date.now())})).catch(c)}}class M extends b{constructor(r,t={}){super("worker-cluster-pool:poolifier",e.isPrimary,r,e.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class D extends b{constructor(e,r={}){super("worker-thread-pool:poolifier",i,e,h,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{M as ClusterWorker,I as DynamicClusterPool,v as DynamicThreadPool,S as FixedClusterPool,x as FixedThreadPool,l as KillBehaviors,D as ThreadWorker,p as WorkerChoiceStrategies};
|
|
1
|
+
import e from"node:cluster";import r from"node:crypto";import t from"node:events";import{cpus as s}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as h,parentPort as a}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));const c=Object.freeze((()=>{})),l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class d extends t{}const m=Object.freeze({full:"full",busy:"busy"}),p=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class g{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,r={medRunTime:!1}){this.pool=e,this.opts=r,this.checkOptions(),this.isDynamicPool=this.pool.type===k.DYNAMIC,this.choose.bind(this)}checkOptions(){this.requiredStatistics.avgRunTime&&!0===this.opts.medRunTime&&(this.requiredStatistics.medRunTime=!0)}}class w extends g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),t=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(t??0)})}}class T extends g{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class f extends g{reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class W extends g{nextWorkerNodeId=0;reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class y extends g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,r){super(e,r),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,t,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of s()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s().length)}}class N{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=p.ROUND_ROBIN,t={medRunTime:!1}){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[p.ROUND_ROBIN,new W(e,t)],[p.LESS_USED,new f(e,t)],[p.LESS_BUSY,new T(e,t)],[p.FAIR_SHARE,new w(e,t)],[p.WEIGHTED_ROUND_ROBIN,new y(e,t)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType!==e&&(this.workerChoiceStrategyType=e),this.workerChoiceStrategies.get(this.workerChoiceStrategyType)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class S extends Array{size;constructor(e=1024,...r){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...r)}push(...e){const r=super.push(...e);return r>this.size&&super.splice(0,r-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const r=super.concat(e);return r.size=this.size,r.length>r.size&&r.splice(0,r.length-r.size),r}splice(e,r,...t){let s;return arguments.length>=3&&void 0!==r?(s=super.splice(e,r),this.push(...t)):s=2===arguments.length?super.splice(e,r):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let r=e;r<this.size;r++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class R{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!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.chooseWorkerNode.bind(this),this.internalExecute.bind(this),this.checkAndEmitEvents.bind(this),this.sendToWorker.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new d),this.workerChoiceStrategyContext=new N(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)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 TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??p.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??{medRunTime:!1},this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1}checkValidWorkerChoiceStrategy(e){if(!Object.values(p).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,r)=>e+r.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,r)=>e+r.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const[e,r]of this.workerNodes.entries())this.setWorkerNode(e,r.worker,{run:0,running:0,runTime:0,runTimeHistory:new S,avgRunTime:0,medRunTime:0,error:0},r.tasksQueue);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return this.numberOfRunningTasks>=this.numberOfWorkers&&-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,s]=this.chooseWorkerNode(),i={data:e??{},id:r.randomUUID()},o=this.internalExecute(t,s,i);let n=i;return!0===this.opts.enableTasksQueue&&(this.busy||this.tasksQueueSize(t)>0)&&(n=this.enqueueDequeueTask(t,i)),this.sendToWorker(s.worker,n),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async e=>{this.flushTasksQueueByWorker(e.worker),await this.destroyWorker(e.worker)})))}setupHook(){}beforePromiseResponseHook(e){++this.workerNodes[e].tasksUsage.running}afterPromiseResponseHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(t.runTimeHistory.push(r.runTime??0),t.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const r=e.slice().sort(((e,r)=>e-r)),t=Math.floor(r.length/2);return r.length%2==0?r[t/2]:(r[t-1]+r[t])/2})(t.runTimeHistory)))}chooseWorkerNode(){let e;if(this.type!==k.DYNAMIC||this.full||-1!==this.findFreeWorkerNodeKey())e=this.workerChoiceStrategyContext.execute();else{const r=this.createAndSetupWorker();this.registerWorkerMessageListener(r,(e=>{var t;t=l.HARD,(e.kill===t||null!=e.kill&&0===this.getWorkerTasksUsage(r)?.running)&&(this.flushTasksQueueByWorker(r),this.destroyWorker(r))})),e=this.getWorkerNodeKey(r)}return[e,this.workerNodes[e]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);if(null!=r){null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseResponseHook(r.worker,e),this.promiseResponseMap.delete(e.id);const t=this.getWorkerNodeKey(r.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(t)>0&&this.sendToWorker(r.worker,this.dequeueTask(t))}}}}async internalExecute(e,r,t){return this.beforePromiseResponseHook(e),await new Promise(((e,s)=>{this.promiseResponseMap.set(t.id,{resolve:e,reject:s,worker:r.worker})}))}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(m.busy),this.type===k.DYNAMIC&&this.full&&this.emitter?.emit(m.full))}getWorkerTasksUsage(e){const r=this.getWorkerNodeKey(e);if(-1!==r)return this.workerNodes[r].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new S,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,r,t,s){this.workerNodes[e]={worker:r,tasksUsage:t,tasksQueue:s}}removeWorkerNode(e){const r=this.getWorkerNodeKey(e);this.workerNodes.splice(r,1),this.workerChoiceStrategyContext.remove(r)}enqueueDequeueTask(e,r){return this.enqueueTask(e,r),this.dequeueTask(e)}enqueueTask(e,r){this.workerNodes[e].tasksQueue.push(r)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0){for(const r of this.workerNodes[e].tasksQueue)this.sendToWorker(this.workerNodes[e].worker,r);this.workerNodes[e].tasksQueue=[]}}flushTasksQueueByWorker(e){const r=this.getWorkerNodeKey(e);this.flushTasksQueue(r)}}class I extends R{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){e.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return e.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return e.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class v extends I{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerNodeKey()}}class x extends R{constructor(e,r,t={}){super(e,r,t)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:r,port2:t}=new h;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class C extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerNodeKey()}}const b=6e4,E=l.SOFT;class M extends u{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:E,maxInactiveTime:b}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??b)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){null!=e.data&&null!=e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??E,this.opts.maxInactiveTime=e.maxInactiveTime??b,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??b)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=performance.now(),s=e(r.data),i=performance.now()-t;this.sendToMainWorker({data:s,id:r.id,runTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,r){const t=performance.now();e(r.data).then((e=>{const s=performance.now()-t;return this.sendToMainWorker({data:e,id:r.id,runTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(c)}}class O extends M{constructor(r,t={}){super("worker-cluster-pool:poolifier",e.isPrimary,r,e.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class D extends M{constructor(e,r={}){super("worker-thread-pool:poolifier",i,e,a,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{O as ClusterWorker,v as DynamicClusterPool,C as DynamicThreadPool,I as FixedClusterPool,x as FixedThreadPool,l as KillBehaviors,m as PoolEvents,D as ThreadWorker,p as WorkerChoiceStrategies};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
|
|
2
|
-
import type
|
|
2
|
+
import { type PoolOptions } from './pool';
|
|
3
3
|
import { PoolEmitter } from './pool';
|
|
4
|
-
import type { IPoolInternal
|
|
4
|
+
import type { IPoolInternal } from './pool-internal';
|
|
5
5
|
import { PoolType } from './pool-internal';
|
|
6
|
-
import type {
|
|
6
|
+
import type { IWorker, WorkerNode } from './worker';
|
|
7
7
|
import { type WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
|
|
8
8
|
import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context';
|
|
9
9
|
/**
|
|
@@ -13,21 +13,21 @@ import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choic
|
|
|
13
13
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
14
14
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
15
15
|
*/
|
|
16
|
-
export declare abstract class AbstractPool<Worker extends
|
|
16
|
+
export declare abstract class AbstractPool<Worker extends IWorker, Data = unknown, Response = unknown> implements IPoolInternal<Worker, Data, Response> {
|
|
17
17
|
readonly numberOfWorkers: number;
|
|
18
18
|
readonly filePath: string;
|
|
19
19
|
readonly opts: PoolOptions<Worker>;
|
|
20
20
|
/** @inheritDoc */
|
|
21
|
-
readonly
|
|
21
|
+
readonly workerNodes: Array<WorkerNode<Worker, Data>>;
|
|
22
22
|
/** @inheritDoc */
|
|
23
23
|
readonly emitter?: PoolEmitter;
|
|
24
24
|
/**
|
|
25
|
-
* The
|
|
25
|
+
* The execution response promise map.
|
|
26
26
|
*
|
|
27
27
|
* - `key`: The message id of each submitted task.
|
|
28
|
-
* - `value`: An object that contains the worker, the promise resolve and reject callbacks.
|
|
28
|
+
* - `value`: An object that contains the worker, the execution response promise resolve and reject callbacks.
|
|
29
29
|
*
|
|
30
|
-
* When we receive a message from the worker we get a map entry with the promise resolve/reject bound to the message.
|
|
30
|
+
* When we receive a message from the worker, we get a map entry with the promise resolve/reject bound to the message id.
|
|
31
31
|
*/
|
|
32
32
|
protected promiseResponseMap: Map<string, PromiseResponseWrapper<Worker, Response>>;
|
|
33
33
|
/**
|
|
@@ -47,19 +47,24 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
47
47
|
private checkFilePath;
|
|
48
48
|
private checkNumberOfWorkers;
|
|
49
49
|
private checkPoolOptions;
|
|
50
|
+
private checkValidWorkerChoiceStrategy;
|
|
50
51
|
/** @inheritDoc */
|
|
51
52
|
abstract get type(): PoolType;
|
|
52
53
|
/**
|
|
53
|
-
* Number of tasks
|
|
54
|
+
* Number of tasks running in the pool.
|
|
54
55
|
*/
|
|
55
56
|
private get numberOfRunningTasks();
|
|
56
57
|
/**
|
|
57
|
-
*
|
|
58
|
+
* Number of tasks queued in the pool.
|
|
59
|
+
*/
|
|
60
|
+
private get numberOfQueuedTasks();
|
|
61
|
+
/**
|
|
62
|
+
* Gets the given worker its worker node key.
|
|
58
63
|
*
|
|
59
64
|
* @param worker - The worker.
|
|
60
|
-
* @returns The worker key if the worker is found in the pool, `-1` otherwise.
|
|
65
|
+
* @returns The worker node key if the worker is found in the pool worker nodes, `-1` otherwise.
|
|
61
66
|
*/
|
|
62
|
-
private
|
|
67
|
+
private getWorkerNodeKey;
|
|
63
68
|
/** @inheritDoc */
|
|
64
69
|
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
|
|
65
70
|
/** @inheritDoc */
|
|
@@ -68,20 +73,19 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
68
73
|
abstract get busy(): boolean;
|
|
69
74
|
protected internalBusy(): boolean;
|
|
70
75
|
/** @inheritDoc */
|
|
71
|
-
|
|
76
|
+
findFreeWorkerNodeKey(): number;
|
|
72
77
|
/** @inheritDoc */
|
|
73
78
|
execute(data: Data): Promise<Response>;
|
|
74
79
|
/** @inheritDoc */
|
|
75
80
|
destroy(): Promise<void>;
|
|
76
81
|
/**
|
|
77
|
-
* Shutdowns given worker
|
|
82
|
+
* Shutdowns the given worker.
|
|
78
83
|
*
|
|
79
|
-
* @param worker - A worker within `
|
|
84
|
+
* @param worker - A worker within `workerNodes`.
|
|
80
85
|
*/
|
|
81
86
|
protected abstract destroyWorker(worker: Worker): void | Promise<void>;
|
|
82
87
|
/**
|
|
83
|
-
* Setup hook
|
|
84
|
-
* to run code before workers are created in the abstract constructor.
|
|
88
|
+
* Setup hook to run code before worker node are created in the abstract constructor.
|
|
85
89
|
* Can be overridden
|
|
86
90
|
*
|
|
87
91
|
* @virtual
|
|
@@ -95,9 +99,9 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
95
99
|
* Hook executed before the worker task promise resolution.
|
|
96
100
|
* Can be overridden.
|
|
97
101
|
*
|
|
98
|
-
* @param
|
|
102
|
+
* @param workerNodeKey - The worker node key.
|
|
99
103
|
*/
|
|
100
|
-
protected beforePromiseResponseHook(
|
|
104
|
+
protected beforePromiseResponseHook(workerNodeKey: number): void;
|
|
101
105
|
/**
|
|
102
106
|
* Hook executed after the worker task promise resolution.
|
|
103
107
|
* Can be overridden.
|
|
@@ -107,13 +111,13 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
107
111
|
*/
|
|
108
112
|
protected afterPromiseResponseHook(worker: Worker, message: MessageValue<Response>): void;
|
|
109
113
|
/**
|
|
110
|
-
* Chooses a worker for the next task.
|
|
114
|
+
* Chooses a worker node for the next task.
|
|
111
115
|
*
|
|
112
116
|
* The default uses a round robin algorithm to distribute the load.
|
|
113
117
|
*
|
|
114
|
-
* @returns [worker key, worker].
|
|
118
|
+
* @returns [worker node key, worker node].
|
|
115
119
|
*/
|
|
116
|
-
protected
|
|
120
|
+
protected chooseWorkerNode(): [number, WorkerNode<Worker, Data>];
|
|
117
121
|
/**
|
|
118
122
|
* Sends a message to the given worker.
|
|
119
123
|
*
|
|
@@ -122,7 +126,7 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
122
126
|
*/
|
|
123
127
|
protected abstract sendToWorker(worker: Worker, message: MessageValue<Data>): void;
|
|
124
128
|
/**
|
|
125
|
-
* Registers a listener callback on
|
|
129
|
+
* Registers a listener callback on the given worker.
|
|
126
130
|
*
|
|
127
131
|
* @param worker - The worker which should register a listener.
|
|
128
132
|
* @param listener - The message listener callback.
|
|
@@ -133,55 +137,60 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
133
137
|
*/
|
|
134
138
|
protected abstract createWorker(): Worker;
|
|
135
139
|
/**
|
|
136
|
-
* Function that can be hooked up when a worker has been newly created and moved to the
|
|
140
|
+
* Function that can be hooked up when a worker has been newly created and moved to the pool worker nodes.
|
|
137
141
|
*
|
|
138
142
|
* Can be used to update the `maxListeners` or binding the `main-worker`\<-\>`worker` connection if not bind by default.
|
|
139
143
|
*
|
|
140
144
|
* @param worker - The newly created worker.
|
|
141
|
-
* @virtual
|
|
142
145
|
*/
|
|
143
146
|
protected abstract afterWorkerSetup(worker: Worker): void;
|
|
144
147
|
/**
|
|
145
|
-
* Creates a new worker
|
|
148
|
+
* Creates a new worker and sets it up completely in the pool worker nodes.
|
|
146
149
|
*
|
|
147
150
|
* @returns New, completely set up worker.
|
|
148
151
|
*/
|
|
149
152
|
protected createAndSetupWorker(): Worker;
|
|
150
153
|
/**
|
|
151
|
-
* This function is the listener registered for each worker.
|
|
154
|
+
* This function is the listener registered for each worker message.
|
|
152
155
|
*
|
|
153
156
|
* @returns The listener function to execute when a message is received from a worker.
|
|
154
157
|
*/
|
|
155
158
|
protected workerListener(): (message: MessageValue<Response>) => void;
|
|
156
159
|
private internalExecute;
|
|
157
|
-
private
|
|
158
|
-
private checkAndEmitFull;
|
|
160
|
+
private checkAndEmitEvents;
|
|
159
161
|
/**
|
|
160
|
-
* Gets the given worker tasks usage in the pool.
|
|
162
|
+
* Gets the given worker its tasks usage in the pool.
|
|
161
163
|
*
|
|
162
164
|
* @param worker - The worker.
|
|
163
165
|
* @returns The worker tasks usage.
|
|
164
166
|
*/
|
|
165
167
|
private getWorkerTasksUsage;
|
|
166
168
|
/**
|
|
167
|
-
* Pushes the given worker in the pool.
|
|
169
|
+
* Pushes the given worker in the pool worker nodes.
|
|
168
170
|
*
|
|
169
171
|
* @param worker - The worker.
|
|
170
|
-
* @
|
|
172
|
+
* @returns The worker nodes length.
|
|
171
173
|
*/
|
|
172
|
-
private
|
|
174
|
+
private pushWorkerNode;
|
|
173
175
|
/**
|
|
174
|
-
* Sets the given worker in the pool.
|
|
176
|
+
* Sets the given worker in the pool worker nodes.
|
|
175
177
|
*
|
|
176
|
-
* @param
|
|
178
|
+
* @param workerNodeKey - The worker node key.
|
|
177
179
|
* @param worker - The worker.
|
|
178
180
|
* @param tasksUsage - The worker tasks usage.
|
|
181
|
+
* @param tasksQueue - The worker task queue.
|
|
179
182
|
*/
|
|
180
|
-
private
|
|
183
|
+
private setWorkerNode;
|
|
181
184
|
/**
|
|
182
|
-
* Removes the given worker from the pool.
|
|
185
|
+
* Removes the given worker from the pool worker nodes.
|
|
183
186
|
*
|
|
184
|
-
* @param worker - The worker
|
|
187
|
+
* @param worker - The worker.
|
|
185
188
|
*/
|
|
186
|
-
|
|
189
|
+
private removeWorkerNode;
|
|
190
|
+
private enqueueDequeueTask;
|
|
191
|
+
private enqueueTask;
|
|
192
|
+
private dequeueTask;
|
|
193
|
+
private tasksQueueSize;
|
|
194
|
+
private flushTasksQueue;
|
|
195
|
+
private flushTasksQueueByWorker;
|
|
187
196
|
}
|
|
@@ -13,7 +13,7 @@ import { FixedClusterPool } from './fixed';
|
|
|
13
13
|
* @since 2.0.0
|
|
14
14
|
*/
|
|
15
15
|
export declare class DynamicClusterPool<Data = unknown, Response = unknown> extends FixedClusterPool<Data, Response> {
|
|
16
|
-
|
|
16
|
+
readonly max: number;
|
|
17
17
|
/**
|
|
18
18
|
* Constructs a new poolifier dynamic cluster pool.
|
|
19
19
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IPool } from './pool';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IWorker, WorkerNode } from './worker';
|
|
3
3
|
/**
|
|
4
4
|
* Internal pool types.
|
|
5
5
|
*
|
|
@@ -9,25 +9,6 @@ export declare enum PoolType {
|
|
|
9
9
|
FIXED = "fixed",
|
|
10
10
|
DYNAMIC = "dynamic"
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Internal tasks usage statistics.
|
|
14
|
-
*/
|
|
15
|
-
export interface TasksUsage {
|
|
16
|
-
run: number;
|
|
17
|
-
running: number;
|
|
18
|
-
runTime: number;
|
|
19
|
-
avgRunTime: number;
|
|
20
|
-
error: number;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Internal worker type.
|
|
24
|
-
*
|
|
25
|
-
* @typeParam Worker - Type of worker type items which manages this pool.
|
|
26
|
-
*/
|
|
27
|
-
export interface WorkerType<Worker extends IPoolWorker> {
|
|
28
|
-
worker: Worker;
|
|
29
|
-
tasksUsage: TasksUsage;
|
|
30
|
-
}
|
|
31
12
|
/**
|
|
32
13
|
* Internal contract definition for a poolifier pool.
|
|
33
14
|
*
|
|
@@ -35,11 +16,11 @@ export interface WorkerType<Worker extends IPoolWorker> {
|
|
|
35
16
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
36
17
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
37
18
|
*/
|
|
38
|
-
export interface IPoolInternal<Worker extends
|
|
19
|
+
export interface IPoolInternal<Worker extends IWorker, Data = unknown, Response = unknown> extends IPool<Data, Response> {
|
|
39
20
|
/**
|
|
40
|
-
* Pool worker
|
|
21
|
+
* Pool worker nodes.
|
|
41
22
|
*/
|
|
42
|
-
readonly
|
|
23
|
+
readonly workerNodes: Array<WorkerNode<Worker, Data>>;
|
|
43
24
|
/**
|
|
44
25
|
* Pool type.
|
|
45
26
|
*
|
|
@@ -59,13 +40,13 @@ export interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Respo
|
|
|
59
40
|
*/
|
|
60
41
|
readonly busy: boolean;
|
|
61
42
|
/**
|
|
62
|
-
* Finds a free worker key based on the number of tasks the worker has applied.
|
|
43
|
+
* Finds a free worker node key based on the number of tasks the worker has applied.
|
|
63
44
|
*
|
|
64
|
-
* If a worker is found with `0` running tasks, it is detected as free and its key is returned.
|
|
45
|
+
* If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
|
|
65
46
|
*
|
|
66
|
-
* If no free worker is found, `
|
|
47
|
+
* If no free worker is found, `-1` is returned.
|
|
67
48
|
*
|
|
68
|
-
* @returns A worker key if there is one, `-1` otherwise.
|
|
49
|
+
* @returns A worker node key if there is one, `-1` otherwise.
|
|
69
50
|
*/
|
|
70
|
-
|
|
51
|
+
findFreeWorkerNodeKey: () => number;
|
|
71
52
|
}
|
package/lib/pools/pool.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import EventEmitter from 'node:events';
|
|
3
|
-
import type { ErrorHandler, ExitHandler, MessageHandler, OnlineHandler } from './
|
|
4
|
-
import type { WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
|
|
3
|
+
import type { ErrorHandler, ExitHandler, MessageHandler, OnlineHandler } from './worker';
|
|
4
|
+
import type { WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types';
|
|
5
5
|
/**
|
|
6
6
|
* Pool events emitter.
|
|
7
7
|
*/
|
|
8
8
|
export declare class PoolEmitter extends EventEmitter {
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Enumeration of pool events.
|
|
12
|
+
*/
|
|
13
|
+
export declare const PoolEvents: Readonly<{
|
|
14
|
+
readonly full: "full";
|
|
15
|
+
readonly busy: "busy";
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Pool event.
|
|
19
|
+
*/
|
|
20
|
+
export type PoolEvent = keyof typeof PoolEvents;
|
|
10
21
|
/**
|
|
11
22
|
* Options for a poolifier pool.
|
|
12
23
|
*/
|
|
@@ -31,12 +42,23 @@ export interface PoolOptions<Worker> {
|
|
|
31
42
|
* The worker choice strategy to use in this pool.
|
|
32
43
|
*/
|
|
33
44
|
workerChoiceStrategy?: WorkerChoiceStrategy;
|
|
45
|
+
/**
|
|
46
|
+
* The worker choice strategy options.
|
|
47
|
+
*/
|
|
48
|
+
workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions;
|
|
34
49
|
/**
|
|
35
50
|
* Pool events emission.
|
|
36
51
|
*
|
|
37
52
|
* @defaultValue true
|
|
38
53
|
*/
|
|
39
54
|
enableEvents?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Pool worker tasks queue.
|
|
57
|
+
*
|
|
58
|
+
* @experimental
|
|
59
|
+
* @defaultValue false
|
|
60
|
+
*/
|
|
61
|
+
enableTasksQueue?: boolean;
|
|
40
62
|
}
|
|
41
63
|
/**
|
|
42
64
|
* Contract definition for a poolifier pool.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IPoolInternal } from '../pool-internal';
|
|
2
|
-
import type {
|
|
3
|
-
import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
|
|
2
|
+
import type { IWorker } from '../worker';
|
|
3
|
+
import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
5
5
|
* Worker choice strategy abstract base class.
|
|
6
6
|
*
|
|
@@ -8,22 +8,25 @@ import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-stra
|
|
|
8
8
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
9
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
|
-
export declare abstract class AbstractWorkerChoiceStrategy<Worker extends
|
|
12
|
-
readonly pool: IPoolInternal<Worker, Data, Response>;
|
|
11
|
+
export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> implements IWorkerChoiceStrategy {
|
|
12
|
+
protected readonly pool: IPoolInternal<Worker, Data, Response>;
|
|
13
|
+
protected readonly opts: WorkerChoiceStrategyOptions;
|
|
13
14
|
/** @inheritDoc */
|
|
14
|
-
readonly isDynamicPool: boolean;
|
|
15
|
+
protected readonly isDynamicPool: boolean;
|
|
15
16
|
/** @inheritDoc */
|
|
16
|
-
requiredStatistics: RequiredStatistics;
|
|
17
|
+
readonly requiredStatistics: RequiredStatistics;
|
|
17
18
|
/**
|
|
18
19
|
* Constructs a worker choice strategy bound to the pool.
|
|
19
20
|
*
|
|
20
21
|
* @param pool - The pool instance.
|
|
22
|
+
* @param opts - The worker choice strategy options.
|
|
21
23
|
*/
|
|
22
|
-
constructor(pool: IPoolInternal<Worker, Data, Response
|
|
24
|
+
constructor(pool: IPoolInternal<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
25
|
+
private checkOptions;
|
|
23
26
|
/** @inheritDoc */
|
|
24
27
|
abstract reset(): boolean;
|
|
25
28
|
/** @inheritDoc */
|
|
26
29
|
abstract choose(): number;
|
|
27
30
|
/** @inheritDoc */
|
|
28
|
-
abstract remove(
|
|
31
|
+
abstract remove(workerNodeKey: number): boolean;
|
|
29
32
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IWorker } from '../worker';
|
|
2
2
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
3
|
import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
@@ -9,11 +9,11 @@ import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-stra
|
|
|
9
9
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
10
10
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
11
11
|
*/
|
|
12
|
-
export declare class FairShareWorkerChoiceStrategy<Worker extends
|
|
12
|
+
export declare class FairShareWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
13
|
/** @inheritDoc */
|
|
14
14
|
readonly requiredStatistics: RequiredStatistics;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Worker last virtual task execution timestamp.
|
|
17
17
|
*/
|
|
18
18
|
private readonly workerLastVirtualTaskTimestamp;
|
|
19
19
|
/** @inheritDoc */
|
|
@@ -21,11 +21,11 @@ export declare class FairShareWorkerChoiceStrategy<Worker extends IPoolWorker, D
|
|
|
21
21
|
/** @inheritDoc */
|
|
22
22
|
choose(): number;
|
|
23
23
|
/** @inheritDoc */
|
|
24
|
-
remove(
|
|
24
|
+
remove(workerNodeKey: number): boolean;
|
|
25
25
|
/**
|
|
26
26
|
* Computes worker last virtual task timestamp.
|
|
27
27
|
*
|
|
28
|
-
* @param
|
|
28
|
+
* @param workerNodeKey - The worker node key.
|
|
29
29
|
*/
|
|
30
30
|
private computeWorkerLastVirtualTaskTimestamp;
|
|
31
31
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IWorker } from '../worker';
|
|
2
2
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
3
|
import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
@@ -8,7 +8,7 @@ import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-stra
|
|
|
8
8
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
9
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
|
-
export declare class LessBusyWorkerChoiceStrategy<Worker extends
|
|
11
|
+
export declare class LessBusyWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
12
|
/** @inheritDoc */
|
|
13
13
|
readonly requiredStatistics: RequiredStatistics;
|
|
14
14
|
/** @inheritDoc */
|
|
@@ -16,5 +16,5 @@ export declare class LessBusyWorkerChoiceStrategy<Worker extends IPoolWorker, Da
|
|
|
16
16
|
/** @inheritDoc */
|
|
17
17
|
choose(): number;
|
|
18
18
|
/** @inheritDoc */
|
|
19
|
-
remove(
|
|
19
|
+
remove(workerNodeKey: number): boolean;
|
|
20
20
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IWorker } from '../worker';
|
|
2
2
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
3
|
import type { IWorkerChoiceStrategy } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
@@ -8,11 +8,11 @@ import type { IWorkerChoiceStrategy } from './selection-strategies-types';
|
|
|
8
8
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
9
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
|
-
export declare class LessUsedWorkerChoiceStrategy<Worker extends
|
|
11
|
+
export declare class LessUsedWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
12
|
/** @inheritDoc */
|
|
13
13
|
reset(): boolean;
|
|
14
14
|
/** @inheritDoc */
|
|
15
15
|
choose(): number;
|
|
16
16
|
/** @inheritDoc */
|
|
17
|
-
remove(
|
|
17
|
+
remove(workerNodeKey: number): boolean;
|
|
18
18
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IWorker } from '../worker';
|
|
2
2
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
3
|
import type { IWorkerChoiceStrategy } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
@@ -8,15 +8,15 @@ import type { IWorkerChoiceStrategy } from './selection-strategies-types';
|
|
|
8
8
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
9
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
|
-
export declare class RoundRobinWorkerChoiceStrategy<Worker extends
|
|
11
|
+
export declare class RoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
12
|
/**
|
|
13
|
-
* Id of the next worker.
|
|
13
|
+
* Id of the next worker node.
|
|
14
14
|
*/
|
|
15
|
-
private
|
|
15
|
+
private nextWorkerNodeId;
|
|
16
16
|
/** @inheritDoc */
|
|
17
17
|
reset(): boolean;
|
|
18
18
|
/** @inheritDoc */
|
|
19
19
|
choose(): number;
|
|
20
20
|
/** @inheritDoc */
|
|
21
|
-
remove(
|
|
21
|
+
remove(workerNodeKey: number): boolean;
|
|
22
22
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { IPoolInternal } from '../pool-internal';
|
|
2
|
-
import type { IPoolWorker } from '../pool-worker';
|
|
3
1
|
/**
|
|
4
2
|
* Enumeration of worker choice strategies.
|
|
5
3
|
*/
|
|
@@ -29,27 +27,29 @@ export declare const WorkerChoiceStrategies: Readonly<{
|
|
|
29
27
|
* Worker choice strategy.
|
|
30
28
|
*/
|
|
31
29
|
export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies;
|
|
30
|
+
/**
|
|
31
|
+
* Worker choice strategy options.
|
|
32
|
+
*/
|
|
33
|
+
export interface WorkerChoiceStrategyOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Use tasks median run time instead of average run time.
|
|
36
|
+
*/
|
|
37
|
+
medRunTime?: boolean;
|
|
38
|
+
}
|
|
32
39
|
/**
|
|
33
40
|
* Pool worker tasks usage statistics requirements.
|
|
34
41
|
*/
|
|
35
42
|
export interface RequiredStatistics {
|
|
36
43
|
runTime: boolean;
|
|
37
44
|
avgRunTime: boolean;
|
|
45
|
+
medRunTime: boolean;
|
|
38
46
|
}
|
|
39
47
|
/**
|
|
40
48
|
* Worker choice strategy interface.
|
|
41
49
|
*/
|
|
42
|
-
export interface IWorkerChoiceStrategy
|
|
43
|
-
/**
|
|
44
|
-
* The pool instance.
|
|
45
|
-
*/
|
|
46
|
-
readonly pool: IPoolInternal<Worker, Data, Response>;
|
|
47
|
-
/**
|
|
48
|
-
* Is the pool bound to the strategy dynamic?.
|
|
49
|
-
*/
|
|
50
|
-
readonly isDynamicPool: boolean;
|
|
50
|
+
export interface IWorkerChoiceStrategy {
|
|
51
51
|
/**
|
|
52
|
-
* Required
|
|
52
|
+
* Required tasks usage statistics.
|
|
53
53
|
*/
|
|
54
54
|
readonly requiredStatistics: RequiredStatistics;
|
|
55
55
|
/**
|
|
@@ -57,13 +57,13 @@ export interface IWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknow
|
|
|
57
57
|
*/
|
|
58
58
|
reset: () => boolean;
|
|
59
59
|
/**
|
|
60
|
-
* Chooses a worker in the pool and returns its key.
|
|
60
|
+
* Chooses a worker node in the pool and returns its key.
|
|
61
61
|
*/
|
|
62
62
|
choose: () => number;
|
|
63
63
|
/**
|
|
64
|
-
* Removes a worker
|
|
64
|
+
* Removes a worker node key from strategy internals.
|
|
65
65
|
*
|
|
66
|
-
* @param
|
|
66
|
+
* @param workerNodeKey - The worker node key.
|
|
67
67
|
*/
|
|
68
|
-
remove: (
|
|
68
|
+
remove: (workerNodeKey: number) => boolean;
|
|
69
69
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IPoolInternal } from '../pool-internal';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IWorker } from '../worker';
|
|
3
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
4
|
-
import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
|
|
4
|
+
import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
5
5
|
/**
|
|
6
6
|
* Selects the next worker with a weighted round robin scheduling algorithm.
|
|
7
7
|
* Loosely modeled after the weighted round robin queueing algorithm: https://en.wikipedia.org/wiki/Weighted_round_robin.
|
|
@@ -10,33 +10,34 @@ import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-stra
|
|
|
10
10
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
11
11
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
12
12
|
*/
|
|
13
|
-
export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends
|
|
13
|
+
export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
14
14
|
/** @inheritDoc */
|
|
15
15
|
readonly requiredStatistics: RequiredStatistics;
|
|
16
16
|
/**
|
|
17
|
-
* Worker id where the current task will be submitted.
|
|
17
|
+
* Worker node id where the current task will be submitted.
|
|
18
18
|
*/
|
|
19
|
-
private
|
|
19
|
+
private currentWorkerNodeId;
|
|
20
20
|
/**
|
|
21
21
|
* Default worker weight.
|
|
22
22
|
*/
|
|
23
23
|
private readonly defaultWorkerWeight;
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Workers' virtual task runtime.
|
|
26
26
|
*/
|
|
27
27
|
private readonly workersTaskRunTime;
|
|
28
28
|
/**
|
|
29
29
|
* Constructs a worker choice strategy that selects with a weighted round robin scheduling algorithm.
|
|
30
30
|
*
|
|
31
31
|
* @param pool - The pool instance.
|
|
32
|
+
* @param opts - The worker choice strategy options.
|
|
32
33
|
*/
|
|
33
|
-
constructor(pool: IPoolInternal<Worker, Data, Response
|
|
34
|
+
constructor(pool: IPoolInternal<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
34
35
|
/** @inheritDoc */
|
|
35
36
|
reset(): boolean;
|
|
36
37
|
/** @inheritDoc */
|
|
37
38
|
choose(): number;
|
|
38
39
|
/** @inheritDoc */
|
|
39
|
-
remove(
|
|
40
|
+
remove(workerNodeKey: number): boolean;
|
|
40
41
|
private initWorkersTaskRunTime;
|
|
41
42
|
private initWorkerTaskRunTime;
|
|
42
43
|
private setWorkerTaskRunTime;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IPoolInternal } from '../pool-internal';
|
|
2
|
-
import type {
|
|
3
|
-
import type { RequiredStatistics, WorkerChoiceStrategy } from './selection-strategies-types';
|
|
2
|
+
import type { IWorker } from '../worker';
|
|
3
|
+
import type { RequiredStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
5
5
|
* The worker choice strategy context.
|
|
6
6
|
*
|
|
@@ -8,18 +8,17 @@ import type { RequiredStatistics, WorkerChoiceStrategy } from './selection-strat
|
|
|
8
8
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
9
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
|
-
export declare class WorkerChoiceStrategyContext<Worker extends
|
|
12
|
-
private readonly createWorkerCallback;
|
|
11
|
+
export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data = unknown, Response = unknown> {
|
|
13
12
|
private workerChoiceStrategyType;
|
|
14
13
|
private readonly workerChoiceStrategies;
|
|
15
14
|
/**
|
|
16
15
|
* Worker choice strategy context constructor.
|
|
17
16
|
*
|
|
18
17
|
* @param pool - The pool instance.
|
|
19
|
-
* @param
|
|
20
|
-
* @param
|
|
18
|
+
* @param workerChoiceStrategyType - The worker choice strategy.
|
|
19
|
+
* @param opts - The worker choice strategy options.
|
|
21
20
|
*/
|
|
22
|
-
constructor(pool: IPoolInternal<Worker, Data, Response>,
|
|
21
|
+
constructor(pool: IPoolInternal<Worker, Data, Response>, workerChoiceStrategyType?: WorkerChoiceStrategy, opts?: WorkerChoiceStrategyOptions);
|
|
23
22
|
/**
|
|
24
23
|
* Gets the worker choice strategy in the context required statistics.
|
|
25
24
|
*
|
|
@@ -35,14 +34,14 @@ export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Dat
|
|
|
35
34
|
/**
|
|
36
35
|
* Executes the worker choice strategy algorithm in the context.
|
|
37
36
|
*
|
|
38
|
-
* @returns The key of the
|
|
37
|
+
* @returns The key of the worker node.
|
|
39
38
|
*/
|
|
40
39
|
execute(): number;
|
|
41
40
|
/**
|
|
42
|
-
* Removes a worker from the worker choice strategy in the context.
|
|
41
|
+
* Removes a worker node key from the worker choice strategy in the context.
|
|
43
42
|
*
|
|
44
|
-
* @param
|
|
43
|
+
* @param workerNodeKey - The key of the worker node.
|
|
45
44
|
* @returns `true` if the removal is successful, `false` otherwise.
|
|
46
45
|
*/
|
|
47
|
-
remove(
|
|
46
|
+
remove(workerNodeKey: number): boolean;
|
|
48
47
|
}
|
|
@@ -14,7 +14,7 @@ import { FixedThreadPool } from './fixed';
|
|
|
14
14
|
* @since 0.0.1
|
|
15
15
|
*/
|
|
16
16
|
export declare class DynamicThreadPool<Data = unknown, Response = unknown> extends FixedThreadPool<Data, Response> {
|
|
17
|
-
|
|
17
|
+
readonly max: number;
|
|
18
18
|
/**
|
|
19
19
|
* Constructs a new poolifier dynamic thread pool.
|
|
20
20
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CircularArray } from '../circular-array';
|
|
1
2
|
/**
|
|
2
3
|
* Callback invoked if the worker has received a message.
|
|
3
4
|
*/
|
|
@@ -15,9 +16,28 @@ export type OnlineHandler<Worker> = (this: Worker) => void;
|
|
|
15
16
|
*/
|
|
16
17
|
export type ExitHandler<Worker> = (this: Worker, code: number) => void;
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* Worker task interface.
|
|
19
20
|
*/
|
|
20
|
-
export interface
|
|
21
|
+
export interface Task<Data = unknown> {
|
|
22
|
+
data: Data;
|
|
23
|
+
id: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Worker tasks usage statistics.
|
|
27
|
+
*/
|
|
28
|
+
export interface TasksUsage {
|
|
29
|
+
run: number;
|
|
30
|
+
running: number;
|
|
31
|
+
runTime: number;
|
|
32
|
+
runTimeHistory: CircularArray<number>;
|
|
33
|
+
avgRunTime: number;
|
|
34
|
+
medRunTime: number;
|
|
35
|
+
error: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Worker interface.
|
|
39
|
+
*/
|
|
40
|
+
export interface IWorker {
|
|
21
41
|
/**
|
|
22
42
|
* Register an event listener.
|
|
23
43
|
*
|
|
@@ -33,3 +53,11 @@ export interface IPoolWorker {
|
|
|
33
53
|
*/
|
|
34
54
|
once: (event: 'exit', handler: ExitHandler<this>) => void;
|
|
35
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Worker node interface.
|
|
58
|
+
*/
|
|
59
|
+
export interface WorkerNode<Worker extends IWorker, Data = unknown> {
|
|
60
|
+
worker: Worker;
|
|
61
|
+
tasksUsage: TasksUsage;
|
|
62
|
+
tasksQueue: Array<Task<Data>>;
|
|
63
|
+
}
|
package/lib/utility-types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { Worker as ClusterWorker } from 'node:cluster';
|
|
4
4
|
import type { MessagePort } from 'node:worker_threads';
|
|
5
5
|
import type { KillBehavior } from './worker/worker-options';
|
|
6
|
-
import type {
|
|
6
|
+
import type { IWorker } from './pools/worker';
|
|
7
7
|
/**
|
|
8
8
|
* Make all properties in T non-readonly.
|
|
9
9
|
*/
|
|
@@ -31,9 +31,9 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
|
|
|
31
31
|
*/
|
|
32
32
|
readonly error?: string;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Runtime.
|
|
35
35
|
*/
|
|
36
|
-
readonly
|
|
36
|
+
readonly runTime?: number;
|
|
37
37
|
/**
|
|
38
38
|
* Reference to main worker.
|
|
39
39
|
*
|
|
@@ -47,7 +47,7 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
|
|
|
47
47
|
* @typeParam Worker - Type of worker.
|
|
48
48
|
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
49
49
|
*/
|
|
50
|
-
export interface PromiseResponseWrapper<Worker extends
|
|
50
|
+
export interface PromiseResponseWrapper<Worker extends IWorker, Response = unknown> {
|
|
51
51
|
/**
|
|
52
52
|
* Resolve callback to fulfill the promise.
|
|
53
53
|
*/
|
|
@@ -57,7 +57,7 @@ export interface PromiseResponseWrapper<Worker extends IPoolWorker, Response = u
|
|
|
57
57
|
*/
|
|
58
58
|
readonly reject: (reason?: string) => void;
|
|
59
59
|
/**
|
|
60
|
-
* The worker handling the
|
|
60
|
+
* The worker handling the execution.
|
|
61
61
|
*/
|
|
62
62
|
readonly worker: Worker;
|
|
63
63
|
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -2,3 +2,10 @@
|
|
|
2
2
|
* An intentional empty function.
|
|
3
3
|
*/
|
|
4
4
|
export declare const EMPTY_FUNCTION: () => void;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the median of the given data set.
|
|
7
|
+
*
|
|
8
|
+
* @param dataSet - Data set.
|
|
9
|
+
* @returns The median of the given data set.
|
|
10
|
+
*/
|
|
11
|
+
export declare const median: (dataSet: number[]) => number;
|
|
@@ -23,7 +23,7 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
23
23
|
*/
|
|
24
24
|
protected lastTaskTimestamp: number;
|
|
25
25
|
/**
|
|
26
|
-
* Handler
|
|
26
|
+
* Handler id of the `aliveInterval` worker alive check.
|
|
27
27
|
*/
|
|
28
28
|
protected readonly aliveInterval?: NodeJS.Timeout;
|
|
29
29
|
/**
|
|
@@ -36,7 +36,13 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
36
36
|
* @param opts - Options for the worker.
|
|
37
37
|
*/
|
|
38
38
|
constructor(type: string, isMain: boolean, fn: (data: Data) => Response, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Worker message listener.
|
|
41
|
+
*
|
|
42
|
+
* @param message - Message received.
|
|
43
|
+
* @param fn - Function processed by the worker when the pool's `execution` function is invoked.
|
|
44
|
+
*/
|
|
45
|
+
protected messageListener(message: MessageValue<Data, MainWorker>, fn: (data: Data) => Response): void;
|
|
40
46
|
private checkWorkerOptions;
|
|
41
47
|
/**
|
|
42
48
|
* Checks if the `fn` parameter is passed to the constructor.
|
|
@@ -71,14 +77,14 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
71
77
|
* Runs the given function synchronously.
|
|
72
78
|
*
|
|
73
79
|
* @param fn - Function that will be executed.
|
|
74
|
-
* @param
|
|
80
|
+
* @param message - Input data for the given function.
|
|
75
81
|
*/
|
|
76
|
-
protected run(fn: (data?: Data) => Response,
|
|
82
|
+
protected run(fn: (data?: Data) => Response, message: MessageValue<Data>): void;
|
|
77
83
|
/**
|
|
78
84
|
* Runs the given function asynchronously.
|
|
79
85
|
*
|
|
80
86
|
* @param fn - Function that will be executed.
|
|
81
|
-
* @param
|
|
87
|
+
* @param message - Input data for the given function.
|
|
82
88
|
*/
|
|
83
|
-
protected runAsync(fn: (data?: Data) => Promise<Response>,
|
|
89
|
+
protected runAsync(fn: (data?: Data) => Promise<Response>, message: MessageValue<Data>): void;
|
|
84
90
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poolifier",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
4
|
"description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -84,20 +84,20 @@
|
|
|
84
84
|
"@typescript-eslint/parser": "^5.57.1",
|
|
85
85
|
"benny": "^3.7.1",
|
|
86
86
|
"c8": "^7.13.0",
|
|
87
|
-
"eslint": "^8.
|
|
87
|
+
"eslint": "^8.38.0",
|
|
88
88
|
"eslint-config-standard": "^17.0.0",
|
|
89
89
|
"eslint-config-standard-with-typescript": "^34.0.1",
|
|
90
90
|
"eslint-define-config": "^1.17.0",
|
|
91
91
|
"eslint-import-resolver-typescript": "^3.5.5",
|
|
92
92
|
"eslint-plugin-import": "^2.27.5",
|
|
93
|
-
"eslint-plugin-jsdoc": "^40.1.
|
|
93
|
+
"eslint-plugin-jsdoc": "^40.1.2",
|
|
94
94
|
"eslint-plugin-n": "^15.7.0",
|
|
95
95
|
"eslint-plugin-promise": "^6.1.1",
|
|
96
96
|
"eslint-plugin-spellcheck": "^0.0.20",
|
|
97
97
|
"eslint-plugin-tsdoc": "^0.2.17",
|
|
98
98
|
"expect": "^29.5.0",
|
|
99
99
|
"husky": "^8.0.3",
|
|
100
|
-
"lint-staged": "^13.2.
|
|
100
|
+
"lint-staged": "^13.2.1",
|
|
101
101
|
"microtime": "^3.1.1",
|
|
102
102
|
"mocha": "^10.2.0",
|
|
103
103
|
"mochawesome": "^7.1.3",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"source-map-support": "^0.5.21",
|
|
113
113
|
"ts-standard": "^12.0.2",
|
|
114
114
|
"typedoc": "^0.23.28",
|
|
115
|
-
"typescript": "^5.0.
|
|
115
|
+
"typescript": "^5.0.4"
|
|
116
116
|
},
|
|
117
117
|
"scripts": {
|
|
118
118
|
"preinstall": "npx only-allow pnpm",
|