poolifier 2.4.2 → 2.4.4
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 +4 -4
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/pools/abstract-pool.d.ts +17 -12
- package/lib/pools/cluster/dynamic.d.ts +4 -4
- package/lib/pools/cluster/fixed.d.ts +10 -10
- package/lib/pools/pool-internal.d.ts +3 -1
- package/lib/pools/pool.d.ts +11 -0
- package/lib/pools/selection-strategies/abstract-worker-choice-strategy.d.ts +8 -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 +5 -5
- package/lib/pools/selection-strategies/less-used-worker-choice-strategy.d.ts +4 -4
- package/lib/pools/selection-strategies/round-robin-worker-choice-strategy.d.ts +4 -4
- package/lib/pools/selection-strategies/selection-strategies-types.d.ts +1 -11
- package/lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.d.ts +5 -5
- package/lib/pools/selection-strategies/worker-choice-strategy-context.d.ts +1 -2
- package/lib/pools/thread/dynamic.d.ts +4 -4
- package/lib/pools/thread/fixed.d.ts +9 -9
- package/lib/utility-types.d.ts +2 -2
- package/lib/worker/abstract-worker.d.ts +13 -10
- package/lib/worker/cluster-worker.d.ts +2 -2
- package/lib/worker/thread-worker.d.ts +1 -1
- package/package.json +3 -3
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
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
|
4
|
+
export { PoolEvents } from './pools/pool';
|
|
5
|
+
export type { IPool, PoolEmitter, PoolOptions, PoolEvent } from './pools/pool';
|
|
5
6
|
export type { ErrorHandler, ExitHandler, MessageHandler, OnlineHandler } from './pools/pool-worker';
|
|
6
7
|
export { WorkerChoiceStrategies } from './pools/selection-strategies/selection-strategies-types';
|
|
7
8
|
export type { WorkerChoiceStrategy } from './pools/selection-strategies/selection-strategies-types';
|
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(void 0!==e.id){const r=this.promiseResponseMap.get(e.id);void 0!==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;lastTaskTimestamp;aliveInterval;opts;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){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==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 k extends s{}const u=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;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1};constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose.bind(this)}}class p extends l{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 m extends l{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 d extends l{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 g extends l{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 l{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 T{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=c.ROUND_ROBIN){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[c.ROUND_ROBIN,new g(e)],[c.LESS_USED,new d(e)],[c.LESS_BUSY,new m(e)],[c.FAIR_SHARE,new p(e)],[c.WEIGHTED_ROUND_ROBIN,new w(e)]])}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{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 T(this,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){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??c.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.enableEvents=e.enableEvents??!0}checkValidWorkerChoiceStrategy(e){if(!Object.values(c).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.promiseResponseMap.size}getWorkerKey(e){return this.workers.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(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.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run))}chooseWorker(){let r;if(this.type!==e.DYNAMIC||this.full||-1!==this.findFreeWorkerKey())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.destroyWorker(e)})),r=this.getWorkerKey(e)}return[r,this.workers[r].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(u.busy)}checkAndEmitFull(){this.type===e.DYNAMIC&&!0===this.opts.enableEvents&&this.full&&this.emitter?.emit(u.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 f extends W{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 y extends W{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 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=Date.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(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??S)&&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,runTime: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,runTime: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 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 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.DynamicThreadPool=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.workers.length===this.max}get busy(){return this.full&&-1===this.findFreeWorkerKey()}},exports.FixedClusterPool=f,exports.FixedThreadPool=y,exports.KillBehaviors=h,exports.PoolEvents=u,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 d{pool;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1};constructor(e){this.pool=e,this.isDynamicPool=this.pool.type===u.DYNAMIC,this.choose.bind(this)}}class w extends d{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 d{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 d{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 d{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 d{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 w(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(void 0!==e.id){const r=this.promiseResponseMap.get(e.id);void 0!==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 v 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 x extends v{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;lastTaskTimestamp;aliveInterval;opts;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){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==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,x as DynamicThreadPool,S as FixedClusterPool,v 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 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({full:"full",busy:"busy"}),d=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 g 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 T 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 W 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 f 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 y 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 S{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=d.ROUND_ROBIN){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[d.ROUND_ROBIN,new f(e)],[d.LESS_USED,new W(e)],[d.LESS_BUSY,new T(e)],[d.FAIR_SHARE,new g(e)],[d.WEIGHTED_ROUND_ROBIN,new y(e)]])}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 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 S(this,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){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??d.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.enableEvents=e.enableEvents??!0}checkValidWorkerChoiceStrategy(e){if(!Object.values(d).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.promiseResponseMap.size}getWorkerKey(e){return this.workers.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(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.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run))}chooseWorker(){let e;if(this.type!==u.DYNAMIC||this.full||-1!==this.findFreeWorkerKey())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.destroyWorker(r)})),e=this.getWorkerKey(r)}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(p.busy)}checkAndEmitFull(){this.type===u.DYNAMIC&&!0===this.opts.enableEvents&&this.full&&this.emitter?.emit(p.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 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 u.FIXED}get full(){return this.workers.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class x extends I{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 v 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 E extends v{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 C=6e4,b=l.SOFT;class M extends k{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:b,maxInactiveTime:C}){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??C)/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??b,this.opts.maxInactiveTime=e.maxInactiveTime??C,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??C)&&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,runTime: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,runTime: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 D 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 O extends M{constructor(e,r={}){super("worker-thread-pool:poolifier",i,e,h,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{D as ClusterWorker,x as DynamicClusterPool,E as DynamicThreadPool,I as FixedClusterPool,v as FixedThreadPool,l as KillBehaviors,p as PoolEvents,O as ThreadWorker,d as WorkerChoiceStrategies};
|
|
@@ -1,5 +1,5 @@
|
|
|
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
4
|
import type { IPoolInternal, WorkerType } from './pool-internal';
|
|
5
5
|
import { PoolType } from './pool-internal';
|
|
@@ -17,9 +17,9 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
17
17
|
readonly numberOfWorkers: number;
|
|
18
18
|
readonly filePath: string;
|
|
19
19
|
readonly opts: PoolOptions<Worker>;
|
|
20
|
-
/**
|
|
20
|
+
/** @inheritDoc */
|
|
21
21
|
readonly workers: Array<WorkerType<Worker>>;
|
|
22
|
-
/**
|
|
22
|
+
/** @inheritDoc */
|
|
23
23
|
readonly emitter?: PoolEmitter;
|
|
24
24
|
/**
|
|
25
25
|
* The promise response map.
|
|
@@ -47,7 +47,8 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
47
47
|
private checkFilePath;
|
|
48
48
|
private checkNumberOfWorkers;
|
|
49
49
|
private checkPoolOptions;
|
|
50
|
-
|
|
50
|
+
private checkValidWorkerChoiceStrategy;
|
|
51
|
+
/** @inheritDoc */
|
|
51
52
|
abstract get type(): PoolType;
|
|
52
53
|
/**
|
|
53
54
|
* Number of tasks concurrently running in the pool.
|
|
@@ -60,21 +61,21 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
60
61
|
* @returns The worker key if the worker is found in the pool, `-1` otherwise.
|
|
61
62
|
*/
|
|
62
63
|
private getWorkerKey;
|
|
63
|
-
/**
|
|
64
|
+
/** @inheritDoc */
|
|
64
65
|
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
|
|
65
|
-
/**
|
|
66
|
+
/** @inheritDoc */
|
|
66
67
|
abstract get full(): boolean;
|
|
67
|
-
/**
|
|
68
|
+
/** @inheritDoc */
|
|
68
69
|
abstract get busy(): boolean;
|
|
69
70
|
protected internalBusy(): boolean;
|
|
70
|
-
/**
|
|
71
|
+
/** @inheritDoc */
|
|
71
72
|
findFreeWorkerKey(): number;
|
|
72
|
-
/**
|
|
73
|
+
/** @inheritDoc */
|
|
73
74
|
execute(data: Data): Promise<Response>;
|
|
74
|
-
/**
|
|
75
|
+
/** @inheritDoc */
|
|
75
76
|
destroy(): Promise<void>;
|
|
76
77
|
/**
|
|
77
|
-
* Shutdowns given worker.
|
|
78
|
+
* Shutdowns given worker in the pool.
|
|
78
79
|
*
|
|
79
80
|
* @param worker - A worker within `workers`.
|
|
80
81
|
*/
|
|
@@ -82,6 +83,9 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
82
83
|
/**
|
|
83
84
|
* Setup hook that can be overridden by a Poolifier pool implementation
|
|
84
85
|
* to run code before workers are created in the abstract constructor.
|
|
86
|
+
* Can be overridden
|
|
87
|
+
*
|
|
88
|
+
* @virtual
|
|
85
89
|
*/
|
|
86
90
|
protected setupHook(): void;
|
|
87
91
|
/**
|
|
@@ -135,6 +139,7 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
135
139
|
* Can be used to update the `maxListeners` or binding the `main-worker`\<-\>`worker` connection if not bind by default.
|
|
136
140
|
*
|
|
137
141
|
* @param worker - The newly created worker.
|
|
142
|
+
* @virtual
|
|
138
143
|
*/
|
|
139
144
|
protected abstract afterWorkerSetup(worker: Worker): void;
|
|
140
145
|
/**
|
|
@@ -153,7 +158,7 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
153
158
|
private checkAndEmitBusy;
|
|
154
159
|
private checkAndEmitFull;
|
|
155
160
|
/**
|
|
156
|
-
* Gets worker tasks usage.
|
|
161
|
+
* Gets the given worker tasks usage in the pool.
|
|
157
162
|
*
|
|
158
163
|
* @param worker - The worker.
|
|
159
164
|
* @returns The worker tasks usage.
|
|
@@ -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
|
*
|
|
@@ -23,10 +23,10 @@ export declare class DynamicClusterPool<Data = unknown, Response = unknown> exte
|
|
|
23
23
|
* @param opts - Options for this dynamic cluster pool.
|
|
24
24
|
*/
|
|
25
25
|
constructor(min: number, max: number, filePath: string, opts?: ClusterPoolOptions);
|
|
26
|
-
/**
|
|
26
|
+
/** @inheritDoc */
|
|
27
27
|
get type(): PoolType;
|
|
28
|
-
/**
|
|
28
|
+
/** @inheritDoc */
|
|
29
29
|
get full(): boolean;
|
|
30
|
-
/**
|
|
30
|
+
/** @inheritDoc */
|
|
31
31
|
get busy(): boolean;
|
|
32
32
|
}
|
|
@@ -43,24 +43,24 @@ export declare class FixedClusterPool<Data = unknown, Response = unknown> extend
|
|
|
43
43
|
* @param opts - Options for this fixed cluster pool.
|
|
44
44
|
*/
|
|
45
45
|
constructor(numberOfWorkers: number, filePath: string, opts?: ClusterPoolOptions);
|
|
46
|
-
/**
|
|
46
|
+
/** @inheritDoc */
|
|
47
47
|
protected setupHook(): void;
|
|
48
|
-
/**
|
|
48
|
+
/** @inheritDoc */
|
|
49
49
|
protected isMain(): boolean;
|
|
50
|
-
/**
|
|
50
|
+
/** @inheritDoc */
|
|
51
51
|
destroyWorker(worker: Worker): void;
|
|
52
|
-
/**
|
|
52
|
+
/** @inheritDoc */
|
|
53
53
|
protected sendToWorker(worker: Worker, message: MessageValue<Data>): void;
|
|
54
|
-
/**
|
|
54
|
+
/** @inheritDoc */
|
|
55
55
|
registerWorkerMessageListener<Message extends Data | Response>(worker: Worker, listener: (message: MessageValue<Message>) => void): void;
|
|
56
|
-
/**
|
|
56
|
+
/** @inheritDoc */
|
|
57
57
|
protected createWorker(): Worker;
|
|
58
|
-
/**
|
|
58
|
+
/** @inheritDoc */
|
|
59
59
|
protected afterWorkerSetup(worker: Worker): void;
|
|
60
|
-
/**
|
|
60
|
+
/** @inheritDoc */
|
|
61
61
|
get type(): PoolType;
|
|
62
|
-
/**
|
|
62
|
+
/** @inheritDoc */
|
|
63
63
|
get full(): boolean;
|
|
64
|
-
/**
|
|
64
|
+
/** @inheritDoc */
|
|
65
65
|
get busy(): boolean;
|
|
66
66
|
}
|
|
@@ -2,6 +2,8 @@ import type { IPool } from './pool';
|
|
|
2
2
|
import type { IPoolWorker } from './pool-worker';
|
|
3
3
|
/**
|
|
4
4
|
* Internal pool types.
|
|
5
|
+
*
|
|
6
|
+
* @enum
|
|
5
7
|
*/
|
|
6
8
|
export declare enum PoolType {
|
|
7
9
|
FIXED = "fixed",
|
|
@@ -61,7 +63,7 @@ export interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Respo
|
|
|
61
63
|
*
|
|
62
64
|
* If a worker is found with `0` running tasks, it is detected as free and its key is returned.
|
|
63
65
|
*
|
|
64
|
-
* If no free worker is found, `
|
|
66
|
+
* If no free worker is found, `-1` is returned.
|
|
65
67
|
*
|
|
66
68
|
* @returns A worker key if there is one, `-1` otherwise.
|
|
67
69
|
*/
|
package/lib/pools/pool.d.ts
CHANGED
|
@@ -7,6 +7,17 @@ import type { WorkerChoiceStrategy } from './selection-strategies/selection-stra
|
|
|
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
|
*/
|
|
@@ -8,11 +8,11 @@ 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 IPoolWorker, Data = unknown, Response = unknown> implements IWorkerChoiceStrategy
|
|
12
|
-
readonly pool: IPoolInternal<Worker, Data, Response>;
|
|
13
|
-
/**
|
|
14
|
-
readonly isDynamicPool: boolean;
|
|
15
|
-
/**
|
|
11
|
+
export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> implements IWorkerChoiceStrategy {
|
|
12
|
+
protected readonly pool: IPoolInternal<Worker, Data, Response>;
|
|
13
|
+
/** @inheritDoc */
|
|
14
|
+
protected readonly isDynamicPool: boolean;
|
|
15
|
+
/** @inheritDoc */
|
|
16
16
|
requiredStatistics: RequiredStatistics;
|
|
17
17
|
/**
|
|
18
18
|
* Constructs a worker choice strategy bound to the pool.
|
|
@@ -20,10 +20,10 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IPoolW
|
|
|
20
20
|
* @param pool - The pool instance.
|
|
21
21
|
*/
|
|
22
22
|
constructor(pool: IPoolInternal<Worker, Data, Response>);
|
|
23
|
-
/**
|
|
23
|
+
/** @inheritDoc */
|
|
24
24
|
abstract reset(): boolean;
|
|
25
|
-
/**
|
|
25
|
+
/** @inheritDoc */
|
|
26
26
|
abstract choose(): number;
|
|
27
|
-
/**
|
|
27
|
+
/** @inheritDoc */
|
|
28
28
|
abstract remove(workerKey: number): boolean;
|
|
29
29
|
}
|
|
@@ -9,18 +9,18 @@ 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 IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy
|
|
13
|
-
/**
|
|
12
|
+
export declare class FairShareWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
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 */
|
|
20
20
|
reset(): boolean;
|
|
21
|
-
/**
|
|
21
|
+
/** @inheritDoc */
|
|
22
22
|
choose(): number;
|
|
23
|
-
/**
|
|
23
|
+
/** @inheritDoc */
|
|
24
24
|
remove(workerKey: number): boolean;
|
|
25
25
|
/**
|
|
26
26
|
* Computes worker last virtual task timestamp.
|
|
@@ -8,13 +8,13 @@ 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 IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy
|
|
12
|
-
/**
|
|
11
|
+
export declare class LessBusyWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
|
+
/** @inheritDoc */
|
|
13
13
|
readonly requiredStatistics: RequiredStatistics;
|
|
14
|
-
/**
|
|
14
|
+
/** @inheritDoc */
|
|
15
15
|
reset(): boolean;
|
|
16
|
-
/**
|
|
16
|
+
/** @inheritDoc */
|
|
17
17
|
choose(): number;
|
|
18
|
-
/**
|
|
18
|
+
/** @inheritDoc */
|
|
19
19
|
remove(workerKey: number): boolean;
|
|
20
20
|
}
|
|
@@ -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 IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy
|
|
12
|
-
/**
|
|
11
|
+
export declare class LessUsedWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
|
+
/** @inheritDoc */
|
|
13
13
|
reset(): boolean;
|
|
14
|
-
/**
|
|
14
|
+
/** @inheritDoc */
|
|
15
15
|
choose(): number;
|
|
16
|
-
/**
|
|
16
|
+
/** @inheritDoc */
|
|
17
17
|
remove(workerKey: number): boolean;
|
|
18
18
|
}
|
|
@@ -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 IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy
|
|
11
|
+
export declare class RoundRobinWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
12
|
/**
|
|
13
13
|
* Id of the next worker.
|
|
14
14
|
*/
|
|
15
15
|
private nextWorkerId;
|
|
16
|
-
/**
|
|
16
|
+
/** @inheritDoc */
|
|
17
17
|
reset(): boolean;
|
|
18
|
-
/**
|
|
18
|
+
/** @inheritDoc */
|
|
19
19
|
choose(): number;
|
|
20
|
-
/**
|
|
20
|
+
/** @inheritDoc */
|
|
21
21
|
remove(workerKey: 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
|
*/
|
|
@@ -39,15 +37,7 @@ export interface RequiredStatistics {
|
|
|
39
37
|
/**
|
|
40
38
|
* Worker choice strategy interface.
|
|
41
39
|
*/
|
|
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;
|
|
40
|
+
export interface IWorkerChoiceStrategy {
|
|
51
41
|
/**
|
|
52
42
|
* Required pool tasks usage statistics.
|
|
53
43
|
*/
|
|
@@ -10,8 +10,8 @@ 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 IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy
|
|
14
|
-
/**
|
|
13
|
+
export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
14
|
+
/** @inheritDoc */
|
|
15
15
|
readonly requiredStatistics: RequiredStatistics;
|
|
16
16
|
/**
|
|
17
17
|
* Worker id where the current task will be submitted.
|
|
@@ -31,11 +31,11 @@ export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IPool
|
|
|
31
31
|
* @param pool - The pool instance.
|
|
32
32
|
*/
|
|
33
33
|
constructor(pool: IPoolInternal<Worker, Data, Response>);
|
|
34
|
-
/**
|
|
34
|
+
/** @inheritDoc */
|
|
35
35
|
reset(): boolean;
|
|
36
|
-
/**
|
|
36
|
+
/** @inheritDoc */
|
|
37
37
|
choose(): number;
|
|
38
|
-
/**
|
|
38
|
+
/** @inheritDoc */
|
|
39
39
|
remove(workerKey: number): boolean;
|
|
40
40
|
private initWorkersTaskRunTime;
|
|
41
41
|
private initWorkerTaskRunTime;
|
|
@@ -9,7 +9,6 @@ import type { RequiredStatistics, WorkerChoiceStrategy } from './selection-strat
|
|
|
9
9
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
11
|
export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Data = unknown, Response = unknown> {
|
|
12
|
-
private readonly createWorkerCallback;
|
|
13
12
|
private workerChoiceStrategyType;
|
|
14
13
|
private readonly workerChoiceStrategies;
|
|
15
14
|
/**
|
|
@@ -19,7 +18,7 @@ export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Dat
|
|
|
19
18
|
* @param createWorkerCallback - The worker creation callback for dynamic pool.
|
|
20
19
|
* @param workerChoiceStrategy - The worker choice strategy.
|
|
21
20
|
*/
|
|
22
|
-
constructor(pool: IPoolInternal<Worker, Data, Response>,
|
|
21
|
+
constructor(pool: IPoolInternal<Worker, Data, Response>, workerChoiceStrategyType?: WorkerChoiceStrategy);
|
|
23
22
|
/**
|
|
24
23
|
* Gets the worker choice strategy in the context required statistics.
|
|
25
24
|
*
|
|
@@ -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
|
*
|
|
@@ -24,10 +24,10 @@ export declare class DynamicThreadPool<Data = unknown, Response = unknown> exten
|
|
|
24
24
|
* @param opts - Options for this dynamic thread pool.
|
|
25
25
|
*/
|
|
26
26
|
constructor(min: number, max: number, filePath: string, opts?: PoolOptions<ThreadWorkerWithMessageChannel>);
|
|
27
|
-
/**
|
|
27
|
+
/** @inheritDoc */
|
|
28
28
|
get type(): PoolType;
|
|
29
|
-
/**
|
|
29
|
+
/** @inheritDoc */
|
|
30
30
|
get full(): boolean;
|
|
31
|
-
/**
|
|
31
|
+
/** @inheritDoc */
|
|
32
32
|
get busy(): boolean;
|
|
33
33
|
}
|
|
@@ -29,22 +29,22 @@ export declare class FixedThreadPool<Data = unknown, Response = unknown> extends
|
|
|
29
29
|
* @param opts - Options for this fixed thread pool.
|
|
30
30
|
*/
|
|
31
31
|
constructor(numberOfThreads: number, filePath: string, opts?: PoolOptions<ThreadWorkerWithMessageChannel>);
|
|
32
|
-
/**
|
|
32
|
+
/** @inheritDoc */
|
|
33
33
|
protected isMain(): boolean;
|
|
34
|
-
/**
|
|
34
|
+
/** @inheritDoc */
|
|
35
35
|
destroyWorker(worker: ThreadWorkerWithMessageChannel): Promise<void>;
|
|
36
|
-
/**
|
|
36
|
+
/** @inheritDoc */
|
|
37
37
|
protected sendToWorker(worker: ThreadWorkerWithMessageChannel, message: MessageValue<Data>): void;
|
|
38
|
-
/**
|
|
38
|
+
/** @inheritDoc */
|
|
39
39
|
registerWorkerMessageListener<Message extends Data | Response>(messageChannel: ThreadWorkerWithMessageChannel, listener: (message: MessageValue<Message>) => void): void;
|
|
40
|
-
/**
|
|
40
|
+
/** @inheritDoc */
|
|
41
41
|
protected createWorker(): ThreadWorkerWithMessageChannel;
|
|
42
|
-
/**
|
|
42
|
+
/** @inheritDoc */
|
|
43
43
|
protected afterWorkerSetup(worker: ThreadWorkerWithMessageChannel): void;
|
|
44
|
-
/**
|
|
44
|
+
/** @inheritDoc */
|
|
45
45
|
get type(): PoolType;
|
|
46
|
-
/**
|
|
46
|
+
/** @inheritDoc */
|
|
47
47
|
get full(): boolean;
|
|
48
|
-
/**
|
|
48
|
+
/** @inheritDoc */
|
|
49
49
|
get busy(): boolean;
|
|
50
50
|
}
|
package/lib/utility-types.d.ts
CHANGED
|
@@ -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
|
*
|
|
@@ -17,18 +17,15 @@ import type { WorkerOptions } from './worker-options';
|
|
|
17
17
|
export declare abstract class AbstractWorker<MainWorker extends Worker | MessagePort, Data = unknown, Response = unknown> extends AsyncResource {
|
|
18
18
|
protected readonly isMain: boolean;
|
|
19
19
|
protected mainWorker: MainWorker | undefined | null;
|
|
20
|
+
protected readonly opts: WorkerOptions;
|
|
20
21
|
/**
|
|
21
22
|
* Timestamp of the last task processed by this worker.
|
|
22
23
|
*/
|
|
23
24
|
protected lastTaskTimestamp: number;
|
|
24
25
|
/**
|
|
25
|
-
* Handler
|
|
26
|
+
* Handler id of the `aliveInterval` worker alive check.
|
|
26
27
|
*/
|
|
27
28
|
protected readonly aliveInterval?: NodeJS.Timeout;
|
|
28
|
-
/**
|
|
29
|
-
* Options for the worker.
|
|
30
|
-
*/
|
|
31
|
-
readonly opts: WorkerOptions;
|
|
32
29
|
/**
|
|
33
30
|
* Constructs a new poolifier worker.
|
|
34
31
|
*
|
|
@@ -39,7 +36,13 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
39
36
|
* @param opts - Options for the worker.
|
|
40
37
|
*/
|
|
41
38
|
constructor(type: string, isMain: boolean, fn: (data: Data) => Response, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
|
|
42
|
-
|
|
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;
|
|
43
46
|
private checkWorkerOptions;
|
|
44
47
|
/**
|
|
45
48
|
* Checks if the `fn` parameter is passed to the constructor.
|
|
@@ -74,14 +77,14 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
74
77
|
* Runs the given function synchronously.
|
|
75
78
|
*
|
|
76
79
|
* @param fn - Function that will be executed.
|
|
77
|
-
* @param
|
|
80
|
+
* @param message - Input data for the given function.
|
|
78
81
|
*/
|
|
79
|
-
protected run(fn: (data?: Data) => Response,
|
|
82
|
+
protected run(fn: (data?: Data) => Response, message: MessageValue<Data>): void;
|
|
80
83
|
/**
|
|
81
84
|
* Runs the given function asynchronously.
|
|
82
85
|
*
|
|
83
86
|
* @param fn - Function that will be executed.
|
|
84
|
-
* @param
|
|
87
|
+
* @param message - Input data for the given function.
|
|
85
88
|
*/
|
|
86
|
-
protected runAsync(fn: (data?: Data) => Promise<Response>,
|
|
89
|
+
protected runAsync(fn: (data?: Data) => Promise<Response>, message: MessageValue<Data>): void;
|
|
87
90
|
}
|
|
@@ -25,8 +25,8 @@ export declare class ClusterWorker<Data = unknown, Response = unknown> extends A
|
|
|
25
25
|
* @param opts - Options for the worker.
|
|
26
26
|
*/
|
|
27
27
|
constructor(fn: (data: Data) => Response, opts?: WorkerOptions);
|
|
28
|
-
/**
|
|
28
|
+
/** @inheritDoc */
|
|
29
29
|
protected sendToMainWorker(message: MessageValue<Response>): void;
|
|
30
|
-
/**
|
|
30
|
+
/** @inheritDoc */
|
|
31
31
|
protected handleError(e: Error | string): string;
|
|
32
32
|
}
|
|
@@ -25,6 +25,6 @@ export declare class ThreadWorker<Data = unknown, Response = unknown> extends Ab
|
|
|
25
25
|
* @param opts - Options for the worker.
|
|
26
26
|
*/
|
|
27
27
|
constructor(fn: (data: Data) => Response, opts?: WorkerOptions);
|
|
28
|
-
/**
|
|
28
|
+
/** @inheritDoc */
|
|
29
29
|
protected sendToMainWorker(message: MessageValue<Response>): void;
|
|
30
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poolifier",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.4",
|
|
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",
|
|
@@ -97,7 +97,7 @@
|
|
|
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",
|