poolifier 2.3.2 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -146,7 +146,7 @@ Remember that workers can only send and receive serializable data.
146
146
 
147
147
  ## Node versions
148
148
 
149
- Bode node versions >= 16.x are supported.
149
+ Node versions >= 16.x are supported.
150
150
 
151
151
  ## API
152
152
 
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import EventEmitter from "events";
3
- import { Worker } from "cluster";
3
+ import { ClusterSettings, Worker } from "cluster";
4
4
  import { Worker as ClusterWorker } from "cluster";
5
5
  import { MessagePort, MessageChannel } from "worker_threads";
6
6
  import { Worker as Worker$0 } from "worker_threads";
@@ -624,6 +624,12 @@ interface ClusterPoolOptions extends PoolOptions<Worker> {
624
624
  */
625
625
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
626
626
  env?: any;
627
+ /**
628
+ * Cluster settings.
629
+ *
630
+ * @see https://nodejs.org/api/cluster.html#cluster_cluster_settings
631
+ */
632
+ settings?: ClusterSettings;
627
633
  }
628
634
  /**
629
635
  * A cluster pool with a fixed number of workers.
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,r=require("events"),t=require("cluster"),s=require("os"),o=require("worker_threads"),i=require("async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class n extends r{}const a=()=>{},h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});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 u{constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.requiredStatistics={runTime:!1}}}class c extends u{constructor(){super(...arguments),this.requiredStatistics={runTime:!0},this.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),t=r+(this.pool.getWorkerAverageTasksRunTime(e)??0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:t})}}class l 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(!1===this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class p extends u{constructor(){super(...arguments),this.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 u{constructor(e){super(e),this.requiredStatistics={runTime:!0},this.currentWorkerIndex=0,this.workersTaskRunTime=new Map,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];!0===this.isDynamicPool&&!1===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)}}class m{static getWorkerChoiceStrategy(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 c(e);case k.WEIGHTED_ROUND_ROBIN:return new g(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}}class W extends u{constructor(e,r,t=k.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=m.getWorkerChoiceStrategy(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return e||(!0===this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback())}}class T{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 W(this.pool,this.createDynamicallyWorkerCallback,r):m.getWorkerChoiceStrategy(this.pool,r)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class d{constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,this.workers=[],this.workersTasksUsage=new Map,this.promiseMap=new Map,this.nextMessageId=0,!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();this.opts.enableEvents&&(this.emitter=new n),this.workerChoiceStrategyContext=new T(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(!e)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(!1===Number.isSafeInteger(r))throw new Error("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new Error("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}execute(e){const r=this.chooseWorker(),t=++this.nextMessageId,s=this.internalExecute(r,t);return this.checkAndEmitBusy(),e=e??{},this.sendToWorker(r,{data:e,id:t}),s}async destroy(){await Promise.all(this.workers.map((e=>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()}internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??a),e.on("error",this.opts.errorHandler??a),e.on("online",this.opts.onlineHandler??a),e.on("exit",this.opts.exitHandler??a),e.once("exit",(()=>this.removeWorker(e))),this.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&&(this.afterPromiseWorkerResponseHook(e,r),e.error?r.reject(e.error):r.resolve(e.data),this.promiseMap.delete(e.id))}}}checkAndEmitBusy(){this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.running=t.running+r,this.workersTasksUsage.set(e,t)}stepWorkerRunTasks(e,r){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.run=t.run+r,this.workersTasksUsage.set(e,t)}updateWorkerTasksRunTime(e,r){if(!0===this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}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 w extends d{constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){t.setupPrimary({exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}class y extends d{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 R=h.SOFT;class f extends i.AsyncResource{constructor(e,r,t,s,o={killBehavior:R,maxInactiveTime:6e4}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),!1===r&&(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??6e4)/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?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&&(this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??R,this.opts.maxInactiveTime=e.maxInactiveTime??6e4,this.opts.async=!!e.async}checkFunctionInput(e){if(!e)throw new Error("fn parameter is mandatory")}getMainWorker(){if(!this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??6e4)&&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(a)}}exports.AbstractWorker=f,exports.ClusterWorker=class extends f{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends w{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{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=w,exports.FixedThreadPool=y,exports.KillBehaviors=h,exports.ThreadWorker=class extends f{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=k;
1
+ "use strict";var e,r=require("events"),t=require("cluster"),s=require("os"),o=require("worker_threads"),i=require("async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class n extends r{}const a=()=>{},h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});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 u{constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.requiredStatistics={runTime:!1}}}class c extends u{constructor(){super(...arguments),this.requiredStatistics={runTime:!0},this.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),t=r+(this.pool.getWorkerAverageTasksRunTime(e)??0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:t})}}class l 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(!1===this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class p extends u{constructor(){super(...arguments),this.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 u{constructor(e){super(e),this.requiredStatistics={runTime:!0},this.currentWorkerIndex=0,this.workersTaskRunTime=new Map,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];!0===this.isDynamicPool&&!1===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)}}class m{static getWorkerChoiceStrategy(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 c(e);case k.WEIGHTED_ROUND_ROBIN:return new g(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}}class W extends u{constructor(e,r,t=k.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=m.getWorkerChoiceStrategy(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return e||(!0===this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback())}}class T{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 W(this.pool,this.createDynamicallyWorkerCallback,r):m.getWorkerChoiceStrategy(this.pool,r)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class d{constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,this.workers=[],this.workersTasksUsage=new Map,this.promiseMap=new Map,this.nextMessageId=0,!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();this.opts.enableEvents&&(this.emitter=new n),this.workerChoiceStrategyContext=new T(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(!e)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(!1===Number.isSafeInteger(r))throw new Error("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new Error("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}execute(e){const r=this.chooseWorker(),t=++this.nextMessageId,s=this.internalExecute(r,t);return this.checkAndEmitBusy(),e=e??{},this.sendToWorker(r,{data:e,id:t}),s}async destroy(){await Promise.all(this.workers.map((e=>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()}internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??a),e.on("error",this.opts.errorHandler??a),e.on("online",this.opts.onlineHandler??a),e.on("exit",this.opts.exitHandler??a),e.once("exit",(()=>this.removeWorker(e))),this.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&&(this.afterPromiseWorkerResponseHook(e,r),e.error?r.reject(e.error):r.resolve(e.data),this.promiseMap.delete(e.id))}}}checkAndEmitBusy(){this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.running=t.running+r,this.workersTasksUsage.set(e,t)}stepWorkerRunTasks(e,r){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.run=t.run+r,this.workersTasksUsage.set(e,t)}updateWorkerTasksRunTime(e,r){if(!0===this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}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 w extends d{constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}class y extends d{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 R=h.SOFT;class f extends i.AsyncResource{constructor(e,r,t,s,o={killBehavior:R,maxInactiveTime:6e4}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),!1===r&&(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??6e4)/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?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&&(this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??R,this.opts.maxInactiveTime=e.maxInactiveTime??6e4,this.opts.async=!!e.async}checkFunctionInput(e){if(!e)throw new Error("fn parameter is mandatory")}getMainWorker(){if(!this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??6e4)&&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(a)}}exports.AbstractWorker=f,exports.ClusterWorker=class extends f{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends w{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{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=w,exports.FixedThreadPool=y,exports.KillBehaviors=h,exports.ThreadWorker=class extends f{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=k;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -64,7 +64,7 @@
64
64
  "lib"
65
65
  ],
66
66
  "devDependencies": {
67
- "@types/node": "^18.8.5",
67
+ "@types/node": "^18.11.0",
68
68
  "@typescript-eslint/eslint-plugin": "^5.40.0",
69
69
  "@typescript-eslint/parser": "^5.40.0",
70
70
  "benchmark": "^2.1.4",
@@ -89,7 +89,7 @@
89
89
  "prettier": "^2.7.1",
90
90
  "prettier-plugin-organize-imports": "^3.1.1",
91
91
  "prettierx": "^0.18.3",
92
- "rollup": "^3.1.0",
92
+ "rollup": "^3.2.0",
93
93
  "rollup-plugin-analyzer": "^4.0.0",
94
94
  "rollup-plugin-command": "^1.1.3",
95
95
  "rollup-plugin-delete": "^2.0.0",