poolifier 2.4.7 → 2.4.9
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/lib/index.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/pools/abstract-pool.d.ts +14 -5
- package/lib/pools/cluster/dynamic.d.ts +1 -1
- package/lib/pools/cluster/fixed.d.ts +1 -1
- package/lib/pools/pool.d.ts +28 -8
- package/lib/pools/selection-strategies/abstract-worker-choice-strategy.d.ts +5 -3
- package/lib/pools/selection-strategies/fair-share-worker-choice-strategy.d.ts +5 -2
- package/lib/pools/selection-strategies/less-busy-worker-choice-strategy.d.ts +5 -2
- package/lib/pools/selection-strategies/less-used-worker-choice-strategy.d.ts +5 -2
- package/lib/pools/selection-strategies/round-robin-worker-choice-strategy.d.ts +5 -2
- package/lib/pools/selection-strategies/selection-strategies-types.d.ts +10 -0
- package/lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.d.ts +2 -7
- package/lib/pools/selection-strategies/worker-choice-strategy-context.d.ts +10 -4
- package/lib/pools/thread/dynamic.d.ts +1 -1
- package/lib/pools/thread/fixed.d.ts +1 -1
- package/lib/pools/worker.d.ts +30 -7
- package/lib/utility-types.d.ts +6 -14
- package/lib/worker/abstract-worker.d.ts +2 -2
- package/lib/worker/cluster-worker.d.ts +1 -1
- package/lib/worker/thread-worker.d.ts +1 -1
- package/lib/worker/worker-options.d.ts +1 -1
- package/package.json +4 -4
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,r=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class a extends r{}const h=Object.freeze({full:"full",busy:"busy"}),u=Object.freeze((()=>{})),k={medRunTime:!1},c=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const l=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;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(r,t=k){this.pool=r,this.opts=t,this.checkOptions(this.opts),this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.medRunTime=!0)}}class p extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),t=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(t??0)})}}class m extends d{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class g extends d{reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class T extends d{nextWorkerNodeId=0;reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class w extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,r){super(e,r),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,t,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of i.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/i.cpus().length)}}class f{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=l.ROUND_ROBIN,t=k){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[l.ROUND_ROBIN,new T(e,t)],[l.LESS_USED,new g(e,t)],[l.LESS_BUSY,new m(e,t)],[l.FAIR_SHARE,new p(e,t)],[l.WEIGHTED_ROUND_ROBIN,new w(e,t)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType!==e&&(this.workerChoiceStrategyType=e),this.workerChoiceStrategies.get(this.workerChoiceStrategyType)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class W extends Array{size;constructor(e=1024,...r){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...r)}push(...e){const r=super.push(...e);return r>this.size&&super.splice(0,r-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const r=super.concat(e);return r.size=this.size,r.length>r.size&&r.splice(0,r.length-r.size),r}splice(e,r,...t){let s;return arguments.length>=3&&void 0!==r?(s=super.splice(e,r),this.push(...t)):s=2===arguments.length?super.splice(e,r):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let r=e;r<this.size;r++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class y{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode.bind(this),this.executeTask.bind(this),this.enqueueTask.bind(this),this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new a),this.workerChoiceStrategyContext=new f(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(this.opts.workerChoiceStrategy=e.workerChoiceStrategy??l.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??k,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue){if(e.tasksQueueOptions?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.tasksQueueOptions.concurrency}'`);this.opts.tasksQueueOptions={concurrency:e.tasksQueueOptions?.concurrency??1}}}checkValidWorkerChoiceStrategy(e){if(!Object.values(l).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,r)=>e+r.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,r)=>e+r.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[r,t]=this.chooseWorkerNode(),i={data:e??{},id:s.randomUUID()},o=new Promise(((e,r)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:r,worker:t.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[r].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(r,i):this.executeTask(r,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,r)=>{this.flushTasksQueue(r),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(t.runTimeHistory.push(r.runTime??0),t.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const r=e.slice().sort(((e,r)=>e-r)),t=Math.floor(r.length/2);return r.length%2==0?r[t/2]:(r[t-1]+r[t])/2})(t.runTimeHistory)))}chooseWorkerNode(){let r;if(this.type===e.DYNAMIC&&!this.full&&this.internalBusy()){const e=this.createAndSetupWorker();this.registerWorkerMessageListener(e,(r=>{var t;t=c.HARD,(r.kill===t||null!=r.kill&&0===this.getWorkerTasksUsage(e)?.running)&&(this.flushTasksQueueByWorker(e),this.destroyWorker(e))})),r=this.getWorkerNodeKey(e)}else r=this.workerChoiceStrategyContext.execute();return[r,this.workerNodes[r]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);if(null!=r){null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterTaskExecutionHook(r.worker,e),this.promiseResponseMap.delete(e.id);const t=this.getWorkerNodeKey(r.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(t)>0&&this.executeTask(t,this.dequeueTask(t))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(h.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(h.full))}setWorkerNodeTasksUsage(e,r){e.tasksUsage=r}getWorkerTasksUsage(e){const r=this.getWorkerNodeKey(e);if(-1!==r)return this.workerNodes[r].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,r,t,s){this.workerNodes[e]={worker:r,tasksUsage:t,tasksQueue:s}}removeWorkerNode(e){const r=this.getWorkerNodeKey(e);this.workerNodes.splice(r,1),this.workerChoiceStrategyContext.remove(r)}executeTask(e,r){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,r)}enqueueTask(e,r){return this.workerNodes[e].tasksQueue.push(r)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const r of this.workerNodes[e].tasksQueue)this.executeTask(e,r)}flushTasksQueueByWorker(e){const r=this.getWorkerNodeKey(e);this.flushTasksQueue(r)}}class N extends y{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.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 t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class S extends y{constructor(e,r,t={}){super(e,r,t)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new o.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const R=6e4,x=c.SOFT;class I extends n.AsyncResource{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:x,maxInactiveTime:R}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??R)/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??x,this.opts.maxInactiveTime=e.maxInactiveTime??R,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??R)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=performance.now(),s=e(r.data),i=performance.now()-t;this.sendToMainWorker({data:s,id:r.id,runTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,r){const t=performance.now();e(r.data).then((e=>{const s=performance.now()-t;return this.sendToMainWorker({data:e,id:r.id,runTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(u)}}exports.ClusterWorker=class extends I{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends N{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends S{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=N,exports.FixedThreadPool=S,exports.KillBehaviors=c,exports.PoolEvents=h,exports.ThreadWorker=class extends I{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=l;
|
|
1
|
+
"use strict";var e,t=require("node:events"),r=require("node:cluster"),s=require("node:crypto"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class a extends t{}const h=Object.freeze({full:"full",busy:"busy"}),u=Object.freeze((()=>{})),k={medRunTime:!1},c=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const l=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;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(t,r=k){this.pool=t,this.opts=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime)}setOptions(e){e=e??k,this.checkOptions(e),this.opts=e}}class p extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(r);const s=this.workerLastVirtualTaskTimestamp.get(r)?.end??0;s<t&&(t=s,e=r)}return e}remove(e){const t=this.workerLastVirtualTaskTimestamp.delete(e);for(const[t,r]of this.workerLastVirtualTaskTimestamp.entries())t>e&&this.workerLastVirtualTaskTimestamp.set(t-1,r);return t}computeWorkerLastVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),r=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:t,end:t+(r??0)})}}class m extends d{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(e){return!0}}class T extends d{constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(e){return!0}}class g extends d{nextWorkerNodeId=0;constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class w extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,t=k){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const t=this.workersTaskRunTime.get(e)?.runTime??0,r=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return t<r?this.setWorkerTaskRunTime(e,r,t+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,r,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const t=this.workersTaskRunTime.delete(e);for(const[t,r]of this.workersTaskRunTime)t>e&&this.workersTaskRunTime.set(t-1,r);return t}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,t,r){this.workersTaskRunTime.set(e,{weight:t,runTime:r})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const t of i.cpus()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/i.cpus().length)}}class f{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=l.ROUND_ROBIN,r=k){this.workerChoiceStrategy=t,this.execute.bind(this),this.workerChoiceStrategies=new Map([[l.ROUND_ROBIN,new g(e,r)],[l.LESS_USED,new T(e,r)],[l.LESS_BUSY,new m(e,r)],[l.FAIR_SHARE,new p(e,r)],[l.WEIGHTED_ROUND_ROBIN,new w(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){this.workerChoiceStrategies.forEach((t=>{t.setOptions(e)}))}}class W extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class y{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode.bind(this),this.executeTask.bind(this),this.enqueueTask.bind(this),this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new a),this.workerChoiceStrategyContext=new f(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(t){if(null==t)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(t))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(t<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===t)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??l.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??k,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(l).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidTasksQueueOptions(e){if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){if(!0===this.opts.enableTasksQueue&&!e)for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e);this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,r]=this.chooseWorkerNode(),i={data:e??{},id:s.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:r.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[t].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(t,i):this.executeTask(t,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.getWorkerTasksUsage(e);--r.running,++r.run,null!=t.error&&++r.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(r.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==r.run&&(r.avgRunTime=r.runTime/r.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(r.runTimeHistory.push(t.runTime??0),r.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),r=Math.floor(t.length/2);return t.length%2==0?t[r/2]:(t[r-1]+t[r])/2})(r.runTimeHistory)))}chooseWorkerNode(){let t;if(this.type===e.DYNAMIC&&!this.full&&this.internalBusy()){const e=this.createAndSetupWorker();this.registerWorkerMessageListener(e,(t=>{var r;r=c.HARD,(t.kill===r||null!=t.kill&&0===this.getWorkerTasksUsage(e)?.running)&&(this.flushTasksQueueByWorker(e),this.destroyWorker(e))})),t=this.getWorkerNodeKey(e)}else t=this.workerChoiceStrategyContext.execute();return[t,this.workerNodes[t]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(h.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(h.full))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}getWorkerTasksUsage(e){const t=this.getWorkerNodeKey(e);if(-1!==t)return this.workerNodes[t].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t)}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.push(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const t of this.workerNodes[e].tasksQueue)this.executeTask(e,t)}flushTasksQueueByWorker(e){const t=this.getWorkerNodeKey(e);this.flushTasksQueue(t)}}class N extends y{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class S extends y{constructor(e,t,r={}){super(e,t,r)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:t,port2:r}=new o.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const R=6e4,x=c.SOFT;class I extends n.AsyncResource{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:x,maxInactiveTime:R}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkFunctionInput(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??R)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,r)}))}messageListener(e,t){null!=e.id&&null!=e.data?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.run.bind(this),this,t,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??x,this.opts.maxInactiveTime=e.maxInactiveTime??R,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");if("AsyncFunction"===e.constructor.name&&!1===this.opts.async)throw new Error("fn parameter is an async function, please set the async option to true")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??R)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const r=performance.now(),s=e(t.data),i=performance.now()-r;this.sendToMainWorker({data:s,id:t.id,runTime:i})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now();e(t.data).then((e=>{const s=performance.now()-r;return this.sendToMainWorker({data:e,id:t.id,runTime:s}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(u)}}exports.ClusterWorker=class extends I{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 N{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends S{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=N,exports.FixedThreadPool=S,exports.KillBehaviors=c,exports.PoolEvents=h,exports.ThreadWorker=class extends I{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=l;
|
package/lib/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:events";import r from"node:cluster";import t from"node:crypto";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 u}from"node:async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));class c extends e{}const l=Object.freeze({full:"full",busy:"busy"}),d=Object.freeze((()=>{})),p={medRunTime:!1},m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const g=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 T{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,r=p){this.pool=e,this.opts=r,this.checkOptions(this.opts),this.isDynamicPool=this.pool.type===k.DYNAMIC,this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.medRunTime=!0)}}class w extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),t=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(t??0)})}}class f extends T{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class W extends T{reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class y extends T{nextWorkerNodeId=0;reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class N extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,r){super(e,r),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,t,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of s()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s().length)}}class S{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=g.ROUND_ROBIN,t=p){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new y(e,t)],[g.LESS_USED,new W(e,t)],[g.LESS_BUSY,new f(e,t)],[g.FAIR_SHARE,new w(e,t)],[g.WEIGHTED_ROUND_ROBIN,new N(e,t)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType!==e&&(this.workerChoiceStrategyType=e),this.workerChoiceStrategies.get(this.workerChoiceStrategyType)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class R extends Array{size;constructor(e=1024,...r){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...r)}push(...e){const r=super.push(...e);return r>this.size&&super.splice(0,r-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const r=super.concat(e);return r.size=this.size,r.length>r.size&&r.splice(0,r.length-r.size),r}splice(e,r,...t){let s;return arguments.length>=3&&void 0!==r?(s=super.splice(e,r),this.push(...t)):s=2===arguments.length?super.splice(e,r):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let r=e;r<this.size;r++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class I{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode.bind(this),this.executeTask.bind(this),this.enqueueTask.bind(this),this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new c),this.workerChoiceStrategyContext=new S(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(this.opts.workerChoiceStrategy=e.workerChoiceStrategy??g.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??p,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue){if(e.tasksQueueOptions?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.tasksQueueOptions.concurrency}'`);this.opts.tasksQueueOptions={concurrency:e.tasksQueueOptions?.concurrency??1}}}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,r)=>e+r.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,r)=>e+r.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[r,s]=this.chooseWorkerNode(),i={data:e??{},id:t.randomUUID()},o=new Promise(((e,r)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:r,worker:s.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[r].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(r,i):this.executeTask(r,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,r)=>{this.flushTasksQueue(r),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(t.runTimeHistory.push(r.runTime??0),t.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const r=e.slice().sort(((e,r)=>e-r)),t=Math.floor(r.length/2);return r.length%2==0?r[t/2]:(r[t-1]+r[t])/2})(t.runTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const r=this.createAndSetupWorker();this.registerWorkerMessageListener(r,(e=>{var t;t=m.HARD,(e.kill===t||null!=e.kill&&0===this.getWorkerTasksUsage(r)?.running)&&(this.flushTasksQueueByWorker(r),this.destroyWorker(r))})),e=this.getWorkerNodeKey(r)}else e=this.workerChoiceStrategyContext.execute();return[e,this.workerNodes[e]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??d),e.on("error",this.opts.errorHandler??d),e.on("online",this.opts.onlineHandler??d),e.on("exit",this.opts.exitHandler??d),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);if(null!=r){null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterTaskExecutionHook(r.worker,e),this.promiseResponseMap.delete(e.id);const t=this.getWorkerNodeKey(r.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(t)>0&&this.executeTask(t,this.dequeueTask(t))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(l.busy),this.type===k.DYNAMIC&&this.full&&this.emitter?.emit(l.full))}setWorkerNodeTasksUsage(e,r){e.tasksUsage=r}getWorkerTasksUsage(e){const r=this.getWorkerNodeKey(e);if(-1!==r)return this.workerNodes[r].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,r,t,s){this.workerNodes[e]={worker:r,tasksUsage:t,tasksQueue:s}}removeWorkerNode(e){const r=this.getWorkerNodeKey(e);this.workerNodes.splice(r,1),this.workerChoiceStrategyContext.remove(r)}executeTask(e,r){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,r)}enqueueTask(e,r){return this.workerNodes[e].tasksQueue.push(r)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const r of this.workerNodes[e].tasksQueue)this.executeTask(e,r)}flushTasksQueueByWorker(e){const r=this.getWorkerNodeKey(e);this.flushTasksQueue(r)}}class x extends I{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 k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class v extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class C extends I{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 k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class E extends C{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}const b=6e4,O=m.SOFT;class M extends u{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:O,maxInactiveTime:b}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??b)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){null!=e.data&&null!=e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??O,this.opts.maxInactiveTime=e.maxInactiveTime??b,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??b)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=performance.now(),s=e(r.data),i=performance.now()-t;this.sendToMainWorker({data:s,id:r.id,runTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,r){const t=performance.now();e(r.data).then((e=>{const s=performance.now()-t;return this.sendToMainWorker({data:e,id:r.id,runTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(d)}}class U extends M{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}}class D extends M{constructor(e,r={}){super("worker-thread-pool:poolifier",i,e,h,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{U as ClusterWorker,v as DynamicClusterPool,E as DynamicThreadPool,x as FixedClusterPool,C as FixedThreadPool,m as KillBehaviors,l as PoolEvents,D as ThreadWorker,g as WorkerChoiceStrategies};
|
|
1
|
+
import e from"node:events";import t from"node:cluster";import r from"node:crypto";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 u}from"node:async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));class c extends e{}const l=Object.freeze({full:"full",busy:"busy"}),d=Object.freeze((()=>{})),p={medRunTime:!1},m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const g=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 T{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){this.pool=e,this.opts=t,this.isDynamicPool=this.pool.type===k.DYNAMIC,this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime)}setOptions(e){e=e??p,this.checkOptions(e),this.opts=e}}class w extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(r);const s=this.workerLastVirtualTaskTimestamp.get(r)?.end??0;s<t&&(t=s,e=r)}return e}remove(e){const t=this.workerLastVirtualTaskTimestamp.delete(e);for(const[t,r]of this.workerLastVirtualTaskTimestamp.entries())t>e&&this.workerLastVirtualTaskTimestamp.set(t-1,r);return t}computeWorkerLastVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),r=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:t,end:t+(r??0)})}}class f extends T{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(e){return!0}}class W extends T{constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(e){return!0}}class y extends T{nextWorkerNodeId=0;constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class N extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,t=p){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const t=this.workersTaskRunTime.get(e)?.runTime??0,r=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return t<r?this.setWorkerTaskRunTime(e,r,t+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,r,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const t=this.workersTaskRunTime.delete(e);for(const[t,r]of this.workersTaskRunTime)t>e&&this.workersTaskRunTime.set(t-1,r);return t}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,t,r){this.workersTaskRunTime.set(e,{weight:t,runTime:r})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const t of s()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s().length)}}class S{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=g.ROUND_ROBIN,r=p){this.workerChoiceStrategy=t,this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new y(e,r)],[g.LESS_USED,new W(e,r)],[g.LESS_BUSY,new f(e,r)],[g.FAIR_SHARE,new w(e,r)],[g.WEIGHTED_ROUND_ROBIN,new N(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){this.workerChoiceStrategies.forEach((t=>{t.setOptions(e)}))}}class R extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class I{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode.bind(this),this.executeTask.bind(this),this.enqueueTask.bind(this),this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new c),this.workerChoiceStrategyContext=new S(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??g.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??p,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidTasksQueueOptions(e){if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){if(!0===this.opts.enableTasksQueue&&!e)for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e);this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,s]=this.chooseWorkerNode(),i={data:e??{},id:r.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:s.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[t].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(t,i):this.executeTask(t,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.getWorkerTasksUsage(e);--r.running,++r.run,null!=t.error&&++r.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(r.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==r.run&&(r.avgRunTime=r.runTime/r.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(r.runTimeHistory.push(t.runTime??0),r.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),r=Math.floor(t.length/2);return t.length%2==0?t[r/2]:(t[r-1]+t[r])/2})(r.runTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{var r;r=m.HARD,(e.kill===r||null!=e.kill&&0===this.getWorkerTasksUsage(t)?.running)&&(this.flushTasksQueueByWorker(t),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return[e,this.workerNodes[e]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??d),e.on("error",this.opts.errorHandler??d),e.on("online",this.opts.onlineHandler??d),e.on("exit",this.opts.exitHandler??d),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(l.busy),this.type===k.DYNAMIC&&this.full&&this.emitter?.emit(l.full))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}getWorkerTasksUsage(e){const t=this.getWorkerNodeKey(e);if(-1!==t)return this.workerNodes[t].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t)}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.push(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const t of this.workerNodes[e].tasksQueue)this.executeTask(e,t)}flushTasksQueueByWorker(e){const t=this.getWorkerNodeKey(e);this.flushTasksQueue(t)}}class O extends I{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class x extends O{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class v extends I{constructor(e,t,r={}){super(e,t,r)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:r}=new a;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class C extends v{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}const b=6e4,E=m.SOFT;class M extends u{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:E,maxInactiveTime:b}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkFunctionInput(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??b)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,r)}))}messageListener(e,t){null!=e.id&&null!=e.data?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.run.bind(this),this,t,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??E,this.opts.maxInactiveTime=e.maxInactiveTime??b,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function");if("AsyncFunction"===e.constructor.name&&!1===this.opts.async)throw new Error("fn parameter is an async function, please set the async option to true")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??b)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const r=performance.now(),s=e(t.data),i=performance.now()-r;this.sendToMainWorker({data:s,id:t.id,runTime:i})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now();e(t.data).then((e=>{const s=performance.now()-r;return this.sendToMainWorker({data:e,id:t.id,runTime:s}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(d)}}class Q extends M{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class U extends M{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,h,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{Q as ClusterWorker,x as DynamicClusterPool,C as DynamicThreadPool,O as FixedClusterPool,v as FixedThreadPool,m as KillBehaviors,l as PoolEvents,U as ThreadWorker,g as WorkerChoiceStrategies};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
|
|
2
|
-
import { type IPool, type PoolOptions, PoolType } from './pool';
|
|
2
|
+
import { type IPool, type PoolOptions, type TasksQueueOptions, PoolType } from './pool';
|
|
3
3
|
import { PoolEmitter } from './pool';
|
|
4
4
|
import type { IWorker, WorkerNode } from './worker';
|
|
5
|
-
import { type WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
|
|
5
|
+
import { type WorkerChoiceStrategy, type WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types';
|
|
6
6
|
import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context';
|
|
7
7
|
/**
|
|
8
8
|
* Base class that implements some shared logic for all poolifier pools.
|
|
9
9
|
*
|
|
10
10
|
* @typeParam Worker - Type of worker which manages this pool.
|
|
11
11
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
12
|
-
* @typeParam Response - Type of response
|
|
12
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
13
13
|
*/
|
|
14
14
|
export declare abstract class AbstractPool<Worker extends IWorker, Data = unknown, Response = unknown> implements IPool<Worker, Data, Response> {
|
|
15
15
|
readonly numberOfWorkers: number;
|
|
@@ -38,7 +38,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
38
38
|
* Constructs a new poolifier pool.
|
|
39
39
|
*
|
|
40
40
|
* @param numberOfWorkers - Number of workers that this pool should manage.
|
|
41
|
-
* @param filePath - Path to the worker
|
|
41
|
+
* @param filePath - Path to the worker file.
|
|
42
42
|
* @param opts - Options for the pool.
|
|
43
43
|
*/
|
|
44
44
|
constructor(numberOfWorkers: number, filePath: string, opts: PoolOptions<Worker>);
|
|
@@ -46,6 +46,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
46
46
|
private checkNumberOfWorkers;
|
|
47
47
|
private checkPoolOptions;
|
|
48
48
|
private checkValidWorkerChoiceStrategy;
|
|
49
|
+
private checkValidTasksQueueOptions;
|
|
49
50
|
/** @inheritDoc */
|
|
50
51
|
abstract get type(): PoolType;
|
|
51
52
|
/**
|
|
@@ -64,7 +65,14 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
64
65
|
*/
|
|
65
66
|
private getWorkerNodeKey;
|
|
66
67
|
/** @inheritDoc */
|
|
67
|
-
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
|
|
68
|
+
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy, workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions): void;
|
|
69
|
+
/** @inheritDoc */
|
|
70
|
+
setWorkerChoiceStrategyOptions(workerChoiceStrategyOptions: WorkerChoiceStrategyOptions): void;
|
|
71
|
+
/** @inheritDoc */
|
|
72
|
+
enableTasksQueue(enable: boolean, tasksQueueOptions?: TasksQueueOptions): void;
|
|
73
|
+
/** @inheritDoc */
|
|
74
|
+
setTasksQueueOptions(tasksQueueOptions: TasksQueueOptions): void;
|
|
75
|
+
private buildTasksQueueOptions;
|
|
68
76
|
/**
|
|
69
77
|
* Whether the pool is full or not.
|
|
70
78
|
*
|
|
@@ -174,6 +182,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
174
182
|
* Gets the given worker its tasks usage in the pool.
|
|
175
183
|
*
|
|
176
184
|
* @param worker - The worker.
|
|
185
|
+
* @throws Error if the worker is not found in the pool worker nodes.
|
|
177
186
|
* @returns The worker tasks usage.
|
|
178
187
|
*/
|
|
179
188
|
private getWorkerTasksUsage;
|
|
@@ -8,7 +8,7 @@ import { FixedClusterPool } from './fixed';
|
|
|
8
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
|
-
* @typeParam Response - Type of response
|
|
11
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
12
12
|
* @author [Christopher Quadflieg](https://github.com/Shinigami92)
|
|
13
13
|
* @since 2.0.0
|
|
14
14
|
*/
|
|
@@ -29,7 +29,7 @@ export interface ClusterPoolOptions extends PoolOptions<Worker> {
|
|
|
29
29
|
* This pool selects the workers in a round robin fashion.
|
|
30
30
|
*
|
|
31
31
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
32
|
-
* @typeParam Response - Type of response
|
|
32
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
33
33
|
* @author [Christopher Quadflieg](https://github.com/Shinigami92)
|
|
34
34
|
* @since 2.0.0
|
|
35
35
|
*/
|
package/lib/pools/pool.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './select
|
|
|
6
6
|
* Pool types.
|
|
7
7
|
*
|
|
8
8
|
* @enum
|
|
9
|
+
* @internal
|
|
9
10
|
*/
|
|
10
11
|
export declare enum PoolType {
|
|
11
12
|
/**
|
|
@@ -47,7 +48,7 @@ export interface TasksQueueOptions {
|
|
|
47
48
|
/**
|
|
48
49
|
* Options for a poolifier pool.
|
|
49
50
|
*
|
|
50
|
-
* @typeParam Worker -
|
|
51
|
+
* @typeParam Worker - Type of worker.
|
|
51
52
|
*/
|
|
52
53
|
export interface PoolOptions<Worker extends IWorker> {
|
|
53
54
|
/**
|
|
@@ -68,6 +69,8 @@ export interface PoolOptions<Worker extends IWorker> {
|
|
|
68
69
|
exitHandler?: ExitHandler<Worker>;
|
|
69
70
|
/**
|
|
70
71
|
* The worker choice strategy to use in this pool.
|
|
72
|
+
*
|
|
73
|
+
* @defaultValue WorkerChoiceStrategies.ROUND_ROBIN
|
|
71
74
|
*/
|
|
72
75
|
workerChoiceStrategy?: WorkerChoiceStrategy;
|
|
73
76
|
/**
|
|
@@ -83,14 +86,11 @@ export interface PoolOptions<Worker extends IWorker> {
|
|
|
83
86
|
/**
|
|
84
87
|
* Pool worker tasks queue.
|
|
85
88
|
*
|
|
86
|
-
* @experimental
|
|
87
89
|
* @defaultValue false
|
|
88
90
|
*/
|
|
89
91
|
enableTasksQueue?: boolean;
|
|
90
92
|
/**
|
|
91
93
|
* Pool worker tasks queue options.
|
|
92
|
-
*
|
|
93
|
-
* @experimental
|
|
94
94
|
*/
|
|
95
95
|
tasksQueueOptions?: TasksQueueOptions;
|
|
96
96
|
}
|
|
@@ -99,7 +99,7 @@ export interface PoolOptions<Worker extends IWorker> {
|
|
|
99
99
|
*
|
|
100
100
|
* @typeParam Worker - Type of worker which manages this pool.
|
|
101
101
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
102
|
-
* @typeParam Response - Type of response
|
|
102
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
103
103
|
*/
|
|
104
104
|
export interface IPool<Worker extends IWorker, Data = unknown, Response = unknown> {
|
|
105
105
|
/**
|
|
@@ -132,9 +132,9 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
|
|
|
132
132
|
*/
|
|
133
133
|
findFreeWorkerNodeKey: () => number;
|
|
134
134
|
/**
|
|
135
|
-
*
|
|
135
|
+
* Executes the function specified in the constructor with the task data input parameter.
|
|
136
136
|
*
|
|
137
|
-
* @param data - The input for the specified
|
|
137
|
+
* @param data - The task input data for the specified function. This can only be serializable data.
|
|
138
138
|
* @returns Promise that will be resolved when the task is successfully completed.
|
|
139
139
|
*/
|
|
140
140
|
execute: (data: Data) => Promise<Response>;
|
|
@@ -146,6 +146,26 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
|
|
|
146
146
|
* Sets the worker choice strategy in this pool.
|
|
147
147
|
*
|
|
148
148
|
* @param workerChoiceStrategy - The worker choice strategy.
|
|
149
|
+
* @param workerChoiceStrategyOptions - The worker choice strategy options.
|
|
150
|
+
*/
|
|
151
|
+
setWorkerChoiceStrategy: (workerChoiceStrategy: WorkerChoiceStrategy, workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions) => void;
|
|
152
|
+
/**
|
|
153
|
+
* Sets the worker choice strategy options in this pool.
|
|
154
|
+
*
|
|
155
|
+
* @param workerChoiceStrategyOptions - The worker choice strategy options.
|
|
156
|
+
*/
|
|
157
|
+
setWorkerChoiceStrategyOptions: (workerChoiceStrategyOptions: WorkerChoiceStrategyOptions) => void;
|
|
158
|
+
/**
|
|
159
|
+
* Enables/disables the worker tasks queue in this pool.
|
|
160
|
+
*
|
|
161
|
+
* @param enable - Whether to enable or disable the worker tasks queue.
|
|
162
|
+
* @param tasksQueueOptions - The worker tasks queue options.
|
|
163
|
+
*/
|
|
164
|
+
enableTasksQueue: (enable: boolean, tasksQueueOptions?: TasksQueueOptions) => void;
|
|
165
|
+
/**
|
|
166
|
+
* Sets the worker tasks queue options in this pool.
|
|
167
|
+
*
|
|
168
|
+
* @param tasksQueueOptions - The worker tasks queue options.
|
|
149
169
|
*/
|
|
150
|
-
|
|
170
|
+
setTasksQueueOptions: (tasksQueueOptions: TasksQueueOptions) => void;
|
|
151
171
|
}
|
|
@@ -6,11 +6,11 @@ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOpt
|
|
|
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
|
-
* @typeParam Response - Type of response
|
|
9
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
11
|
export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> implements IWorkerChoiceStrategy {
|
|
12
12
|
protected readonly pool: IPool<Worker, Data, Response>;
|
|
13
|
-
protected
|
|
13
|
+
protected opts: WorkerChoiceStrategyOptions;
|
|
14
14
|
/** @inheritDoc */
|
|
15
15
|
protected readonly isDynamicPool: boolean;
|
|
16
16
|
/** @inheritDoc */
|
|
@@ -22,11 +22,13 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
|
|
|
22
22
|
* @param opts - The worker choice strategy options.
|
|
23
23
|
*/
|
|
24
24
|
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
25
|
-
|
|
25
|
+
protected checkOptions(opts: WorkerChoiceStrategyOptions): void;
|
|
26
26
|
/** @inheritDoc */
|
|
27
27
|
abstract reset(): boolean;
|
|
28
28
|
/** @inheritDoc */
|
|
29
29
|
abstract choose(): number;
|
|
30
30
|
/** @inheritDoc */
|
|
31
31
|
abstract remove(workerNodeKey: number): boolean;
|
|
32
|
+
/** @inheritDoc */
|
|
33
|
+
setOptions(opts: WorkerChoiceStrategyOptions): void;
|
|
32
34
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import type { IPool } from '../pool';
|
|
1
2
|
import type { IWorker } from '../worker';
|
|
2
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
|
-
import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
|
|
4
|
+
import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
5
|
/**
|
|
5
6
|
* Selects the next worker with a fair share scheduling algorithm.
|
|
6
7
|
* Loosely modeled after the fair queueing algorithm: https://en.wikipedia.org/wiki/Fair_queuing.
|
|
7
8
|
*
|
|
8
9
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
9
10
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
10
|
-
* @typeParam Response - Type of response
|
|
11
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
11
12
|
*/
|
|
12
13
|
export declare class FairShareWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
14
|
/** @inheritDoc */
|
|
@@ -17,6 +18,8 @@ export declare class FairShareWorkerChoiceStrategy<Worker extends IWorker, Data
|
|
|
17
18
|
*/
|
|
18
19
|
private readonly workerLastVirtualTaskTimestamp;
|
|
19
20
|
/** @inheritDoc */
|
|
21
|
+
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
22
|
+
/** @inheritDoc */
|
|
20
23
|
reset(): boolean;
|
|
21
24
|
/** @inheritDoc */
|
|
22
25
|
choose(): number;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import type { IPool } from '../pool';
|
|
1
2
|
import type { IWorker } from '../worker';
|
|
2
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
|
-
import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
|
|
4
|
+
import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
5
|
/**
|
|
5
6
|
* Selects the less busy worker.
|
|
6
7
|
*
|
|
7
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
8
9
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
|
-
* @typeParam Response - Type of response
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
10
11
|
*/
|
|
11
12
|
export declare class LessBusyWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
13
|
/** @inheritDoc */
|
|
13
14
|
readonly requiredStatistics: RequiredStatistics;
|
|
14
15
|
/** @inheritDoc */
|
|
16
|
+
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
17
|
+
/** @inheritDoc */
|
|
15
18
|
reset(): boolean;
|
|
16
19
|
/** @inheritDoc */
|
|
17
20
|
choose(): number;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import type { IPool } from '../pool';
|
|
1
2
|
import type { IWorker } from '../worker';
|
|
2
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
|
-
import type { IWorkerChoiceStrategy } from './selection-strategies-types';
|
|
4
|
+
import type { IWorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
5
|
/**
|
|
5
6
|
* Selects the less used worker.
|
|
6
7
|
*
|
|
7
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
8
9
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
|
-
* @typeParam Response - Type of response
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
10
11
|
*/
|
|
11
12
|
export declare class LessUsedWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
|
+
/** @inheritDoc */
|
|
14
|
+
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
12
15
|
/** @inheritDoc */
|
|
13
16
|
reset(): boolean;
|
|
14
17
|
/** @inheritDoc */
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import type { IPool } from '../pool';
|
|
1
2
|
import type { IWorker } from '../worker';
|
|
2
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
3
|
-
import type { IWorkerChoiceStrategy } from './selection-strategies-types';
|
|
4
|
+
import type { IWorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
5
|
/**
|
|
5
6
|
* Selects the next worker in a round robin fashion.
|
|
6
7
|
*
|
|
7
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
8
9
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
|
-
* @typeParam Response - Type of response
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
10
11
|
*/
|
|
11
12
|
export declare class RoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
12
13
|
/**
|
|
@@ -14,6 +15,8 @@ export declare class RoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data
|
|
|
14
15
|
*/
|
|
15
16
|
private nextWorkerNodeId;
|
|
16
17
|
/** @inheritDoc */
|
|
18
|
+
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
19
|
+
/** @inheritDoc */
|
|
17
20
|
reset(): boolean;
|
|
18
21
|
/** @inheritDoc */
|
|
19
22
|
choose(): number;
|
|
@@ -33,11 +33,15 @@ export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies;
|
|
|
33
33
|
export interface WorkerChoiceStrategyOptions {
|
|
34
34
|
/**
|
|
35
35
|
* Use tasks median run time instead of average run time.
|
|
36
|
+
*
|
|
37
|
+
* @defaultValue false
|
|
36
38
|
*/
|
|
37
39
|
medRunTime?: boolean;
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
42
|
* Pool worker tasks usage statistics requirements.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
41
45
|
*/
|
|
42
46
|
export interface RequiredStatistics {
|
|
43
47
|
/**
|
|
@@ -75,4 +79,10 @@ export interface IWorkerChoiceStrategy {
|
|
|
75
79
|
* @param workerNodeKey - The worker node key.
|
|
76
80
|
*/
|
|
77
81
|
remove: (workerNodeKey: number) => boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Sets the worker choice strategy options.
|
|
84
|
+
*
|
|
85
|
+
* @param opts - The worker choice strategy options.
|
|
86
|
+
*/
|
|
87
|
+
setOptions: (opts: WorkerChoiceStrategyOptions) => void;
|
|
78
88
|
}
|
|
@@ -8,7 +8,7 @@ import type { IPool } from '../pool';
|
|
|
8
8
|
*
|
|
9
9
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
10
10
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
11
|
-
* @typeParam Response - Type of response
|
|
11
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
12
12
|
*/
|
|
13
13
|
export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
14
14
|
/** @inheritDoc */
|
|
@@ -25,12 +25,7 @@ export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IWork
|
|
|
25
25
|
* Workers' virtual task runtime.
|
|
26
26
|
*/
|
|
27
27
|
private readonly workersTaskRunTime;
|
|
28
|
-
/**
|
|
29
|
-
* Constructs a worker choice strategy that selects with a weighted round robin scheduling algorithm.
|
|
30
|
-
*
|
|
31
|
-
* @param pool - The pool instance.
|
|
32
|
-
* @param opts - The worker choice strategy options.
|
|
33
|
-
*/
|
|
28
|
+
/** @inheritDoc */
|
|
34
29
|
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
35
30
|
/** @inheritDoc */
|
|
36
31
|
reset(): boolean;
|
|
@@ -6,19 +6,19 @@ import type { RequiredStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOpti
|
|
|
6
6
|
*
|
|
7
7
|
* @typeParam Worker - Type of worker.
|
|
8
8
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
9
|
-
* @typeParam Response - Type of response
|
|
9
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
10
10
|
*/
|
|
11
11
|
export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data = unknown, Response = unknown> {
|
|
12
|
-
private
|
|
12
|
+
private workerChoiceStrategy;
|
|
13
13
|
private readonly workerChoiceStrategies;
|
|
14
14
|
/**
|
|
15
15
|
* Worker choice strategy context constructor.
|
|
16
16
|
*
|
|
17
17
|
* @param pool - The pool instance.
|
|
18
|
-
* @param
|
|
18
|
+
* @param workerChoiceStrategy - The worker choice strategy.
|
|
19
19
|
* @param opts - The worker choice strategy options.
|
|
20
20
|
*/
|
|
21
|
-
constructor(pool: IPool<Worker, Data, Response>,
|
|
21
|
+
constructor(pool: IPool<Worker, Data, Response>, workerChoiceStrategy?: WorkerChoiceStrategy, opts?: WorkerChoiceStrategyOptions);
|
|
22
22
|
/**
|
|
23
23
|
* Gets the worker choice strategy in the context required statistics.
|
|
24
24
|
*
|
|
@@ -44,4 +44,10 @@ export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data =
|
|
|
44
44
|
* @returns `true` if the removal is successful, `false` otherwise.
|
|
45
45
|
*/
|
|
46
46
|
remove(workerNodeKey: number): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Sets the worker choice strategies in the context options.
|
|
49
|
+
*
|
|
50
|
+
* @param opts - The worker choice strategy options.
|
|
51
|
+
*/
|
|
52
|
+
setOptions(opts: WorkerChoiceStrategyOptions): void;
|
|
47
53
|
}
|
|
@@ -9,7 +9,7 @@ import { FixedThreadPool } from './fixed';
|
|
|
9
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
|
-
* @typeParam Response - Type of response
|
|
12
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
13
13
|
* @author [Alessandro Pio Ardizio](https://github.com/pioardi)
|
|
14
14
|
* @since 0.0.1
|
|
15
15
|
*/
|
|
@@ -16,7 +16,7 @@ export type ThreadWorkerWithMessageChannel = Worker & Draft<MessageChannel>;
|
|
|
16
16
|
* This pool selects the threads in a round robin fashion.
|
|
17
17
|
*
|
|
18
18
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
19
|
-
* @typeParam Response - Type of response
|
|
19
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
20
20
|
* @author [Alessandro Pio Ardizio](https://github.com/pioardi)
|
|
21
21
|
* @since 0.0.1
|
|
22
22
|
*/
|
package/lib/pools/worker.d.ts
CHANGED
|
@@ -16,31 +16,54 @@ export type OnlineHandler<Worker extends IWorker> = (this: Worker) => void;
|
|
|
16
16
|
*/
|
|
17
17
|
export type ExitHandler<Worker extends IWorker> = (this: Worker, code: number) => void;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Message object that is passed as a task between main worker and worker.
|
|
20
20
|
*
|
|
21
21
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
22
22
|
* @internal
|
|
23
23
|
*/
|
|
24
24
|
export interface Task<Data = unknown> {
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Task input data that will be passed to the worker.
|
|
27
27
|
*/
|
|
28
|
-
data
|
|
28
|
+
readonly data?: Data;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* UUID of the message.
|
|
31
31
|
*/
|
|
32
|
-
id
|
|
32
|
+
readonly id?: string;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Worker tasks usage statistics.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
36
38
|
*/
|
|
37
39
|
export interface TasksUsage {
|
|
40
|
+
/**
|
|
41
|
+
* Number of tasks executed.
|
|
42
|
+
*/
|
|
38
43
|
run: number;
|
|
44
|
+
/**
|
|
45
|
+
* Number of tasks running.
|
|
46
|
+
*/
|
|
39
47
|
running: number;
|
|
48
|
+
/**
|
|
49
|
+
* Tasks runtime.
|
|
50
|
+
*/
|
|
40
51
|
runTime: number;
|
|
52
|
+
/**
|
|
53
|
+
* Tasks runtime history.
|
|
54
|
+
*/
|
|
41
55
|
runTimeHistory: CircularArray<number>;
|
|
56
|
+
/**
|
|
57
|
+
* Average tasks runtime.
|
|
58
|
+
*/
|
|
42
59
|
avgRunTime: number;
|
|
60
|
+
/**
|
|
61
|
+
* Median tasks runtime.
|
|
62
|
+
*/
|
|
43
63
|
medRunTime: number;
|
|
64
|
+
/**
|
|
65
|
+
* Number of tasks errored.
|
|
66
|
+
*/
|
|
44
67
|
error: number;
|
|
45
68
|
}
|
|
46
69
|
/**
|
|
@@ -73,7 +96,7 @@ export interface WorkerNode<Worker extends IWorker, Data = unknown> {
|
|
|
73
96
|
/**
|
|
74
97
|
* Worker node worker.
|
|
75
98
|
*/
|
|
76
|
-
worker: Worker;
|
|
99
|
+
readonly worker: Worker;
|
|
77
100
|
/**
|
|
78
101
|
* Worker node tasks usage statistics.
|
|
79
102
|
*/
|
|
@@ -81,5 +104,5 @@ export interface WorkerNode<Worker extends IWorker, Data = unknown> {
|
|
|
81
104
|
/**
|
|
82
105
|
* Worker node tasks queue.
|
|
83
106
|
*/
|
|
84
|
-
tasksQueue: Array<Task<Data>>;
|
|
107
|
+
readonly tasksQueue: Array<Task<Data>>;
|
|
85
108
|
}
|
package/lib/utility-types.d.ts
CHANGED
|
@@ -3,28 +3,23 @@
|
|
|
3
3
|
import type { Worker as ClusterWorker } from 'node:cluster';
|
|
4
4
|
import type { MessagePort } from 'node:worker_threads';
|
|
5
5
|
import type { KillBehavior } from './worker/worker-options';
|
|
6
|
-
import type { IWorker } from './pools/worker';
|
|
6
|
+
import type { IWorker, Task } from './pools/worker';
|
|
7
7
|
/**
|
|
8
8
|
* Make all properties in T non-readonly.
|
|
9
|
+
*
|
|
10
|
+
* @typeParam T - Type in which properties will be non-readonly.
|
|
9
11
|
*/
|
|
10
12
|
export type Draft<T> = {
|
|
11
13
|
-readonly [P in keyof T]?: T[P];
|
|
12
14
|
};
|
|
13
15
|
/**
|
|
14
|
-
* Message object that is passed between worker and
|
|
16
|
+
* Message object that is passed between main worker and worker.
|
|
15
17
|
*
|
|
16
18
|
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
17
19
|
* @typeParam MainWorker - Type of main worker.
|
|
20
|
+
* @internal
|
|
18
21
|
*/
|
|
19
|
-
export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker | MessagePort | unknown = unknown> {
|
|
20
|
-
/**
|
|
21
|
-
* Input data that will be passed to the worker.
|
|
22
|
-
*/
|
|
23
|
-
readonly data?: Data;
|
|
24
|
-
/**
|
|
25
|
-
* Id of the message.
|
|
26
|
-
*/
|
|
27
|
-
readonly id?: string;
|
|
22
|
+
export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker | MessagePort | unknown = unknown> extends Task<Data> {
|
|
28
23
|
/**
|
|
29
24
|
* Kill code.
|
|
30
25
|
*/
|
|
@@ -39,9 +34,6 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
|
|
|
39
34
|
readonly runTime?: number;
|
|
40
35
|
/**
|
|
41
36
|
* Reference to main worker.
|
|
42
|
-
*
|
|
43
|
-
* Only for internal use.
|
|
44
|
-
* @internal
|
|
45
37
|
*/
|
|
46
38
|
readonly parent?: MainWorker;
|
|
47
39
|
}
|
|
@@ -35,14 +35,14 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
35
35
|
* @param mainWorker - Reference to main worker.
|
|
36
36
|
* @param opts - Options for the worker.
|
|
37
37
|
*/
|
|
38
|
-
constructor(type: string, isMain: boolean, fn: (data: Data) => Response
|
|
38
|
+
constructor(type: string, isMain: boolean, fn: (data: Data) => Response | Promise<Response>, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
|
|
39
39
|
/**
|
|
40
40
|
* Worker message listener.
|
|
41
41
|
*
|
|
42
42
|
* @param message - Message received.
|
|
43
43
|
* @param fn - Function processed by the worker when the pool's `execution` function is invoked.
|
|
44
44
|
*/
|
|
45
|
-
protected messageListener(message: MessageValue<Data, MainWorker>, fn: (data: Data) => Response): void;
|
|
45
|
+
protected messageListener(message: MessageValue<Data, MainWorker>, fn: (data: Data) => Response | Promise<Response>): void;
|
|
46
46
|
private checkWorkerOptions;
|
|
47
47
|
/**
|
|
48
48
|
* Checks if the `fn` parameter is passed to the constructor.
|
|
@@ -24,7 +24,7 @@ export declare class ClusterWorker<Data = unknown, Response = unknown> extends A
|
|
|
24
24
|
* @param fn - Function processed by the worker when the pool's `execution` function is invoked.
|
|
25
25
|
* @param opts - Options for the worker.
|
|
26
26
|
*/
|
|
27
|
-
constructor(fn: (data: Data) => Response
|
|
27
|
+
constructor(fn: (data: Data) => Response | Promise<Response>, opts?: WorkerOptions);
|
|
28
28
|
/** @inheritDoc */
|
|
29
29
|
protected sendToMainWorker(message: MessageValue<Response>): void;
|
|
30
30
|
/** @inheritDoc */
|
|
@@ -24,7 +24,7 @@ export declare class ThreadWorker<Data = unknown, Response = unknown> extends Ab
|
|
|
24
24
|
* @param fn - Function processed by the worker when the pool's `execution` function is invoked.
|
|
25
25
|
* @param opts - Options for the worker.
|
|
26
26
|
*/
|
|
27
|
-
constructor(fn: (data: Data) => Response
|
|
27
|
+
constructor(fn: (data: Data) => Response | Promise<Response>, opts?: WorkerOptions);
|
|
28
28
|
/** @inheritDoc */
|
|
29
29
|
protected sendToMainWorker(message: MessageValue<Response>): void;
|
|
30
30
|
}
|
|
@@ -18,7 +18,7 @@ export type KillBehavior = keyof typeof KillBehaviors;
|
|
|
18
18
|
/**
|
|
19
19
|
* Detects whether the given value is a kill behavior or not.
|
|
20
20
|
*
|
|
21
|
-
* @typeParam KB - Which specific KillBehavior to test against.
|
|
21
|
+
* @typeParam KB - Which specific KillBehavior type to test against.
|
|
22
22
|
* @param killBehavior - Which kind of kill behavior to detect.
|
|
23
23
|
* @param value - Any value.
|
|
24
24
|
* @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poolifier",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.9",
|
|
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",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"lib"
|
|
74
74
|
],
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@commitlint/cli": "^17.
|
|
77
|
-
"@commitlint/config-conventional": "^17.
|
|
76
|
+
"@commitlint/cli": "^17.6.1",
|
|
77
|
+
"@commitlint/config-conventional": "^17.6.1",
|
|
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.1",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"eslint": "^8.38.0",
|
|
88
88
|
"eslint-config-standard": "^17.0.0",
|
|
89
89
|
"eslint-config-standard-with-typescript": "^34.0.1",
|
|
90
|
-
"eslint-define-config": "^1.
|
|
90
|
+
"eslint-define-config": "^1.18.0",
|
|
91
91
|
"eslint-import-resolver-typescript": "^3.5.5",
|
|
92
92
|
"eslint-plugin-import": "^2.27.5",
|
|
93
93
|
"eslint-plugin-jsdoc": "^41.1.1",
|