poolifier 2.3.7 → 2.3.8

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,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=()=>{},a={},h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class k extends t{}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{constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.requiredStatistics={runTime:!1}}}class l 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);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{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 T 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];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=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 T(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}class W extends u{constructor(e,r,t=c.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 d{constructor(e,r,t=c.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(r=c.ROUND_ROBIN){return this.pool.type===e.DYNAMIC?new W(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{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();!0===this.opts.enableEvents&&(this.emitter=new k),this.workerChoiceStrategyContext=new d(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(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??c.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.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??a,id:this.nextMessageId}),++this.nextMessageId,t}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()}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 y extends w{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 R 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=h.SOFT;class x extends i.AsyncResource{constructor(e,r,t,s,o={killBehavior:f,maxInactiveTime:6e4}){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??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?!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??f,this.opts.maxInactiveTime=e.maxInactiveTime??6e4,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??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(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 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.DynamicThreadPool=class extends R{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=y,exports.FixedThreadPool=R,exports.KillBehaviors=h,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=c;
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;
@@ -0,0 +1,219 @@
1
+ import type { MessageValue, PromiseWorkerResponseWrapper } from '../utility-types';
2
+ import type { PoolOptions } from './pool';
3
+ import { PoolEmitter } from './pool';
4
+ import type { IPoolInternal, TasksUsage } from './pool-internal';
5
+ import { PoolType } from './pool-internal';
6
+ import type { IPoolWorker } from './pool-worker';
7
+ import { type WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
8
+ import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context';
9
+ /**
10
+ * Base class that implements some shared logic for all poolifier pools.
11
+ *
12
+ * @typeParam Worker - Type of worker which manages this pool.
13
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
14
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
15
+ */
16
+ export declare abstract class AbstractPool<Worker extends IPoolWorker, Data = unknown, Response = unknown> implements IPoolInternal<Worker, Data, Response> {
17
+ readonly numberOfWorkers: number;
18
+ readonly filePath: string;
19
+ readonly opts: PoolOptions<Worker>;
20
+ /** {@inheritDoc} */
21
+ readonly workers: Worker[];
22
+ /** {@inheritDoc} */
23
+ readonly workersTasksUsage: Map<Worker, TasksUsage>;
24
+ /** {@inheritDoc} */
25
+ readonly emitter?: PoolEmitter;
26
+ /**
27
+ * The promise map.
28
+ *
29
+ * - `key`: This is the message Id of each submitted task.
30
+ * - `value`: An object that contains the worker, the resolve function and the reject function.
31
+ *
32
+ * When we receive a message from the worker we get a map entry and resolve/reject the promise based on the message.
33
+ */
34
+ protected promiseMap: Map<number, PromiseWorkerResponseWrapper<Worker, Response>>;
35
+ /**
36
+ * Id of the next message.
37
+ */
38
+ protected nextMessageId: number;
39
+ /**
40
+ * Worker choice strategy instance implementing the worker choice algorithm.
41
+ *
42
+ * Default to a strategy implementing a round robin algorithm.
43
+ */
44
+ protected workerChoiceStrategyContext: WorkerChoiceStrategyContext<Worker, Data, Response>;
45
+ /**
46
+ * Constructs a new poolifier pool.
47
+ *
48
+ * @param numberOfWorkers - Number of workers that this pool should manage.
49
+ * @param filePath - Path to the worker-file.
50
+ * @param opts - Options for the pool.
51
+ */
52
+ constructor(numberOfWorkers: number, filePath: string, opts: PoolOptions<Worker>);
53
+ private checkFilePath;
54
+ private checkNumberOfWorkers;
55
+ private checkPoolOptions;
56
+ /** {@inheritDoc} */
57
+ abstract get type(): PoolType;
58
+ /** {@inheritDoc} */
59
+ get numberOfRunningTasks(): number;
60
+ /** {@inheritDoc} */
61
+ getWorkerIndex(worker: Worker): number;
62
+ /** {@inheritDoc} */
63
+ getWorkerRunningTasks(worker: Worker): number | undefined;
64
+ /** {@inheritDoc} */
65
+ getWorkerAverageTasksRunTime(worker: Worker): number | undefined;
66
+ /** {@inheritDoc} */
67
+ setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
68
+ /** {@inheritDoc} */
69
+ abstract get busy(): boolean;
70
+ protected internalGetBusyStatus(): boolean;
71
+ /** {@inheritDoc} */
72
+ findFreeWorker(): Worker | false;
73
+ /** {@inheritDoc} */
74
+ execute(data: Data): Promise<Response>;
75
+ /** {@inheritDoc} */
76
+ destroy(): Promise<void>;
77
+ /**
78
+ * Shutdowns given worker.
79
+ *
80
+ * @param worker - A worker within `workers`.
81
+ */
82
+ protected abstract destroyWorker(worker: Worker): void | Promise<void>;
83
+ /**
84
+ * Setup hook that can be overridden by a Poolifier pool implementation
85
+ * to run code before workers are created in the abstract constructor.
86
+ */
87
+ protected setupHook(): void;
88
+ /**
89
+ * Should return whether the worker is the main worker or not.
90
+ */
91
+ protected abstract isMain(): boolean;
92
+ /**
93
+ * Hook executed before the worker task promise resolution.
94
+ * Can be overridden.
95
+ *
96
+ * @param worker - The worker.
97
+ */
98
+ protected beforePromiseWorkerResponseHook(worker: Worker): void;
99
+ /**
100
+ * Hook executed after the worker task promise resolution.
101
+ * Can be overridden.
102
+ *
103
+ * @param message - The received message.
104
+ * @param promise - The Promise response.
105
+ */
106
+ protected afterPromiseWorkerResponseHook(message: MessageValue<Response>, promise: PromiseWorkerResponseWrapper<Worker, Response>): void;
107
+ /**
108
+ * Removes the given worker from the pool.
109
+ *
110
+ * @param worker - The worker that will be removed.
111
+ */
112
+ protected removeWorker(worker: Worker): void;
113
+ /**
114
+ * Chooses a worker for the next task.
115
+ *
116
+ * The default implementation uses a round robin algorithm to distribute the load.
117
+ *
118
+ * @returns Worker.
119
+ */
120
+ protected chooseWorker(): Worker;
121
+ /**
122
+ * Sends a message to the given worker.
123
+ *
124
+ * @param worker - The worker which should receive the message.
125
+ * @param message - The message.
126
+ */
127
+ protected abstract sendToWorker(worker: Worker, message: MessageValue<Data>): void;
128
+ /**
129
+ * Registers a listener callback on a given worker.
130
+ *
131
+ * @param worker - The worker which should register a listener.
132
+ * @param listener - The message listener callback.
133
+ */
134
+ protected abstract registerWorkerMessageListener<Message extends Data | Response>(worker: Worker, listener: (message: MessageValue<Message>) => void): void;
135
+ /**
136
+ * Returns a newly created worker.
137
+ */
138
+ protected abstract createWorker(): Worker;
139
+ /**
140
+ * Function that can be hooked up when a worker has been newly created and moved to the workers registry.
141
+ *
142
+ * Can be used to update the `maxListeners` or binding the `main-worker`\<-\>`worker` connection if not bind by default.
143
+ *
144
+ * @param worker - The newly created worker.
145
+ */
146
+ protected abstract afterWorkerSetup(worker: Worker): void;
147
+ /**
148
+ * Creates a new worker for this pool and sets it up completely.
149
+ *
150
+ * @returns New, completely set up worker.
151
+ */
152
+ protected createAndSetupWorker(): Worker;
153
+ /**
154
+ * This function is the listener registered for each worker.
155
+ *
156
+ * @returns The listener function to execute when a message is received from a worker.
157
+ */
158
+ protected workerListener(): (message: MessageValue<Response>) => void;
159
+ private internalExecute;
160
+ private checkAndEmitBusy;
161
+ /**
162
+ * Increases the number of tasks that the given worker has applied.
163
+ *
164
+ * @param worker - Worker which running tasks is increased.
165
+ */
166
+ private increaseWorkerRunningTasks;
167
+ /**
168
+ * Decreases the number of tasks that the given worker has applied.
169
+ *
170
+ * @param worker - Worker which running tasks is decreased.
171
+ */
172
+ private decreaseWorkerRunningTasks;
173
+ /**
174
+ * Steps the number of tasks that the given worker has applied.
175
+ *
176
+ * @param worker - Worker which running tasks are stepped.
177
+ * @param step - Number of running tasks step.
178
+ */
179
+ private stepWorkerRunningTasks;
180
+ /**
181
+ * Steps the number of tasks that the given worker has run.
182
+ *
183
+ * @param worker - Worker which has run tasks.
184
+ * @param step - Number of run tasks step.
185
+ */
186
+ private stepWorkerRunTasks;
187
+ /**
188
+ * Updates tasks runtime for the given worker.
189
+ *
190
+ * @param worker - Worker which run the task.
191
+ * @param taskRunTime - Worker task runtime.
192
+ */
193
+ private updateWorkerTasksRunTime;
194
+ /**
195
+ * Checks if the given worker is registered in the workers tasks usage map.
196
+ *
197
+ * @param worker - Worker to check.
198
+ * @returns `true` if the worker is registered in the workers tasks usage map. `false` otherwise.
199
+ */
200
+ private checkWorkerTasksUsage;
201
+ /**
202
+ * Initializes tasks usage statistics.
203
+ *
204
+ * @param worker - The worker.
205
+ */
206
+ private initWorkerTasksUsage;
207
+ /**
208
+ * Removes worker tasks usage statistics.
209
+ *
210
+ * @param worker - The worker.
211
+ */
212
+ private removeWorkerTasksUsage;
213
+ /**
214
+ * Resets worker tasks usage statistics.
215
+ *
216
+ * @param worker - The worker.
217
+ */
218
+ private resetWorkerTasksUsage;
219
+ }
@@ -0,0 +1,30 @@
1
+ import { PoolType } from '../pool-internal';
2
+ import type { ClusterPoolOptions } from './fixed';
3
+ import { FixedClusterPool } from './fixed';
4
+ /**
5
+ * A cluster pool with a dynamic number of workers, but a guaranteed minimum number of workers.
6
+ *
7
+ * This cluster pool creates new workers when the others are busy, up to the maximum number of workers.
8
+ * When the maximum number of workers is reached, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
9
+ *
10
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
11
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
12
+ * @author [Christopher Quadflieg](https://github.com/Shinigami92)
13
+ * @since 2.0.0
14
+ */
15
+ export declare class DynamicClusterPool<Data = unknown, Response = unknown> extends FixedClusterPool<Data, Response> {
16
+ protected readonly max: number;
17
+ /**
18
+ * Constructs a new poolifier dynamic cluster pool.
19
+ *
20
+ * @param min - Minimum number of workers which are always active.
21
+ * @param max - Maximum number of workers that can be created by this pool.
22
+ * @param filePath - Path to an implementation of a `ClusterWorker` file, which can be relative or absolute.
23
+ * @param opts - Options for this dynamic cluster pool.
24
+ */
25
+ constructor(min: number, max: number, filePath: string, opts?: ClusterPoolOptions);
26
+ /** {@inheritDoc} */
27
+ get type(): PoolType;
28
+ /** {@inheritDoc} */
29
+ get busy(): boolean;
30
+ }
@@ -0,0 +1,64 @@
1
+ /// <reference types="node" />
2
+ import type { ClusterSettings, Worker } from 'cluster';
3
+ import type { MessageValue } from '../../utility-types';
4
+ import { AbstractPool } from '../abstract-pool';
5
+ import type { PoolOptions } from '../pool';
6
+ import { PoolType } from '../pool-internal';
7
+ /**
8
+ * Options for a poolifier cluster pool.
9
+ */
10
+ export interface ClusterPoolOptions extends PoolOptions<Worker> {
11
+ /**
12
+ * Key/value pairs to add to worker process environment.
13
+ *
14
+ * @see https://nodejs.org/api/cluster.html#cluster_cluster_fork_env
15
+ */
16
+ env?: any;
17
+ /**
18
+ * Cluster settings.
19
+ *
20
+ * @see https://nodejs.org/api/cluster.html#cluster_cluster_settings
21
+ */
22
+ settings?: ClusterSettings;
23
+ }
24
+ /**
25
+ * A cluster pool with a fixed number of workers.
26
+ *
27
+ * It is possible to perform tasks in sync or asynchronous mode as you prefer.
28
+ *
29
+ * This pool selects the workers in a round robin fashion.
30
+ *
31
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
32
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
33
+ * @author [Christopher Quadflieg](https://github.com/Shinigami92)
34
+ * @since 2.0.0
35
+ */
36
+ export declare class FixedClusterPool<Data = unknown, Response = unknown> extends AbstractPool<Worker, Data, Response> {
37
+ readonly opts: ClusterPoolOptions;
38
+ /**
39
+ * Constructs a new poolifier fixed cluster pool.
40
+ *
41
+ * @param numberOfWorkers - Number of workers for this pool.
42
+ * @param filePath - Path to an implementation of a `ClusterWorker` file, which can be relative or absolute.
43
+ * @param opts - Options for this fixed cluster pool.
44
+ */
45
+ constructor(numberOfWorkers: number, filePath: string, opts?: ClusterPoolOptions);
46
+ /** {@inheritDoc} */
47
+ protected setupHook(): void;
48
+ /** {@inheritDoc} */
49
+ protected isMain(): boolean;
50
+ /** {@inheritDoc} */
51
+ destroyWorker(worker: Worker): void;
52
+ /** {@inheritDoc} */
53
+ protected sendToWorker(worker: Worker, message: MessageValue<Data>): void;
54
+ /** {@inheritDoc} */
55
+ registerWorkerMessageListener<Message extends Data | Response>(worker: Worker, listener: (message: MessageValue<Message>) => void): void;
56
+ /** {@inheritDoc} */
57
+ protected createWorker(): Worker;
58
+ /** {@inheritDoc} */
59
+ protected afterWorkerSetup(worker: Worker): void;
60
+ /** {@inheritDoc} */
61
+ get type(): PoolType;
62
+ /** {@inheritDoc} */
63
+ get busy(): boolean;
64
+ }
@@ -0,0 +1,85 @@
1
+ import type { IPool } from './pool';
2
+ import type { IPoolWorker } from './pool-worker';
3
+ /**
4
+ * Internal pool types.
5
+ */
6
+ export declare enum PoolType {
7
+ FIXED = "fixed",
8
+ DYNAMIC = "dynamic"
9
+ }
10
+ /**
11
+ * Internal tasks usage statistics.
12
+ */
13
+ export interface TasksUsage {
14
+ run: number;
15
+ running: number;
16
+ runTime: number;
17
+ avgRunTime: number;
18
+ }
19
+ /**
20
+ * Internal contract definition for a poolifier pool.
21
+ *
22
+ * @typeParam Worker - Type of worker which manages this pool.
23
+ * @typeParam Data - Type of data sent to the worker.
24
+ * @typeParam Response - Type of response of execution.
25
+ */
26
+ export interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends IPool<Data, Response> {
27
+ /**
28
+ * List of currently available workers.
29
+ */
30
+ readonly workers: Worker[];
31
+ /**
32
+ * The workers tasks usage map.
33
+ *
34
+ * `key`: The `Worker`
35
+ * `value`: Worker tasks usage statistics.
36
+ */
37
+ readonly workersTasksUsage: Map<Worker, TasksUsage>;
38
+ /**
39
+ * Pool type.
40
+ *
41
+ * If it is `'dynamic'`, it provides the `max` property.
42
+ */
43
+ readonly type: PoolType;
44
+ /**
45
+ * Whether the pool is busy or not.
46
+ *
47
+ * The pool busyness boolean status.
48
+ */
49
+ readonly busy: boolean;
50
+ /**
51
+ * Number of tasks currently concurrently running.
52
+ */
53
+ readonly numberOfRunningTasks: number;
54
+ /**
55
+ * Finds a free worker based on the number of tasks the worker has applied.
56
+ *
57
+ * If a worker is found with `0` running tasks, it is detected as free and returned.
58
+ *
59
+ * If no free worker is found, `false` is returned.
60
+ *
61
+ * @returns A free worker if there is one, otherwise `false`.
62
+ */
63
+ findFreeWorker: () => Worker | false;
64
+ /**
65
+ * Gets worker index.
66
+ *
67
+ * @param worker - The worker.
68
+ * @returns The worker index.
69
+ */
70
+ getWorkerIndex: (worker: Worker) => number;
71
+ /**
72
+ * Gets worker running tasks.
73
+ *
74
+ * @param worker - The worker.
75
+ * @returns The number of tasks currently running on the worker.
76
+ */
77
+ getWorkerRunningTasks: (worker: Worker) => number | undefined;
78
+ /**
79
+ * Gets worker average tasks runtime.
80
+ *
81
+ * @param worker - The worker.
82
+ * @returns The average tasks runtime on the worker.
83
+ */
84
+ getWorkerAverageTasksRunTime: (worker: Worker) => number | undefined;
85
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Callback invoked if the worker has received a message.
3
+ */
4
+ export type MessageHandler<Worker> = (this: Worker, m: unknown) => void;
5
+ /**
6
+ * Callback invoked if the worker raised an error.
7
+ */
8
+ export type ErrorHandler<Worker> = (this: Worker, e: Error) => void;
9
+ /**
10
+ * Callback invoked when the worker has started successfully.
11
+ */
12
+ export type OnlineHandler<Worker> = (this: Worker) => void;
13
+ /**
14
+ * Callback invoked when the worker exits successfully.
15
+ */
16
+ export type ExitHandler<Worker> = (this: Worker, code: number) => void;
17
+ /**
18
+ * Interface that describes the minimum required implementation of listener events for a pool worker.
19
+ */
20
+ export interface IPoolWorker {
21
+ /**
22
+ * Register an event listener.
23
+ *
24
+ * @param event - The event.
25
+ * @param handler - The event listener.
26
+ */
27
+ on: ((event: 'message', handler: MessageHandler<this>) => void) & ((event: 'error', handler: ErrorHandler<this>) => void) & ((event: 'online', handler: OnlineHandler<this>) => void) & ((event: 'exit', handler: ExitHandler<this>) => void);
28
+ /**
29
+ * Register a listener to the exit event that will only performed once.
30
+ *
31
+ * @param event - `'exit'`.
32
+ * @param handler - The exit handler.
33
+ */
34
+ once: (event: 'exit', handler: ExitHandler<this>) => void;
35
+ }
@@ -0,0 +1,73 @@
1
+ /// <reference types="node" />
2
+ import EventEmitter from 'events';
3
+ import type { ErrorHandler, ExitHandler, MessageHandler, OnlineHandler } from './pool-worker';
4
+ import type { WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
5
+ /**
6
+ * Pool events emitter.
7
+ */
8
+ export declare class PoolEmitter extends EventEmitter {
9
+ }
10
+ /**
11
+ * Options for a poolifier pool.
12
+ */
13
+ export interface PoolOptions<Worker> {
14
+ /**
15
+ * A function that will listen for message event on each worker.
16
+ */
17
+ messageHandler?: MessageHandler<Worker>;
18
+ /**
19
+ * A function that will listen for error event on each worker.
20
+ */
21
+ errorHandler?: ErrorHandler<Worker>;
22
+ /**
23
+ * A function that will listen for online event on each worker.
24
+ */
25
+ onlineHandler?: OnlineHandler<Worker>;
26
+ /**
27
+ * A function that will listen for exit event on each worker.
28
+ */
29
+ exitHandler?: ExitHandler<Worker>;
30
+ /**
31
+ * The worker choice strategy to use in this pool.
32
+ */
33
+ workerChoiceStrategy?: WorkerChoiceStrategy;
34
+ /**
35
+ * Pool events emission.
36
+ *
37
+ * @defaultValue true
38
+ */
39
+ enableEvents?: boolean;
40
+ }
41
+ /**
42
+ * Contract definition for a poolifier pool.
43
+ *
44
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
45
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
46
+ */
47
+ export interface IPool<Data = unknown, Response = unknown> {
48
+ /**
49
+ * Emitter on which events can be listened to.
50
+ *
51
+ * Events that can currently be listened to:
52
+ *
53
+ * - `'busy'`
54
+ */
55
+ readonly emitter?: PoolEmitter;
56
+ /**
57
+ * Performs the task specified in the constructor with the data parameter.
58
+ *
59
+ * @param data - The input for the specified task. This can only be serializable data.
60
+ * @returns Promise that will be resolved when the task is successfully completed.
61
+ */
62
+ execute: (data: Data) => Promise<Response>;
63
+ /**
64
+ * Shutdowns every current worker in this pool.
65
+ */
66
+ destroy: () => Promise<void>;
67
+ /**
68
+ * Sets the worker choice strategy in this pool.
69
+ *
70
+ * @param workerChoiceStrategy - The worker choice strategy.
71
+ */
72
+ setWorkerChoiceStrategy: (workerChoiceStrategy: WorkerChoiceStrategy) => void;
73
+ }
@@ -0,0 +1,27 @@
1
+ import type { IPoolInternal } from '../pool-internal';
2
+ import type { IPoolWorker } from '../pool-worker';
3
+ import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
4
+ /**
5
+ * Abstract worker choice strategy class.
6
+ *
7
+ * @typeParam Worker - Type of worker which manages the strategy.
8
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
9
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
10
+ */
11
+ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> implements IWorkerChoiceStrategy<Worker> {
12
+ protected readonly pool: IPoolInternal<Worker, Data, Response>;
13
+ /** {@inheritDoc} */
14
+ readonly isDynamicPool: boolean;
15
+ /** {@inheritDoc} */
16
+ requiredStatistics: RequiredStatistics;
17
+ /**
18
+ * Constructs a worker choice strategy attached to the pool.
19
+ *
20
+ * @param pool - The pool instance.
21
+ */
22
+ constructor(pool: IPoolInternal<Worker, Data, Response>);
23
+ /** {@inheritDoc} */
24
+ abstract reset(): boolean;
25
+ /** {@inheritDoc} */
26
+ abstract choose(): Worker;
27
+ }
@@ -0,0 +1,27 @@
1
+ import type { IPoolInternal } from '../pool-internal';
2
+ import type { IPoolWorker } from '../pool-worker';
3
+ import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
4
+ import type { WorkerChoiceStrategy } from './selection-strategies-types';
5
+ /**
6
+ * Selects the next worker for dynamic pool.
7
+ *
8
+ * @typeParam Worker - Type of worker which manages the strategy.
9
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
10
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
11
+ */
12
+ export declare class DynamicPoolWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
13
+ private readonly createDynamicallyWorkerCallback;
14
+ private readonly workerChoiceStrategy;
15
+ /**
16
+ * Constructs a worker choice strategy for dynamic pool.
17
+ *
18
+ * @param pool - The pool instance.
19
+ * @param createDynamicallyWorkerCallback - The worker creation callback for dynamic pool.
20
+ * @param workerChoiceStrategy - The worker choice strategy when the pull is busy.
21
+ */
22
+ constructor(pool: IPoolInternal<Worker, Data, Response>, createDynamicallyWorkerCallback: () => Worker, workerChoiceStrategy?: WorkerChoiceStrategy);
23
+ /** {@inheritDoc} */
24
+ reset(): boolean;
25
+ /** {@inheritDoc} */
26
+ choose(): Worker;
27
+ }
@@ -0,0 +1,29 @@
1
+ import type { IPoolWorker } from '../pool-worker';
2
+ import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
+ import type { RequiredStatistics } from './selection-strategies-types';
4
+ /**
5
+ * Selects the next worker with a fair share scheduling algorithm.
6
+ * Loosely modeled after the fair queueing algorithm: https://en.wikipedia.org/wiki/Fair_queuing.
7
+ *
8
+ * @typeParam Worker - Type of worker which manages the strategy.
9
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
10
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
11
+ */
12
+ export declare class FairShareWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
13
+ /** {@inheritDoc} */
14
+ readonly requiredStatistics: RequiredStatistics;
15
+ /**
16
+ * Worker last virtual task execution timestamp.
17
+ */
18
+ private readonly workerLastVirtualTaskTimestamp;
19
+ /** {@inheritDoc} */
20
+ reset(): boolean;
21
+ /** {@inheritDoc} */
22
+ choose(): Worker;
23
+ /**
24
+ * Computes worker last virtual task timestamp.
25
+ *
26
+ * @param worker - The worker.
27
+ */
28
+ private computeWorkerLastVirtualTaskTimestamp;
29
+ }
@@ -0,0 +1,15 @@
1
+ import type { IPoolWorker } from '../pool-worker';
2
+ import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
+ /**
4
+ * Selects the less recently used worker.
5
+ *
6
+ * @typeParam Worker - Type of worker which manages the strategy.
7
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
8
+ * @typeParam Response - Type of response of execution. This can only be serializable data.
9
+ */
10
+ export declare class LessRecentlyUsedWorkerChoiceStrategy<Worker extends IPoolWorker, Data, Response> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> {
11
+ /** {@inheritDoc} */
12
+ reset(): boolean;
13
+ /** {@inheritDoc} */
14
+ choose(): Worker;
15
+ }