poolifier 2.4.0 → 2.4.2
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 -1
- package/lib/index.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/pools/abstract-pool.d.ts +19 -14
- package/lib/pools/cluster/dynamic.d.ts +4 -2
- package/lib/pools/cluster/fixed.d.ts +2 -0
- package/lib/pools/pool-internal.d.ts +10 -8
- package/lib/pools/pool.d.ts +2 -1
- package/lib/pools/selection-strategies/abstract-worker-choice-strategy.d.ts +4 -4
- package/lib/pools/selection-strategies/fair-share-worker-choice-strategy.d.ts +1 -1
- package/lib/pools/selection-strategies/less-busy-worker-choice-strategy.d.ts +1 -1
- package/lib/pools/selection-strategies/less-used-worker-choice-strategy.d.ts +1 -1
- package/lib/pools/selection-strategies/round-robin-worker-choice-strategy.d.ts +1 -1
- package/lib/pools/selection-strategies/selection-strategies-types.d.ts +9 -3
- package/lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.d.ts +1 -1
- package/lib/pools/selection-strategies/worker-choice-strategy-context.d.ts +7 -14
- package/lib/pools/thread/dynamic.d.ts +4 -2
- package/lib/pools/thread/fixed.d.ts +2 -0
- package/lib/worker/abstract-worker.d.ts +2 -1
- package/package.json +3 -3
- package/lib/pools/selection-strategies/dynamic-pool-worker-choice-strategy.d.ts +0 -29
- package/lib/pools/selection-strategies/selection-strategies-utils.d.ts +0 -11
package/README.md
CHANGED
|
@@ -120,11 +120,14 @@ 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('busy', () => console.log('Pool is busy'))
|
|
124
|
+
|
|
123
125
|
// or a dynamic worker-threads pool
|
|
124
126
|
const pool = new DynamicThreadPool(10, 100,
|
|
125
127
|
'./yourWorker.js',
|
|
126
128
|
{ errorHandler: (e) => console.error(e), onlineHandler: () => console.log('worker is online') })
|
|
127
129
|
|
|
130
|
+
pool.emitter.on('full', () => console.log('Pool is full'))
|
|
128
131
|
pool.emitter.on('busy', () => console.log('Pool is busy'))
|
|
129
132
|
|
|
130
133
|
// the execute method signature is the same for both implementations,
|
|
@@ -165,7 +168,7 @@ Node versions >= 16.x are supported.
|
|
|
165
168
|
- `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in a round robbin fashion
|
|
166
169
|
- `WorkerChoiceStrategies.LESS_USED`: Submit tasks to the less used worker
|
|
167
170
|
- `WorkerChoiceStrategies.LESS_BUSY`: Submit tasks to the less busy worker
|
|
168
|
-
- `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
|
|
171
|
+
- `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`: Submit tasks to worker using a weighted round robin scheduling algorithm based on tasks execution time
|
|
169
172
|
- `WorkerChoiceStrategies.FAIR_SHARE`: Submit tasks to worker using a fair share tasks scheduling algorithm based on tasks execution time
|
|
170
173
|
|
|
171
174
|
`WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,r=require("node:cluster"),t=require("node:crypto"),s=require("node:events"),o=require("node:os"),i=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"});function k(e,r){return r===e}class u extends s{}const 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}}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(!this.isDynamicPool&&-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const o=s.tasksUsage.runTime;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class d extends l{reset(){return!0}choose(){const e=this.pool.findFreeWorkerKey();if(!this.isDynamicPool&&-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const o=s.tasksUsage,i=o?.run+o?.running;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class w 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&&(this.nextWorkerId=this.nextWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.nextWorkerId),!0}}class g 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&&(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 o.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/o.cpus().length)}}function W(e,r=c.ROUND_ROBIN){switch(r){case c.ROUND_ROBIN:return new w(e);case c.LESS_USED:return new d(e);case c.LESS_BUSY:return new m(e);case c.FAIR_SHARE:return new p(e);case c.WEIGHTED_ROUND_ROBIN:return new g(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class T extends l{createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=c.ROUND_ROBIN){super(e),this.createWorkerCallback=r,this.workerChoiceStrategy=W(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorkerKey();return-1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createWorkerCallback()}remove(e){return this.workerChoiceStrategy.remove(e)}}class y{pool;createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=c.ROUND_ROBIN){this.pool=e,this.createWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(r=c.ROUND_ROBIN){return this.pool.type===e.DYNAMIC?new T(this.pool,this.createWorkerCallback,r):W(this.pool,r)}getRequiredStatistics(){return this.workerChoiceStrategy.requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}remove(e){return this.workerChoiceStrategy.remove(e)}}class f{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.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new y(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{(k(h.HARD,r.kill)||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){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??c.ROUND_ROBIN,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)}internalGetBusyStatus(){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(),o=t.randomUUID(),i=this.internalExecute(r,s,o);return this.checkAndEmitBusy(),this.sendToWorker(s,{data:e??{},id:o}),i}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))}removeWorker(e){const r=this.getWorkerKey(e);this.workers.splice(r,1),this.workerChoiceStrategyContext.remove(r)}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")}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}}}class R 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 busy(){return this.internalGetBusyStatus()}}class S extends f{constructor(e,r,t={}){super(e,r,t)}isMain(){return i.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 i.Worker(this.filePath,{env:i.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new i.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}const x=6e4,I=h.SOFT;class v extends n.AsyncResource{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:I,maxInactiveTime:x}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),!r&&k(h.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??x)/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??I,this.opts.maxInactiveTime=e.maxInactiveTime??x,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??x)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{k(h.HARD,this.opts.killBehavior)&&(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((()=>{k(h.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now())})).catch(a)}}exports.ClusterWorker=class extends v{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 R{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends S{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=R,exports.FixedThreadPool=S,exports.KillBehaviors=h,exports.ThreadWorker=class extends v{constructor(e,r={}){super("worker-thread-pool:poolifier",i.isMainThread,e,i.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=c;
|
|
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;
|
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 o,Worker as i,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"});function m(e,r){return r===e}class p extends t{}const w=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}}class g 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 W extends d{requiredStatistics={runTime:!0,avgRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerKey();if(!this.isDynamicPool&&-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const o=s.tasksUsage.runTime;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class T extends d{reset(){return!0}choose(){const e=this.pool.findFreeWorkerKey();if(!this.isDynamicPool&&-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workers.entries()){const o=s.tasksUsage,i=o?.run+o?.running;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class f 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&&(this.nextWorkerId=this.nextWorkerId>this.pool.workers.length-1?this.pool.workers.length-1:this.nextWorkerId),!0}}class y 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&&(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)}}function R(e,r=w.ROUND_ROBIN){switch(r){case w.ROUND_ROBIN:return new f(e);case w.LESS_USED:return new T(e);case w.LESS_BUSY:return new W(e);case w.FAIR_SHARE:return new g(e);case w.WEIGHTED_ROUND_ROBIN:return new y(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class S extends d{createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=w.ROUND_ROBIN){super(e),this.createWorkerCallback=r,this.workerChoiceStrategy=R(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorkerKey();return-1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createWorkerCallback()}remove(e){return this.workerChoiceStrategy.remove(e)}}class I{pool;createWorkerCallback;workerChoiceStrategy;constructor(e,r,t=w.ROUND_ROBIN){this.pool=e,this.createWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(e=w.ROUND_ROBIN){return this.pool.type===u.DYNAMIC?new S(this.pool,this.createWorkerCallback,e):R(this.pool,e)}getRequiredStatistics(){return this.workerChoiceStrategy.requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}remove(e){return this.workerChoiceStrategy.remove(e)}}class v{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.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new p),this.workerChoiceStrategyContext=new I(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{(m(l.HARD,r.kill)||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){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??w.ROUND_ROBIN,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)}internalGetBusyStatus(){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(),o=r.randomUUID(),i=this.internalExecute(t,s,o);return this.checkAndEmitBusy(),this.sendToWorker(s,{data:e??{},id:o}),i}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))}removeWorker(e){const r=this.getWorkerKey(e);this.workers.splice(r,1),this.workerChoiceStrategyContext.remove(r)}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")}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}}}class x extends v{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 busy(){return this.internalGetBusyStatus()}}class C extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return u.DYNAMIC}get busy(){return this.workers.length===this.max}}class D extends v{constructor(e,r,t={}){super(e,r,t)}isMain(){return o}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 i(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 busy(){return this.internalGetBusyStatus()}}class E extends D{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return u.DYNAMIC}get busy(){return this.workers.length===this.max}}const O=6e4,b=l.SOFT;class M extends k{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:b,maxInactiveTime:O}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),!r&&m(l.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??O)/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??b,this.opts.maxInactiveTime=e.maxInactiveTime??O,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??O)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{m(l.HARD,this.opts.killBehavior)&&(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((()=>{m(l.HARD,this.opts.killBehavior)&&(this.lastTaskTimestamp=Date.now())})).catch(c)}}class U 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 N extends M{constructor(e,r={}){super("worker-thread-pool:poolifier",o,e,h,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{U as ClusterWorker,C as DynamicClusterPool,E as DynamicThreadPool,x as FixedClusterPool,D as FixedThreadPool,l as KillBehaviors,N as ThreadWorker,w 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({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};
|
|
@@ -31,9 +31,9 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
31
31
|
*/
|
|
32
32
|
protected promiseResponseMap: Map<string, PromiseResponseWrapper<Worker, Response>>;
|
|
33
33
|
/**
|
|
34
|
-
* Worker choice strategy
|
|
34
|
+
* Worker choice strategy context referencing a worker choice algorithm implementation.
|
|
35
35
|
*
|
|
36
|
-
* Default to a
|
|
36
|
+
* Default to a round robin algorithm.
|
|
37
37
|
*/
|
|
38
38
|
protected workerChoiceStrategyContext: WorkerChoiceStrategyContext<Worker, Data, Response>;
|
|
39
39
|
/**
|
|
@@ -49,8 +49,10 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
49
49
|
private checkPoolOptions;
|
|
50
50
|
/** {@inheritDoc} */
|
|
51
51
|
abstract get type(): PoolType;
|
|
52
|
-
/**
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Number of tasks concurrently running in the pool.
|
|
54
|
+
*/
|
|
55
|
+
private get numberOfRunningTasks();
|
|
54
56
|
/**
|
|
55
57
|
* Gets the given worker key.
|
|
56
58
|
*
|
|
@@ -61,8 +63,10 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
61
63
|
/** {@inheritDoc} */
|
|
62
64
|
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
|
|
63
65
|
/** {@inheritDoc} */
|
|
66
|
+
abstract get full(): boolean;
|
|
67
|
+
/** {@inheritDoc} */
|
|
64
68
|
abstract get busy(): boolean;
|
|
65
|
-
protected
|
|
69
|
+
protected internalBusy(): boolean;
|
|
66
70
|
/** {@inheritDoc} */
|
|
67
71
|
findFreeWorkerKey(): number;
|
|
68
72
|
/** {@inheritDoc} */
|
|
@@ -99,16 +103,10 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
99
103
|
* @param message - The received message.
|
|
100
104
|
*/
|
|
101
105
|
protected afterPromiseResponseHook(worker: Worker, message: MessageValue<Response>): void;
|
|
102
|
-
/**
|
|
103
|
-
* Removes the given worker from the pool.
|
|
104
|
-
*
|
|
105
|
-
* @param worker - The worker that will be removed.
|
|
106
|
-
*/
|
|
107
|
-
protected removeWorker(worker: Worker): void;
|
|
108
106
|
/**
|
|
109
107
|
* Chooses a worker for the next task.
|
|
110
108
|
*
|
|
111
|
-
* The default
|
|
109
|
+
* The default uses a round robin algorithm to distribute the load.
|
|
112
110
|
*
|
|
113
111
|
* @returns [worker key, worker].
|
|
114
112
|
*/
|
|
@@ -153,6 +151,7 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
153
151
|
protected workerListener(): (message: MessageValue<Response>) => void;
|
|
154
152
|
private internalExecute;
|
|
155
153
|
private checkAndEmitBusy;
|
|
154
|
+
private checkAndEmitFull;
|
|
156
155
|
/**
|
|
157
156
|
* Gets worker tasks usage.
|
|
158
157
|
*
|
|
@@ -161,18 +160,24 @@ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = un
|
|
|
161
160
|
*/
|
|
162
161
|
private getWorkerTasksUsage;
|
|
163
162
|
/**
|
|
164
|
-
* Pushes the given worker.
|
|
163
|
+
* Pushes the given worker in the pool.
|
|
165
164
|
*
|
|
166
165
|
* @param worker - The worker.
|
|
167
166
|
* @param tasksUsage - The worker tasks usage.
|
|
168
167
|
*/
|
|
169
168
|
private pushWorker;
|
|
170
169
|
/**
|
|
171
|
-
* Sets the given worker.
|
|
170
|
+
* Sets the given worker in the pool.
|
|
172
171
|
*
|
|
173
172
|
* @param workerKey - The worker key.
|
|
174
173
|
* @param worker - The worker.
|
|
175
174
|
* @param tasksUsage - The worker tasks usage.
|
|
176
175
|
*/
|
|
177
176
|
private setWorker;
|
|
177
|
+
/**
|
|
178
|
+
* Removes the given worker from the pool.
|
|
179
|
+
*
|
|
180
|
+
* @param worker - The worker that will be removed.
|
|
181
|
+
*/
|
|
182
|
+
protected removeWorker(worker: Worker): void;
|
|
178
183
|
}
|
|
@@ -5,7 +5,7 @@ import { FixedClusterPool } from './fixed';
|
|
|
5
5
|
* A cluster pool with a dynamic number of workers, but a guaranteed minimum number of workers.
|
|
6
6
|
*
|
|
7
7
|
* This cluster pool creates new workers when the others are busy, up to the maximum number of workers.
|
|
8
|
-
* When the maximum number of workers is reached, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
|
|
8
|
+
* When the maximum number of workers is reached and workers are busy, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
|
|
9
9
|
*
|
|
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.
|
|
@@ -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
|
+
private readonly max;
|
|
17
17
|
/**
|
|
18
18
|
* Constructs a new poolifier dynamic cluster pool.
|
|
19
19
|
*
|
|
@@ -26,5 +26,7 @@ export declare class DynamicClusterPool<Data = unknown, Response = unknown> exte
|
|
|
26
26
|
/** {@inheritDoc} */
|
|
27
27
|
get type(): PoolType;
|
|
28
28
|
/** {@inheritDoc} */
|
|
29
|
+
get full(): boolean;
|
|
30
|
+
/** {@inheritDoc} */
|
|
29
31
|
get busy(): boolean;
|
|
30
32
|
}
|
|
@@ -20,7 +20,7 @@ export interface TasksUsage {
|
|
|
20
20
|
/**
|
|
21
21
|
* Internal worker type.
|
|
22
22
|
*
|
|
23
|
-
* @typeParam Worker - Type of worker which manages this pool.
|
|
23
|
+
* @typeParam Worker - Type of worker type items which manages this pool.
|
|
24
24
|
*/
|
|
25
25
|
export interface WorkerType<Worker extends IPoolWorker> {
|
|
26
26
|
worker: Worker;
|
|
@@ -30,12 +30,12 @@ export interface WorkerType<Worker extends IPoolWorker> {
|
|
|
30
30
|
* Internal contract definition for a poolifier pool.
|
|
31
31
|
*
|
|
32
32
|
* @typeParam Worker - Type of worker which manages this pool.
|
|
33
|
-
* @typeParam Data - Type of data sent to the worker.
|
|
34
|
-
* @typeParam Response - Type of response of execution.
|
|
33
|
+
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
34
|
+
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
35
35
|
*/
|
|
36
36
|
export interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends IPool<Data, Response> {
|
|
37
37
|
/**
|
|
38
|
-
* Pool
|
|
38
|
+
* Pool worker type items array.
|
|
39
39
|
*/
|
|
40
40
|
readonly workers: Array<WorkerType<Worker>>;
|
|
41
41
|
/**
|
|
@@ -44,16 +44,18 @@ export interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Respo
|
|
|
44
44
|
* If it is `'dynamic'`, it provides the `max` property.
|
|
45
45
|
*/
|
|
46
46
|
readonly type: PoolType;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the pool is full or not.
|
|
49
|
+
*
|
|
50
|
+
* The pool filling boolean status.
|
|
51
|
+
*/
|
|
52
|
+
readonly full: boolean;
|
|
47
53
|
/**
|
|
48
54
|
* Whether the pool is busy or not.
|
|
49
55
|
*
|
|
50
56
|
* The pool busyness boolean status.
|
|
51
57
|
*/
|
|
52
58
|
readonly busy: boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Number of tasks currently concurrently running.
|
|
55
|
-
*/
|
|
56
|
-
readonly numberOfRunningTasks: number;
|
|
57
59
|
/**
|
|
58
60
|
* Finds a free worker key based on the number of tasks the worker has applied.
|
|
59
61
|
*
|
package/lib/pools/pool.d.ts
CHANGED
|
@@ -50,7 +50,8 @@ export interface IPool<Data = unknown, Response = unknown> {
|
|
|
50
50
|
*
|
|
51
51
|
* Events that can currently be listened to:
|
|
52
52
|
*
|
|
53
|
-
* - `'
|
|
53
|
+
* - `'full'`: Emitted when the pool is dynamic and full.
|
|
54
|
+
* - `'busy'`: Emitted when the pool is busy.
|
|
54
55
|
*/
|
|
55
56
|
readonly emitter?: PoolEmitter;
|
|
56
57
|
/**
|
|
@@ -2,20 +2,20 @@ import type { IPoolInternal } from '../pool-internal';
|
|
|
2
2
|
import type { IPoolWorker } from '../pool-worker';
|
|
3
3
|
import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Worker choice strategy abstract base class.
|
|
6
6
|
*
|
|
7
7
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
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, Response> implements IWorkerChoiceStrategy {
|
|
12
|
-
|
|
11
|
+
export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> implements IWorkerChoiceStrategy<Worker, Data, Response> {
|
|
12
|
+
readonly pool: IPoolInternal<Worker, Data, Response>;
|
|
13
13
|
/** {@inheritDoc} */
|
|
14
14
|
readonly isDynamicPool: boolean;
|
|
15
15
|
/** {@inheritDoc} */
|
|
16
16
|
requiredStatistics: RequiredStatistics;
|
|
17
17
|
/**
|
|
18
|
-
* Constructs a worker choice strategy
|
|
18
|
+
* Constructs a worker choice strategy bound to the pool.
|
|
19
19
|
*
|
|
20
20
|
* @param pool - The pool instance.
|
|
21
21
|
*/
|
|
@@ -9,7 +9,7 @@ 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, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
|
+
export declare class FairShareWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy<Worker, Data, Response> {
|
|
13
13
|
/** {@inheritDoc} */
|
|
14
14
|
readonly requiredStatistics: RequiredStatistics;
|
|
15
15
|
/**
|
|
@@ -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 IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
11
|
+
export declare class LessBusyWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy<Worker, Data, Response> {
|
|
12
12
|
/** {@inheritDoc} */
|
|
13
13
|
readonly requiredStatistics: RequiredStatistics;
|
|
14
14
|
/** {@inheritDoc} */
|
|
@@ -8,7 +8,7 @@ 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, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
11
|
+
export declare class LessUsedWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy<Worker, Data, Response> {
|
|
12
12
|
/** {@inheritDoc} */
|
|
13
13
|
reset(): boolean;
|
|
14
14
|
/** {@inheritDoc} */
|
|
@@ -8,7 +8,7 @@ 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, Response> 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<Worker, Data, Response> {
|
|
12
12
|
/**
|
|
13
13
|
* Id of the next worker.
|
|
14
14
|
*/
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { IPoolInternal } from '../pool-internal';
|
|
2
|
+
import type { IPoolWorker } from '../pool-worker';
|
|
1
3
|
/**
|
|
2
4
|
* Enumeration of worker choice strategies.
|
|
3
5
|
*/
|
|
@@ -28,7 +30,7 @@ export declare const WorkerChoiceStrategies: Readonly<{
|
|
|
28
30
|
*/
|
|
29
31
|
export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies;
|
|
30
32
|
/**
|
|
31
|
-
* Pool tasks usage statistics requirements.
|
|
33
|
+
* Pool worker tasks usage statistics requirements.
|
|
32
34
|
*/
|
|
33
35
|
export interface RequiredStatistics {
|
|
34
36
|
runTime: boolean;
|
|
@@ -37,9 +39,13 @@ export interface RequiredStatistics {
|
|
|
37
39
|
/**
|
|
38
40
|
* Worker choice strategy interface.
|
|
39
41
|
*/
|
|
40
|
-
export interface IWorkerChoiceStrategy {
|
|
42
|
+
export interface IWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> {
|
|
41
43
|
/**
|
|
42
|
-
*
|
|
44
|
+
* The pool instance.
|
|
45
|
+
*/
|
|
46
|
+
readonly pool: IPoolInternal<Worker, Data, Response>;
|
|
47
|
+
/**
|
|
48
|
+
* Is the pool bound to the strategy dynamic?.
|
|
43
49
|
*/
|
|
44
50
|
readonly isDynamicPool: boolean;
|
|
45
51
|
/**
|
|
@@ -10,7 +10,7 @@ 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, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
|
+
export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy<Worker, Data, Response> {
|
|
14
14
|
/** {@inheritDoc} */
|
|
15
15
|
readonly requiredStatistics: RequiredStatistics;
|
|
16
16
|
/**
|
|
@@ -8,10 +8,10 @@ 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 IPoolWorker, Data, Response> {
|
|
12
|
-
private readonly pool;
|
|
11
|
+
export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Data = unknown, Response = unknown> {
|
|
13
12
|
private readonly createWorkerCallback;
|
|
14
|
-
private
|
|
13
|
+
private workerChoiceStrategyType;
|
|
14
|
+
private readonly workerChoiceStrategies;
|
|
15
15
|
/**
|
|
16
16
|
* Worker choice strategy context constructor.
|
|
17
17
|
*
|
|
@@ -19,16 +19,9 @@ export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Dat
|
|
|
19
19
|
* @param createWorkerCallback - The worker creation callback for dynamic pool.
|
|
20
20
|
* @param workerChoiceStrategy - The worker choice strategy.
|
|
21
21
|
*/
|
|
22
|
-
constructor(pool: IPoolInternal<Worker, Data, Response>, createWorkerCallback: () => number,
|
|
22
|
+
constructor(pool: IPoolInternal<Worker, Data, Response>, createWorkerCallback: () => number, workerChoiceStrategyType?: WorkerChoiceStrategy);
|
|
23
23
|
/**
|
|
24
|
-
* Gets the worker choice strategy
|
|
25
|
-
*
|
|
26
|
-
* @param workerChoiceStrategy - The worker choice strategy.
|
|
27
|
-
* @returns The worker choice strategy instance for the pool type.
|
|
28
|
-
*/
|
|
29
|
-
private getPoolWorkerChoiceStrategy;
|
|
30
|
-
/**
|
|
31
|
-
* Gets the worker choice strategy required statistics.
|
|
24
|
+
* Gets the worker choice strategy in the context required statistics.
|
|
32
25
|
*
|
|
33
26
|
* @returns The required statistics.
|
|
34
27
|
*/
|
|
@@ -40,13 +33,13 @@ export declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Dat
|
|
|
40
33
|
*/
|
|
41
34
|
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
|
|
42
35
|
/**
|
|
43
|
-
*
|
|
36
|
+
* Executes the worker choice strategy algorithm in the context.
|
|
44
37
|
*
|
|
45
38
|
* @returns The key of the chosen one.
|
|
46
39
|
*/
|
|
47
40
|
execute(): number;
|
|
48
41
|
/**
|
|
49
|
-
* Removes a worker
|
|
42
|
+
* Removes a worker from the worker choice strategy in the context.
|
|
50
43
|
*
|
|
51
44
|
* @param workerKey - The key of the worker to remove.
|
|
52
45
|
* @returns `true` if the removal is successful, `false` otherwise.
|
|
@@ -6,7 +6,7 @@ import { FixedThreadPool } from './fixed';
|
|
|
6
6
|
* A thread pool with a dynamic number of threads, but a guaranteed minimum number of threads.
|
|
7
7
|
*
|
|
8
8
|
* This thread pool creates new threads when the others are busy, up to the maximum number of threads.
|
|
9
|
-
* When the maximum number of threads is reached, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
|
|
9
|
+
* When the maximum number of threads is reached and workers are busy, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
|
|
10
10
|
*
|
|
11
11
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
12
12
|
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
@@ -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
|
+
private readonly max;
|
|
18
18
|
/**
|
|
19
19
|
* Constructs a new poolifier dynamic thread pool.
|
|
20
20
|
*
|
|
@@ -27,5 +27,7 @@ export declare class DynamicThreadPool<Data = unknown, Response = unknown> exten
|
|
|
27
27
|
/** {@inheritDoc} */
|
|
28
28
|
get type(): PoolType;
|
|
29
29
|
/** {@inheritDoc} */
|
|
30
|
+
get full(): boolean;
|
|
31
|
+
/** {@inheritDoc} */
|
|
30
32
|
get busy(): boolean;
|
|
31
33
|
}
|
|
@@ -6,7 +6,7 @@ import { AsyncResource } from 'node:async_hooks';
|
|
|
6
6
|
import type { Worker } from 'node:cluster';
|
|
7
7
|
import type { MessagePort } from 'node:worker_threads';
|
|
8
8
|
import type { MessageValue } from '../utility-types';
|
|
9
|
-
import {
|
|
9
|
+
import type { WorkerOptions } from './worker-options';
|
|
10
10
|
/**
|
|
11
11
|
* Base class that implements some shared logic for all poolifier workers.
|
|
12
12
|
*
|
|
@@ -15,6 +15,7 @@ import { type WorkerOptions } from './worker-options';
|
|
|
15
15
|
* @typeParam Response - Type of response the worker sends back to the main worker. This can only be serializable data.
|
|
16
16
|
*/
|
|
17
17
|
export declare abstract class AbstractWorker<MainWorker extends Worker | MessagePort, Data = unknown, Response = unknown> extends AsyncResource {
|
|
18
|
+
protected readonly isMain: boolean;
|
|
18
19
|
protected mainWorker: MainWorker | undefined | null;
|
|
19
20
|
/**
|
|
20
21
|
* Timestamp of the last task processed by this worker.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poolifier",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
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",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@release-it/bumper": "^4.0.2",
|
|
79
79
|
"@release-it/keep-a-changelog": "^3.1.0",
|
|
80
80
|
"@rollup/plugin-terser": "^0.4.0",
|
|
81
|
-
"@rollup/plugin-typescript": "^11.
|
|
81
|
+
"@rollup/plugin-typescript": "^11.1.0",
|
|
82
82
|
"@types/node": "^18.15.11",
|
|
83
83
|
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
|
84
84
|
"@typescript-eslint/parser": "^5.57.1",
|
|
@@ -88,7 +88,7 @@
|
|
|
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
|
-
"eslint-import-resolver-typescript": "^3.5.
|
|
91
|
+
"eslint-import-resolver-typescript": "^3.5.5",
|
|
92
92
|
"eslint-plugin-import": "^2.27.5",
|
|
93
93
|
"eslint-plugin-jsdoc": "^40.1.1",
|
|
94
94
|
"eslint-plugin-n": "^15.7.0",
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { IPoolInternal } from '../pool-internal';
|
|
2
|
-
import type { IPoolWorker } from '../pool-worker';
|
|
3
|
-
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
4
|
-
import type { IWorkerChoiceStrategy, WorkerChoiceStrategy } from './selection-strategies-types';
|
|
5
|
-
/**
|
|
6
|
-
* Selects the next worker for dynamic pool.
|
|
7
|
-
*
|
|
8
|
-
* @typeParam Worker - Type of worker which manages the strategy.
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
10
|
-
* @typeParam Response - Type of response of execution. This can only be serializable data.
|
|
11
|
-
*/
|
|
12
|
-
export declare class DynamicPoolWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
|
-
private readonly createWorkerCallback;
|
|
14
|
-
private readonly workerChoiceStrategy;
|
|
15
|
-
/**
|
|
16
|
-
* Constructs a worker choice strategy for dynamic pool.
|
|
17
|
-
*
|
|
18
|
-
* @param pool - The pool instance.
|
|
19
|
-
* @param createWorkerCallback - The worker creation callback for dynamic pool.
|
|
20
|
-
* @param workerChoiceStrategy - The worker choice strategy when the pool is busy.
|
|
21
|
-
*/
|
|
22
|
-
constructor(pool: IPoolInternal<Worker, Data, Response>, createWorkerCallback: () => number, workerChoiceStrategy?: WorkerChoiceStrategy);
|
|
23
|
-
/** {@inheritDoc} */
|
|
24
|
-
reset(): boolean;
|
|
25
|
-
/** {@inheritDoc} */
|
|
26
|
-
choose(): number;
|
|
27
|
-
/** {@inheritDoc} */
|
|
28
|
-
remove(workerKey: number): boolean;
|
|
29
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { IPoolInternal } from '../pool-internal';
|
|
2
|
-
import type { IPoolWorker } from '../pool-worker';
|
|
3
|
-
import type { IWorkerChoiceStrategy, WorkerChoiceStrategy } from './selection-strategies-types';
|
|
4
|
-
/**
|
|
5
|
-
* Gets the worker choice strategy instance.
|
|
6
|
-
*
|
|
7
|
-
* @param pool - The pool instance.
|
|
8
|
-
* @param workerChoiceStrategy - The worker choice strategy.
|
|
9
|
-
* @returns The worker choice strategy instance.
|
|
10
|
-
*/
|
|
11
|
-
export declare function getWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response>(pool: IPoolInternal<Worker, Data, Response>, workerChoiceStrategy?: WorkerChoiceStrategy): IWorkerChoiceStrategy;
|