poolifier
Advanced tools
Comparing version 2.6.5 to 2.6.6
@@ -1,1 +0,1 @@ | ||
"use strict";var e=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),r=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),a=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),h=Object.freeze({cluster:"cluster",thread:"thread"});class u extends e{}const k=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),c=Object.freeze((()=>{})),l={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},d=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class p 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,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}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 w{items;offset;size;maxSize;constructor(){this.clear()}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}clear(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}}const T=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),f=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class W{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){this.opts=e??l,this.setTaskStatisticsRequirements(this.opts)}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.waitTime.median:this.pool.workerNodes[e].workerUsage.waitTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i.cpus().length)}}class y extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===f.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends W{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class x extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class N extends W{constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class v extends W{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class E extends W{strategyPolicy={useDynamicWorker:!0};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!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.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class I extends W{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=T.ROUND_ROBIN,s=l){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[T.ROUND_ROBIN,new(E.bind(this))(e,s)],[T.LEAST_USED,new(N.bind(this))(e,s)],[T.LEAST_BUSY,new(x.bind(this))(e,s)],[T.LEAST_ELU,new(v.bind(this))(e,s)],[T.FAIR_SHARE,new(y.bind(this))(e,s)],[T.WEIGHTED_ROUND_ROBIN,new(I.bind(this))(e,s)],[T.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class b{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!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=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}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 safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??T.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??l,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,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(T).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(f).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.queued),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.maxQueued),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const[e,t]of this.workerNodes.entries())this.setWorkerNodeTasksUsage(t,this.getWorkerUsage(e)),this.setWorkerStatistics(t.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}async execute(e,t){const i=r.performance.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].workerUsage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=d(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.performance.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=d(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=d(e.elu.idle.history),e.elu.active.median=d(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===n.dynamic&&!this.full&&this.internalBusy()}registerWorkerMessageListener(e,t){e.on("message",t)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,this.workerListener())}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(k.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=m.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(null!=this.emitter&&this.emitter.emit(k.taskError,e.taskError),t.reject(e.taskError.message)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(k.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(),tasksQueue:new w});const t=this.getWorkerNodeKey(e);return this.setWorkerNodeTasksUsage(this.workerNodes[t],this.getWorkerUsage(t)),this.workerNodes.length}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}tasksMaxQueueSize(e){return this.workerNodes[e].tasksQueue.maxSize}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e));this.workerNodes[e].tasksQueue.clear()}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){const t=e=>null!=e?this.tasksQueueSize(e):0,s=e=>null!=e?this.tasksMaxQueueSize(e):0;return{tasks:{executed:0,executing:0,get queued(){return t(e)},get maxQueued(){return s(e)},failed:0},runTime:{aggregate:0,average:0,median:0,history:new p},waitTime:{aggregate:0,average:0,median:0,history:new p},elu:{idle:{aggregate:0,average:0,median:0,history:new p},active:{aggregate:0,average:0,median:0,history:new p},utilization:0}}}}class C extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.on("disconnect",(()=>{e.kill()})),e.disconnect()}sendToWorker(e,t){e.send(t)}createWorker(){return t.fork(this.opts.env)}get type(){return n.fixed}get worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class O extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV,...this.opts.workerOptions})}get type(){return n.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const q="default",z=6e4,U=m.SOFT;class P extends a.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:U,maxInactiveTime:z}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??z)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??U,this.opts.maxInactiveTime=e.maxInactiveTime??z,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(q,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(q,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.statistics?this.statistics=e.statistics:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker not set");return this.mainWorker}checkAlive(){r.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??z)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())})).catch(c)}getTaskFunction(e){e=e??q;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return this.checkStatistics(),{timestamp:r.performance.now(),...this.statistics.elu&&{elu:r.performance.eventLoopUtilization()}}}endTaskPerformance(e){return this.checkStatistics(),{...e,...this.statistics.runTime&&{runTime:r.performance.now()-e.timestamp},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization(e.elu)}}}checkStatistics(){if(null==this.statistics)throw new Error("Performance statistics computation requirements not set")}}exports.ClusterWorker=class extends P{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends C{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends O{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=C,exports.FixedThreadPool=O,exports.KillBehaviors=m,exports.Measurements=f,exports.PoolEvents=k,exports.PoolTypes=n,exports.ThreadWorker=class extends P{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=T,exports.WorkerTypes=h; | ||
"use strict";var e=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),r=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),a=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),h=Object.freeze({cluster:"cluster",thread:"thread"});class u extends e{}const k=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),c=Object.freeze((()=>{})),l={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},d={aggregate:!1,average:!1,median:!1},m=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),p=Object.freeze({SOFT:"SOFT",HARD:"HARD"});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,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}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 T{items;offset;size;maxSize;constructor(){this.clear()}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}clear(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}}const f=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),W=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class y{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:d,waitTime:d,elu:d};constructor(e,t=l){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){this.opts=e??l,this.setTaskStatisticsRequirements(this.opts)}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].usage.runTime.median:this.pool.workerNodes[e].usage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].usage.waitTime.median:this.pool.workerNodes[e].usage.waitTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].usage.elu.active.median:this.pool.workerNodes[e].usage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i.cpus().length)}}class S extends y{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:d,elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===W.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class x extends y{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class N extends y{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:d};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.usage.runTime.aggregate+s.usage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class v extends y{constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.usage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class E extends y{taskStatisticsRequirements={runTime:d,waitTime:d,elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.usage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class I extends y{strategyPolicy={useDynamicWorker:!0};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!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.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class R extends y{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:d,elu:d};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class b{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=f.ROUND_ROBIN,s=l){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[f.ROUND_ROBIN,new(I.bind(this))(e,s)],[f.LEAST_USED,new(v.bind(this))(e,s)],[f.LEAST_BUSY,new(N.bind(this))(e,s)],[f.LEAST_ELU,new(E.bind(this))(e,s)],[f.FAIR_SHARE,new(S.bind(this))(e,s)],[f.WEIGHTED_ROUND_ROBIN,new(R.bind(this))(e,s)],[f.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(x.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class C{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!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=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new b(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}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 safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??f.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??l,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,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(f).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(W).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.usage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.usage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.queued),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.maxQueued),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const[e,t]of this.workerNodes.entries())this.setWorkerNodeTasksUsage(t,this.getWorkerUsage(e)),this.setWorkerStatistics(t.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.usage.tasks.executing))}async execute(e,t){const i=r.performance.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].usage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].usage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].usage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=m(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.performance.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=m(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=m(e.elu.idle.history),e.elu.active.median=m(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===n.dynamic&&!this.full&&this.internalBusy()}registerWorkerMessageListener(e,t){e.on("message",t)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,this.workerListener())}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(k.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=p.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].usage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].usage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(null!=this.emitter&&this.emitter.emit(k.taskError,e.taskError),t.reject(e.taskError.message)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(k.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.usage=t}pushWorkerNode(e){this.workerNodes.push({worker:e,usage:this.getWorkerUsage(),tasksQueue:new T});const t=this.getWorkerNodeKey(e);return this.setWorkerNodeTasksUsage(this.workerNodes[t],this.getWorkerUsage(t)),this.workerNodes.length}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}tasksMaxQueueSize(e){return this.workerNodes[e].tasksQueue.maxSize}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e));this.workerNodes[e].tasksQueue.clear()}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){const t=e=>null!=e?this.tasksQueueSize(e):0,s=e=>null!=e?this.tasksMaxQueueSize(e):0;return{tasks:{executed:0,executing:0,get queued(){return t(e)},get maxQueued(){return s(e)},failed:0},runTime:{aggregate:0,average:0,median:0,history:new w},waitTime:{aggregate:0,average:0,median:0,history:new w},elu:{idle:{aggregate:0,average:0,median:0,history:new w},active:{aggregate:0,average:0,median:0,history:new w},utilization:0}}}}class O extends C{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.on("disconnect",(()=>{e.kill()})),e.disconnect()}sendToWorker(e,t){e.send(t)}createWorker(){return t.fork(this.opts.env)}get type(){return n.fixed}get worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class q extends C{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV,...this.opts.workerOptions})}get type(){return n.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const z="default",P=6e4,A=p.SOFT;class Q extends a.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:A,maxInactiveTime:P}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??P)/2),this.checkAlive.bind(this)(),this.mainWorker?.on("message",this.messageListener.bind(this)))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??A,this.opts.maxInactiveTime=e.maxInactiveTime??P,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(z,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(z,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.statistics?this.statistics=e.statistics:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker not set");return this.mainWorker}checkAlive(){r.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??P)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())})).catch(c)}getTaskFunction(e){e=e??z;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return this.checkStatistics(),{timestamp:r.performance.now(),...this.statistics.elu&&{elu:r.performance.eventLoopUtilization()}}}endTaskPerformance(e){return this.checkStatistics(),{...e,...this.statistics.runTime&&{runTime:r.performance.now()-e.timestamp},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization(e.elu)}}}checkStatistics(){if(null==this.statistics)throw new Error("Performance statistics computation requirements not set")}}exports.ClusterWorker=class extends Q{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends O{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends q{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=O,exports.FixedThreadPool=q,exports.KillBehaviors=p,exports.Measurements=W,exports.PoolEvents=k,exports.PoolTypes=n,exports.ThreadWorker=class extends Q{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=f,exports.WorkerTypes=h,exports.availableParallelism=()=>{let e=1;try{e=i.availableParallelism()}catch{const t=i.cpus();Array.isArray(t)&&t.length>0&&(e=t.length)}return e}; |
@@ -21,1 +21,2 @@ export type { AbstractPool } from './pools/abstract-pool'; | ||
export type { Queue } from './queue'; | ||
export { availableParallelism } from './utils'; |
@@ -1,1 +0,1 @@ | ||
"use strict";var e=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),r=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),a=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),h=Object.freeze({cluster:"cluster",thread:"thread"});class u extends e{}const k=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),c=Object.freeze((()=>{})),l={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},d=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class p 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,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}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 w{items;offset;size;maxSize;constructor(){this.clear()}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}clear(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}}const T=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),f=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class W{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){this.opts=e??l,this.setTaskStatisticsRequirements(this.opts)}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.waitTime.median:this.pool.workerNodes[e].workerUsage.waitTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i.cpus().length)}}class y extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===f.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends W{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class x extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class N extends W{constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class v extends W{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class E extends W{strategyPolicy={useDynamicWorker:!0};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!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.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class I extends W{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=T.ROUND_ROBIN,s=l){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[T.ROUND_ROBIN,new(E.bind(this))(e,s)],[T.LEAST_USED,new(N.bind(this))(e,s)],[T.LEAST_BUSY,new(x.bind(this))(e,s)],[T.LEAST_ELU,new(v.bind(this))(e,s)],[T.FAIR_SHARE,new(y.bind(this))(e,s)],[T.WEIGHTED_ROUND_ROBIN,new(I.bind(this))(e,s)],[T.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class b{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!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=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}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 safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??T.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??l,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,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(T).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(f).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.queued),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.maxQueued),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const[e,t]of this.workerNodes.entries())this.setWorkerNodeTasksUsage(t,this.getWorkerUsage(e)),this.setWorkerStatistics(t.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}async execute(e,t){const i=r.performance.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].workerUsage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=d(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.performance.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=d(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=d(e.elu.idle.history),e.elu.active.median=d(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===n.dynamic&&!this.full&&this.internalBusy()}registerWorkerMessageListener(e,t){e.on("message",t)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,this.workerListener())}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(k.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=m.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(null!=this.emitter&&this.emitter.emit(k.taskError,e.taskError),t.reject(e.taskError.message)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(k.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(),tasksQueue:new w});const t=this.getWorkerNodeKey(e);return this.setWorkerNodeTasksUsage(this.workerNodes[t],this.getWorkerUsage(t)),this.workerNodes.length}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}tasksMaxQueueSize(e){return this.workerNodes[e].tasksQueue.maxSize}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e));this.workerNodes[e].tasksQueue.clear()}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){const t=e=>null!=e?this.tasksQueueSize(e):0,s=e=>null!=e?this.tasksMaxQueueSize(e):0;return{tasks:{executed:0,executing:0,get queued(){return t(e)},get maxQueued(){return s(e)},failed:0},runTime:{aggregate:0,average:0,median:0,history:new p},waitTime:{aggregate:0,average:0,median:0,history:new p},elu:{idle:{aggregate:0,average:0,median:0,history:new p},active:{aggregate:0,average:0,median:0,history:new p},utilization:0}}}}class C extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.on("disconnect",(()=>{e.kill()})),e.disconnect()}sendToWorker(e,t){e.send(t)}createWorker(){return t.fork(this.opts.env)}get type(){return n.fixed}get worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class O extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV,...this.opts.workerOptions})}get type(){return n.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const q="default",z=6e4,U=m.SOFT;class P extends a.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:U,maxInactiveTime:z}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??z)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??U,this.opts.maxInactiveTime=e.maxInactiveTime??z,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(q,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(q,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.statistics?this.statistics=e.statistics:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker not set");return this.mainWorker}checkAlive(){r.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??z)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())})).catch(c)}getTaskFunction(e){e=e??q;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return this.checkStatistics(),{timestamp:r.performance.now(),...this.statistics.elu&&{elu:r.performance.eventLoopUtilization()}}}endTaskPerformance(e){return this.checkStatistics(),{...e,...this.statistics.runTime&&{runTime:r.performance.now()-e.timestamp},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization(e.elu)}}}checkStatistics(){if(null==this.statistics)throw new Error("Performance statistics computation requirements not set")}}exports.ClusterWorker=class extends P{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends C{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends O{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=C,exports.FixedThreadPool=O,exports.KillBehaviors=m,exports.Measurements=f,exports.PoolEvents=k,exports.PoolTypes=n,exports.ThreadWorker=class extends P{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=T,exports.WorkerTypes=h; | ||
"use strict";var e=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),r=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),a=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),h=Object.freeze({cluster:"cluster",thread:"thread"});class u extends e{}const k=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),c=Object.freeze((()=>{})),l={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},d={aggregate:!1,average:!1,median:!1},m=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),p=Object.freeze({SOFT:"SOFT",HARD:"HARD"});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,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}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 T{items;offset;size;maxSize;constructor(){this.clear()}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}clear(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}}const f=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),W=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class y{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:d,waitTime:d,elu:d};constructor(e,t=l){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){this.opts=e??l,this.setTaskStatisticsRequirements(this.opts)}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].usage.runTime.median:this.pool.workerNodes[e].usage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].usage.waitTime.median:this.pool.workerNodes[e].usage.waitTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].usage.elu.active.median:this.pool.workerNodes[e].usage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i.cpus().length)}}class S extends y{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:d,elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===W.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class x extends y{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class N extends y{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:d};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.usage.runTime.aggregate+s.usage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class v extends y{constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.usage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class E extends y{taskStatisticsRequirements={runTime:d,waitTime:d,elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.usage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class I extends y{strategyPolicy={useDynamicWorker:!0};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!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.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class R extends y{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:d,elu:d};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class b{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=f.ROUND_ROBIN,s=l){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[f.ROUND_ROBIN,new(I.bind(this))(e,s)],[f.LEAST_USED,new(v.bind(this))(e,s)],[f.LEAST_BUSY,new(N.bind(this))(e,s)],[f.LEAST_ELU,new(E.bind(this))(e,s)],[f.FAIR_SHARE,new(S.bind(this))(e,s)],[f.WEIGHTED_ROUND_ROBIN,new(R.bind(this))(e,s)],[f.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(x.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class C{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!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=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new b(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}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 safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??f.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??l,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,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(f).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(W).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.usage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.usage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.queued),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.maxQueued),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.usage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const[e,t]of this.workerNodes.entries())this.setWorkerNodeTasksUsage(t,this.getWorkerUsage(e)),this.setWorkerStatistics(t.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.usage.tasks.executing))}async execute(e,t){const i=r.performance.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].usage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].usage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].usage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=m(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.performance.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=m(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=m(e.elu.idle.history),e.elu.active.median=m(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===n.dynamic&&!this.full&&this.internalBusy()}registerWorkerMessageListener(e,t){e.on("message",t)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,this.workerListener())}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(k.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=p.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].usage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].usage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(null!=this.emitter&&this.emitter.emit(k.taskError,e.taskError),t.reject(e.taskError.message)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(k.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.usage=t}pushWorkerNode(e){this.workerNodes.push({worker:e,usage:this.getWorkerUsage(),tasksQueue:new T});const t=this.getWorkerNodeKey(e);return this.setWorkerNodeTasksUsage(this.workerNodes[t],this.getWorkerUsage(t)),this.workerNodes.length}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}tasksMaxQueueSize(e){return this.workerNodes[e].tasksQueue.maxSize}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e));this.workerNodes[e].tasksQueue.clear()}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){const t=e=>null!=e?this.tasksQueueSize(e):0,s=e=>null!=e?this.tasksMaxQueueSize(e):0;return{tasks:{executed:0,executing:0,get queued(){return t(e)},get maxQueued(){return s(e)},failed:0},runTime:{aggregate:0,average:0,median:0,history:new w},waitTime:{aggregate:0,average:0,median:0,history:new w},elu:{idle:{aggregate:0,average:0,median:0,history:new w},active:{aggregate:0,average:0,median:0,history:new w},utilization:0}}}}class O extends C{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.on("disconnect",(()=>{e.kill()})),e.disconnect()}sendToWorker(e,t){e.send(t)}createWorker(){return t.fork(this.opts.env)}get type(){return n.fixed}get worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class q extends C{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV,...this.opts.workerOptions})}get type(){return n.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const z="default",P=6e4,A=p.SOFT;class Q extends a.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:A,maxInactiveTime:P}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??P)/2),this.checkAlive.bind(this)(),this.mainWorker?.on("message",this.messageListener.bind(this)))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??A,this.opts.maxInactiveTime=e.maxInactiveTime??P,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(z,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(z,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.statistics?this.statistics=e.statistics:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker not set");return this.mainWorker}checkAlive(){r.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??P)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())})).catch(c)}getTaskFunction(e){e=e??z;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return this.checkStatistics(),{timestamp:r.performance.now(),...this.statistics.elu&&{elu:r.performance.eventLoopUtilization()}}}endTaskPerformance(e){return this.checkStatistics(),{...e,...this.statistics.runTime&&{runTime:r.performance.now()-e.timestamp},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization(e.elu)}}}checkStatistics(){if(null==this.statistics)throw new Error("Performance statistics computation requirements not set")}}exports.ClusterWorker=class extends Q{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends O{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends q{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=O,exports.FixedThreadPool=q,exports.KillBehaviors=p,exports.Measurements=W,exports.PoolEvents=k,exports.PoolTypes=n,exports.ThreadWorker=class extends Q{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=f,exports.WorkerTypes=h,exports.availableParallelism=()=>{let e=1;try{e=i.availableParallelism()}catch{const t=i.cpus();Array.isArray(t)&&t.length>0&&(e=t.length)}return e}; |
@@ -72,3 +72,3 @@ import type { MessageValue, PromiseResponseWrapper } from '../utility-types'; | ||
* @param worker - The worker. | ||
* @returns The worker node key if the worker is found in the pool worker nodes, `-1` otherwise. | ||
* @returns The worker node key if found in the pool worker nodes, `-1` otherwise. | ||
*/ | ||
@@ -75,0 +75,0 @@ private getWorkerNodeKey; |
@@ -160,3 +160,3 @@ import type { CircularArray } from '../circular-array'; | ||
*/ | ||
workerUsage: WorkerUsage; | ||
usage: WorkerUsage; | ||
/** | ||
@@ -163,0 +163,0 @@ * Worker node tasks queue. |
@@ -65,3 +65,3 @@ /// <reference types="node" /> | ||
/** | ||
* Whether to compute the given statistics or not. | ||
* Whether the worker computes the given statistics or not. | ||
*/ | ||
@@ -68,0 +68,0 @@ readonly statistics?: WorkerStatistics; |
@@ -1,2 +0,2 @@ | ||
import type { WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types'; | ||
import type { MeasurementStatisticsRequirements, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types'; | ||
/** | ||
@@ -11,2 +11,10 @@ * An intentional empty function. | ||
/** | ||
* Default measurement statistics requirements. | ||
*/ | ||
export declare const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS: MeasurementStatisticsRequirements; | ||
/** | ||
* Safe helper to get the host OS optimized maximum pool size. | ||
*/ | ||
export declare const availableParallelism: () => number; | ||
/** | ||
* Compute the median of the given data set. | ||
@@ -13,0 +21,0 @@ * |
{ | ||
"name": "poolifier", | ||
"version": "2.6.5", | ||
"version": "2.6.6", | ||
"description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation", | ||
@@ -86,3 +86,3 @@ "license": "MIT", | ||
"@rollup/plugin-terser": "^0.4.3", | ||
"@rollup/plugin-typescript": "^11.1.1", | ||
"@rollup/plugin-typescript": "^11.1.2", | ||
"@types/node": "^20.3.2", | ||
@@ -95,7 +95,7 @@ "@typescript-eslint/eslint-plugin": "^5.60.1", | ||
"eslint-config-standard": "^17.1.0", | ||
"eslint-config-standard-with-typescript": "^35.0.0", | ||
"eslint-config-standard-with-typescript": "^36.0.0", | ||
"eslint-define-config": "^1.21.0", | ||
"eslint-import-resolver-typescript": "^3.5.5", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-jsdoc": "^46.3.0", | ||
"eslint-plugin-jsdoc": "^46.4.3", | ||
"eslint-plugin-n": "^16.0.1", | ||
@@ -107,3 +107,3 @@ "eslint-plugin-promise": "^6.1.1", | ||
"husky": "^8.0.3", | ||
"lint-staged": "^13.2.2", | ||
"lint-staged": "^13.2.3", | ||
"microtime": "^3.1.1", | ||
@@ -114,3 +114,3 @@ "mocha": "^10.2.0", | ||
"release-it": "^15.11.0", | ||
"rollup": "^3.25.3", | ||
"rollup": "^3.26.0", | ||
"rollup-plugin-analyzer": "^4.0.0", | ||
@@ -123,3 +123,3 @@ "rollup-plugin-command": "^1.1.3", | ||
"typedoc": "^0.24.8", | ||
"typescript": "^5.1.3" | ||
"typescript": "^5.1.6" | ||
}, | ||
@@ -126,0 +126,0 @@ "scripts": { |
@@ -86,3 +86,3 @@ <div align="center"> | ||
The second implementation is a dynamic worker pool with a number of worker started at creation time (these workers will be always active and reused) and other workers created when the load will increase (with an upper limit, these workers will be reused when active), the new created workers will be stopped after a configurable period of inactivity. | ||
You have to implement your worker extending the ThreadWorker or ClusterWorker class. | ||
You have to implement your worker by extending the ThreadWorker or ClusterWorker class. | ||
@@ -118,25 +118,31 @@ ## Installation | ||
'use strict' | ||
const { DynamicThreadPool, FixedThreadPool, PoolEvents } = require('poolifier') | ||
const { DynamicThreadPool, FixedThreadPool, PoolEvents, availableParallelism } = require('poolifier') | ||
// a fixed worker-threads pool | ||
const pool = new FixedThreadPool(15, | ||
'./yourWorker.js', | ||
{ errorHandler: (e) => console.error(e), onlineHandler: () => console.log('worker is online') }) | ||
const pool = new FixedThreadPool(availableParallelism(), './yourWorker.js', { | ||
errorHandler: e => console.error(e), | ||
onlineHandler: () => console.info('worker is online') | ||
}) | ||
pool.emitter.on(PoolEvents.busy, () => console.log('Pool is busy')) | ||
pool.emitter.on(PoolEvents.busy, () => console.info('Pool is busy')) | ||
// or a dynamic worker-threads pool | ||
const pool = new DynamicThreadPool(10, 100, | ||
'./yourWorker.js', | ||
{ errorHandler: (e) => console.error(e), onlineHandler: () => console.log('worker is online') }) | ||
const pool = new DynamicThreadPool(availableParallelism() / 2, availableParallelism(), './yourWorker.js', { | ||
errorHandler: e => console.error(e), | ||
onlineHandler: () => console.info('worker is online') | ||
}) | ||
pool.emitter.on(PoolEvents.full, () => console.log('Pool is full')) | ||
pool.emitter.on(PoolEvents.busy, () => console.log('Pool is busy')) | ||
pool.emitter.on(PoolEvents.full, () => console.info('Pool is full')) | ||
pool.emitter.on(PoolEvents.busy, () => console.info('Pool is busy')) | ||
// the execute method signature is the same for both implementations, | ||
// so you can easy switch from one to another | ||
pool.execute({}).then(res => { | ||
console.log(res) | ||
}).catch .... | ||
pool | ||
.execute({}) | ||
.then(res => { | ||
console.info(res) | ||
}) | ||
.catch(err => { | ||
console.error(err) | ||
}) | ||
``` | ||
@@ -143,0 +149,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2099
315
174061