poolifier 2.3.8 → 2.3.10-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("cluster"),r=require("events"),t=require("os"),s=require("worker_threads"),o=require("async_hooks");const i=Object.freeze((()=>{})),n={};var a;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(a||(a={}));const h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class k extends r{}const c=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class u{pool;isDynamicPool;requiredStatistics={runTime:!1};constructor(e){this.pool=e,this.isDynamicPool=this.pool.type===a.DYNAMIC}}class l extends u{requiredStatistics={runTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const t of this.pool.workers){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.getWorkerAverageTasksRunTime(e)??0)})}}class p extends u{reset(){return!0}choose(){let e,r=1/0;for(const t of this.pool.workers){const s=this.pool.getWorkerRunningTasks(t);if(!this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class g extends u{nextWorkerIndex=0;reset(){return this.nextWorkerIndex=0,!0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.nextWorkerIndex===this.pool.workers.length-1?0:this.nextWorkerIndex+1,e}}class m extends u{requiredStatistics={runTime:!0};currentWorkerIndex=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerIndex=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.pool.workers[this.currentWorkerIndex];this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerIndex=this.currentWorkerIndex===this.pool.workers.length-1?0:this.currentWorkerIndex+1,this.setWorkerTaskRunTime(this.pool.workers[this.currentWorkerIndex],t,0)),e}initWorkersTaskRunTime(){for(const e of this.pool.workers)this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.getWorkerAverageTasksRunTime(e)}computeWorkerWeight(){let e=0;for(const r of t.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/t.cpus().length)}}function T(e,r=c.ROUND_ROBIN){switch(r){case c.ROUND_ROBIN:return new g(e);case c.LESS_RECENTLY_USED:return new p(e);case c.FAIR_SHARE:return new l(e);case c.WEIGHTED_ROUND_ROBIN:return new m(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class W extends u{createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=c.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=T(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return!1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback()}}class w{pool;createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=c.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(e=c.ROUND_ROBIN){return this.pool.type===a.DYNAMIC?new W(this.pool,this.createDynamicallyWorkerCallback,e):T(this.pool,e)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class d{numberOfWorkers;filePath;opts;workers=[];workersTasksUsage=new Map;emitter;promiseMap=new Map;nextMessageId=0;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new k),this.workerChoiceStrategyContext=new w(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=h.HARD,(r.kill===t||0===this.getWorkerRunningTasks(e))&&this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||0===e.length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===a.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??c.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseMap.size}getWorkerIndex(e){return this.workers.indexOf(e)}getWorkerRunningTasks(e){return this.workersTasksUsage.get(e)?.running}getWorkerAverageTasksRunTime(e){return this.workersTasksUsage.get(e)?.avgRunTime}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const e of this.workers)this.resetWorkerTasksUsage(e);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorker()}findFreeWorker(){for(const e of this.workers)if(0===this.getWorkerRunningTasks(e))return e;return!1}async execute(e){const r=this.chooseWorker(),t=this.internalExecute(r,this.nextMessageId);return this.checkAndEmitBusy(),this.sendToWorker(r,{data:e??n,id:this.nextMessageId}),++this.nextMessageId,t}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e)})))}setupHook(){}beforePromiseWorkerResponseHook(e){this.increaseWorkerRunningTasks(e)}afterPromiseWorkerResponseHook(e,r){this.decreaseWorkerRunningTasks(r.worker),this.stepWorkerRunTasks(r.worker,1),this.updateWorkerTasksRunTime(r.worker,e.taskRunTime)}removeWorker(e){this.workers.splice(this.getWorkerIndex(e),1),this.removeWorkerTasksUsage(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??i),e.on("error",this.opts.errorHandler??i),e.on("online",this.opts.onlineHandler??i),e.on("exit",this.opts.exitHandler??i),e.once("exit",(()=>{this.removeWorker(e)})),this.workers.push(e),this.initWorkerTasksUsage(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseMap.get(e.id);void 0!==r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseWorkerResponseHook(e,r),this.promiseMap.delete(e.id))}}}async internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),await new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.running=t.running+r,this.workersTasksUsage.set(e,t)}}stepWorkerRunTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.run=t.run+r,this.workersTasksUsage.set(e,t)}}updateWorkerTasksRunTime(e,r){if(this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime&&this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}checkWorkerTasksUsage(e){const r=this.workersTasksUsage.has(e);if(!r)throw new Error("Worker could not be found in workers tasks usage map");return r}initWorkerTasksUsage(e){this.workersTasksUsage.set(e,{run:0,running:0,runTime:0,avgRunTime:0})}removeWorkerTasksUsage(e){this.workersTasksUsage.delete(e)}resetWorkerTasksUsage(e){this.removeWorkerTasksUsage(e),this.initWorkerTasksUsage(e)}}class y extends d{opts;constructor(e,r,t=n){super(e,r,t),this.opts=t}setupHook(){e.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return e.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return e.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return a.FIXED}get busy(){return this.internalGetBusyStatus()}}class f extends d{constructor(e,r,t={}){super(e,r,t)}isMain(){return s.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new s.Worker(this.filePath,{env:s.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new s.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return a.FIXED}get busy(){return this.internalGetBusyStatus()}}const R=6e4,x=h.SOFT;class S extends o.AsyncResource{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:x,maxInactiveTime:R}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),r||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??R)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??x,this.opts.maxInactiveTime=e.maxInactiveTime??R,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??R)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(i)}}exports.ClusterWorker=class extends S{constructor(r,t=n){super("worker-cluster-pool:poolifier",e.isPrimary,r,e.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends y{max;constructor(e,r,t,s=n){super(e,t,s),this.max=r}get type(){return a.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends f{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return a.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=y,exports.FixedThreadPool=f,exports.KillBehaviors=h,exports.ThreadWorker=class extends S{constructor(e,r=n){super("worker-thread-pool:poolifier",s.isMainThread,e,s.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=c;
1
+ "use strict";var e,r=require("cluster"),t=require("events"),s=require("os"),o=require("worker_threads"),i=require("async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));const n=Object.freeze((()=>{})),a=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class h extends t{}const k=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class c{pool;isDynamicPool;requiredStatistics={runTime:!1};constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC}}class u extends c{requiredStatistics={runTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const t of this.pool.workers){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.getWorkerAverageTasksRunTime(e)??0)})}}class l extends c{reset(){return!0}choose(){let e,r=1/0;for(const t of this.pool.workers){const s=this.pool.getWorkerRunningTasks(t);if(!this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class p extends c{nextWorkerIndex=0;reset(){return this.nextWorkerIndex=0,!0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.nextWorkerIndex===this.pool.workers.length-1?0:this.nextWorkerIndex+1,e}}class g extends c{requiredStatistics={runTime:!0};currentWorkerIndex=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerIndex=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.pool.workers[this.currentWorkerIndex];this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerIndex=this.currentWorkerIndex===this.pool.workers.length-1?0:this.currentWorkerIndex+1,this.setWorkerTaskRunTime(this.pool.workers[this.currentWorkerIndex],t,0)),e}initWorkersTaskRunTime(){for(const e of this.pool.workers)this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.getWorkerAverageTasksRunTime(e)}computeWorkerWeight(){let e=0;for(const r of s.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s.cpus().length)}}function m(e,r=k.ROUND_ROBIN){switch(r){case k.ROUND_ROBIN:return new p(e);case k.LESS_RECENTLY_USED:return new l(e);case k.FAIR_SHARE:return new u(e);case k.WEIGHTED_ROUND_ROBIN:return new g(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class T extends c{createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=k.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=m(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return!1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback()}}class W{pool;createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=k.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(r=k.ROUND_ROBIN){return this.pool.type===e.DYNAMIC?new T(this.pool,this.createDynamicallyWorkerCallback,r):m(this.pool,r)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class w{numberOfWorkers;filePath;opts;workers=[];workersTasksUsage=new Map;emitter;promiseMap=new Map;nextMessageId=0;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new h),this.workerChoiceStrategyContext=new W(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=a.HARD,(r.kill===t||0===this.getWorkerRunningTasks(e))&&this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||0===e.length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??k.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseMap.size}getWorkerIndex(e){return this.workers.indexOf(e)}getWorkerRunningTasks(e){return this.workersTasksUsage.get(e)?.running}getWorkerAverageTasksRunTime(e){return this.workersTasksUsage.get(e)?.avgRunTime}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const e of this.workers)this.resetWorkerTasksUsage(e);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorker()}findFreeWorker(){for(const e of this.workers)if(0===this.getWorkerRunningTasks(e))return e;return!1}async execute(e){const r=this.chooseWorker(),t=this.internalExecute(r,this.nextMessageId);return this.checkAndEmitBusy(),this.sendToWorker(r,{data:e??{},id:this.nextMessageId}),++this.nextMessageId,t}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e)})))}setupHook(){}beforePromiseWorkerResponseHook(e){this.increaseWorkerRunningTasks(e)}afterPromiseWorkerResponseHook(e,r){this.decreaseWorkerRunningTasks(r.worker),this.stepWorkerRunTasks(r.worker,1),this.updateWorkerTasksRunTime(r.worker,e.taskRunTime)}removeWorker(e){this.workers.splice(this.getWorkerIndex(e),1),this.removeWorkerTasksUsage(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??n),e.on("error",this.opts.errorHandler??n),e.on("online",this.opts.onlineHandler??n),e.on("exit",this.opts.exitHandler??n),e.once("exit",(()=>{this.removeWorker(e)})),this.workers.push(e),this.initWorkerTasksUsage(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseMap.get(e.id);void 0!==r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseWorkerResponseHook(e,r),this.promiseMap.delete(e.id))}}}async internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),await new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.running=t.running+r,this.workersTasksUsage.set(e,t)}}stepWorkerRunTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.run=t.run+r,this.workersTasksUsage.set(e,t)}}updateWorkerTasksRunTime(e,r){if(this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime&&this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}checkWorkerTasksUsage(e){const r=this.workersTasksUsage.has(e);if(!r)throw new Error("Worker could not be found in workers tasks usage map");return r}initWorkerTasksUsage(e){this.workersTasksUsage.set(e,{run:0,running:0,runTime:0,avgRunTime:0})}removeWorkerTasksUsage(e){this.workersTasksUsage.delete(e)}resetWorkerTasksUsage(e){this.removeWorkerTasksUsage(e),this.initWorkerTasksUsage(e)}}class d extends w{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}class y extends w{constructor(e,r,t={}){super(e,r,t)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new o.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}const f=6e4,R=a.SOFT;class x extends i.AsyncResource{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:R,maxInactiveTime:f}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),r||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??f)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??R,this.opts.maxInactiveTime=e.maxInactiveTime??f,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??f)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(n)}}exports.ClusterWorker=class extends x{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends d{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends y{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=d,exports.FixedThreadPool=y,exports.KillBehaviors=a,exports.ThreadWorker=class extends x{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=k;
package/lib/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import e from"cluster";import r from"events";import{cpus as t}from"os";import{isMainThread as s,Worker as o,SHARE_ENV as i,MessageChannel as n,parentPort as a}from"worker_threads";import{AsyncResource as h}from"async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));const c=Object.freeze((()=>{})),u=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class l extends r{}const p=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class m{pool;isDynamicPool;requiredStatistics={runTime:!1};constructor(e){this.pool=e,this.isDynamicPool=this.pool.type===k.DYNAMIC}}class g extends m{requiredStatistics={runTime:!0};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const t of this.pool.workers){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(this.pool.getWorkerAverageTasksRunTime(e)??0)})}}class T extends m{reset(){return!0}choose(){let e,r=1/0;for(const t of this.pool.workers){const s=this.pool.getWorkerRunningTasks(t);if(!this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class W extends m{nextWorkerIndex=0;reset(){return this.nextWorkerIndex=0,!0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.nextWorkerIndex===this.pool.workers.length-1?0:this.nextWorkerIndex+1,e}}class w extends m{requiredStatistics={runTime:!0};currentWorkerIndex=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e){super(e),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerIndex=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.pool.workers[this.currentWorkerIndex];this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerIndex=this.currentWorkerIndex===this.pool.workers.length-1?0:this.currentWorkerIndex+1,this.setWorkerTaskRunTime(this.pool.workers[this.currentWorkerIndex],t,0)),e}initWorkersTaskRunTime(){for(const e of this.pool.workers)this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.getWorkerAverageTasksRunTime(e)}computeWorkerWeight(){let e=0;for(const r of t()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/t().length)}}function d(e,r=p.ROUND_ROBIN){switch(r){case p.ROUND_ROBIN:return new W(e);case p.LESS_RECENTLY_USED:return new T(e);case p.FAIR_SHARE:return new g(e);case p.WEIGHTED_ROUND_ROBIN:return new w(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class y extends m{createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=p.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=d(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return!1!==e?e:this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback()}}class f{pool;createDynamicallyWorkerCallback;workerChoiceStrategy;constructor(e,r,t=p.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(e=p.ROUND_ROBIN){return this.pool.type===k.DYNAMIC?new y(this.pool,this.createDynamicallyWorkerCallback,e):d(this.pool,e)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class R{numberOfWorkers;filePath;opts;workers=[];workersTasksUsage=new Map;emitter;promiseMap=new Map;nextMessageId=0;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new l),this.workerChoiceStrategyContext=new f(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=u.HARD,(r.kill===t||0===this.getWorkerRunningTasks(e))&&this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(null==e||0===e.length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??p.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseMap.size}getWorkerIndex(e){return this.workers.indexOf(e)}getWorkerRunningTasks(e){return this.workersTasksUsage.get(e)?.running}getWorkerAverageTasksRunTime(e){return this.workersTasksUsage.get(e)?.avgRunTime}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const e of this.workers)this.resetWorkerTasksUsage(e);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorker()}findFreeWorker(){for(const e of this.workers)if(0===this.getWorkerRunningTasks(e))return e;return!1}async execute(e){const r=this.chooseWorker(),t=this.internalExecute(r,this.nextMessageId);return this.checkAndEmitBusy(),this.sendToWorker(r,{data:e??{},id:this.nextMessageId}),++this.nextMessageId,t}async destroy(){await Promise.all(this.workers.map((async e=>{await this.destroyWorker(e)})))}setupHook(){}beforePromiseWorkerResponseHook(e){this.increaseWorkerRunningTasks(e)}afterPromiseWorkerResponseHook(e,r){this.decreaseWorkerRunningTasks(r.worker),this.stepWorkerRunTasks(r.worker,1),this.updateWorkerTasksRunTime(r.worker,e.taskRunTime)}removeWorker(e){this.workers.splice(this.getWorkerIndex(e),1),this.removeWorkerTasksUsage(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorker(e)})),this.workers.push(e),this.initWorkerTasksUsage(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseMap.get(e.id);void 0!==r&&(null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseWorkerResponseHook(e,r),this.promiseMap.delete(e.id))}}}async internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),await new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}checkAndEmitBusy(){!0===this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.running=t.running+r,this.workersTasksUsage.set(e,t)}}stepWorkerRunTasks(e,r){if(this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.run=t.run+r,this.workersTasksUsage.set(e,t)}}updateWorkerTasksRunTime(e,r){if(this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime&&this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}checkWorkerTasksUsage(e){const r=this.workersTasksUsage.has(e);if(!r)throw new Error("Worker could not be found in workers tasks usage map");return r}initWorkerTasksUsage(e){this.workersTasksUsage.set(e,{run:0,running:0,runTime:0,avgRunTime:0})}removeWorkerTasksUsage(e){this.workersTasksUsage.delete(e)}resetWorkerTasksUsage(e){this.removeWorkerTasksUsage(e),this.initWorkerTasksUsage(e)}}class x extends R{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){e.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return e.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return e.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get busy(){return this.internalGetBusyStatus()}}class I extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get busy(){return this.workers.length===this.max}}class S extends R{constructor(e,r,t={}){super(e,r,t)}isMain(){return s}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o(this.filePath,{env:i})}afterWorkerSetup(e){const{port1:r,port2:t}=new n;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get busy(){return this.internalGetBusyStatus()}}class v extends S{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get busy(){return this.workers.length===this.max}}const C=6e4,D=u.SOFT;class E extends h{mainWorker;lastTaskTimestamp;aliveInterval;opts;constructor(e,r,t,s,o={killBehavior:D,maxInactiveTime:C}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),r||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??C)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??D,this.opts.maxInactiveTime=e.maxInactiveTime??C,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??C)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(c)}}class M extends E{constructor(r,t={}){super("worker-cluster-pool:poolifier",e.isPrimary,r,e.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class O extends E{constructor(e,r={}){super("worker-thread-pool:poolifier",s,e,a,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{M as ClusterWorker,I as DynamicClusterPool,v as DynamicThreadPool,x as FixedClusterPool,S as FixedThreadPool,u as KillBehaviors,O as ThreadWorker,p as WorkerChoiceStrategies};
package/lib/utils.d.ts CHANGED
@@ -2,7 +2,3 @@
2
2
  * An intentional empty function.
3
3
  */
4
4
  export declare const EMPTY_FUNCTION: () => void;
5
- /**
6
- * An intentional empty object literal.
7
- */
8
- export declare const EMPTY_OBJECT_LITERAL: {};
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.3.8",
3
+ "version": "2.3.10-1",
4
4
  "description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "require": "lib/index.js",
10
+ "import": "lib/index.mjs"
11
+ }
12
+ },
7
13
  "scripts": {
8
14
  "prepare": "node prepare.js",
9
15
  "build": "rollup --config --environment BUILD:development",
@@ -98,7 +104,7 @@
98
104
  "eslint": "^8.36.0",
99
105
  "eslint-config-standard": "^17.0.0",
100
106
  "eslint-config-standard-with-typescript": "^34.0.1",
101
- "eslint-define-config": "^1.16.0",
107
+ "eslint-define-config": "^1.17.0",
102
108
  "eslint-import-resolver-typescript": "^3.5.3",
103
109
  "eslint-plugin-import": "^2.27.5",
104
110
  "eslint-plugin-jsdoc": "^40.0.3",