poolifier 2.3.6 → 2.3.7

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
@@ -3,10 +3,6 @@
3
3
  </div>
4
4
 
5
5
  <h2 align="center">Node Thread Pool and Cluster Pool :arrow_double_up: :on:</h2>
6
- <h2 align="center">
7
- <a href="https://ko-fi.com/Q5Q31D6QY">
8
- <img alt="Ko-fi" src="https://ko-fi.com/img/githubbutton_sm.svg"></a>
9
- </h2>
10
6
 
11
7
  <p align="center">
12
8
  <a href="https://www.npmjs.com/package/poolifier">
@@ -16,11 +12,13 @@
16
12
  <a href="https://sonarcloud.io/dashboard?id=pioardi_poolifier">
17
13
  <img alt="Quality Gate Status" src="https://sonarcloud.io/api/project_badges/measure?project=pioardi_poolifier&metric=alert_status"></a>
18
14
  <a href="https://sonarcloud.io/component_measures/metric/coverage/list?id=pioardi_poolifier">
19
- <img alt="Code coverage" src="https://sonarcloud.io/api/project_badges/measure?project=pioardi_poolifier&metric=coverage"></a>
15
+ <img alt="Code Coverage" src="https://sonarcloud.io/api/project_badges/measure?project=pioardi_poolifier&metric=coverage"></a>
20
16
  <a href="https://standardjs.com">
21
17
  <img alt="Javascript Standard Style Guide" src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"></a>
22
18
  <a href="https://gitter.im/poolifier/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge">
23
19
  <img alt="Gitter chat" src="https://badges.gitter.im/poolifier/community.svg"></a>
20
+ <a href="https://opencollective.com/poolifier">
21
+ <img alt="Open Collective" src="https://opencollective.com/poolifier/tiers/badge.svg"></a>
24
22
  <a href="https://badgen.net/badge/Dependabot/enabled/green?icon=dependabot">
25
23
  <img alt="Dependabot" src="https://badgen.net/badge/Dependabot/enabled/green?icon=dependabot"></a>
26
24
  <a href="http://makeapullrequest.com">
@@ -99,7 +97,7 @@ You can implement a worker-threads worker in a simple way by extending the class
99
97
  'use strict'
100
98
  const { ThreadWorker } = require('poolifier')
101
99
 
102
- function yourFunction (data) {
100
+ function yourFunction(data) {
103
101
  // this will be executed in the worker thread,
104
102
  // the data will be received by using the execute method
105
103
  return { ok: 1 }
package/lib/index.d.ts CHANGED
@@ -26,40 +26,19 @@ type ExitHandler<Worker> = (this: Worker, code: number) => void;
26
26
  */
27
27
  interface IPoolWorker {
28
28
  /**
29
- * Register a listener to the message event.
29
+ * Register an event listener.
30
30
  *
31
- * @param event `'message'`.
32
- * @param handler The message handler.
31
+ * @param event The event.
32
+ * @param handler The event listener.
33
33
  */
34
- on(event: "message", handler: MessageHandler<this>): void;
35
- /**
36
- * Register a listener to the error event.
37
- *
38
- * @param event `'error'`.
39
- * @param handler The error handler.
40
- */
41
- on(event: "error", handler: ErrorHandler<this>): void;
42
- /**
43
- * Register a listener to the online event.
44
- *
45
- * @param event `'online'`.
46
- * @param handler The online handler.
47
- */
48
- on(event: "online", handler: OnlineHandler<this>): void;
49
- /**
50
- * Register a listener to the exit event.
51
- *
52
- * @param event `'exit'`.
53
- * @param handler The exit handler.
54
- */
55
- on(event: "exit", handler: ExitHandler<this>): void;
34
+ 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);
56
35
  /**
57
36
  * Register a listener to the exit event that will only performed once.
58
37
  *
59
38
  * @param event `'exit'`.
60
39
  * @param handler The exit handler.
61
40
  */
62
- once(event: "exit", handler: ExitHandler<this>): void;
41
+ once: (event: "exit", handler: ExitHandler<this>) => void;
63
42
  }
64
43
  /**
65
44
  * Enumeration of worker choice strategies.
@@ -77,9 +56,9 @@ type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies;
77
56
  /**
78
57
  * Pool tasks usage statistics requirements.
79
58
  */
80
- type RequiredStatistics = {
59
+ interface RequiredStatistics {
81
60
  runTime: boolean;
82
- };
61
+ }
83
62
  /**
84
63
  * Worker choice strategy interface.
85
64
  *
@@ -97,11 +76,11 @@ interface IWorkerChoiceStrategy<Worker extends IPoolWorker> {
97
76
  /**
98
77
  * Resets strategy internals (counters, statistics, etc.).
99
78
  */
100
- reset(): boolean;
79
+ reset: () => boolean;
101
80
  /**
102
81
  * Chooses a worker in the pool.
103
82
  */
104
- choose(): Worker;
83
+ choose: () => Worker;
105
84
  }
106
85
  /**
107
86
  * Pool events emitter.
@@ -160,17 +139,17 @@ interface IPool<Data = unknown, Response = unknown> {
160
139
  * @param data The input for the specified task. This can only be serializable data.
161
140
  * @returns Promise that will be resolved when the task is successfully completed.
162
141
  */
163
- execute(data: Data): Promise<Response>;
142
+ execute: (data: Data) => Promise<Response>;
164
143
  /**
165
144
  * Shutdowns every current worker in this pool.
166
145
  */
167
- destroy(): Promise<void>;
146
+ destroy: () => Promise<void>;
168
147
  /**
169
148
  * Sets the worker choice strategy in this pool.
170
149
  *
171
150
  * @param workerChoiceStrategy The worker choice strategy.
172
151
  */
173
- setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
152
+ setWorkerChoiceStrategy: (workerChoiceStrategy: WorkerChoiceStrategy) => void;
174
153
  }
175
154
  /**
176
155
  * Internal pool types.
@@ -232,28 +211,28 @@ interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Response = u
232
211
  *
233
212
  * @returns A free worker if there is one, otherwise `false`.
234
213
  */
235
- findFreeWorker(): Worker | false;
214
+ findFreeWorker: () => Worker | false;
236
215
  /**
237
216
  * Gets worker index.
238
217
  *
239
218
  * @param worker The worker.
240
219
  * @returns The worker index.
241
220
  */
242
- getWorkerIndex(worker: Worker): number;
221
+ getWorkerIndex: (worker: Worker) => number;
243
222
  /**
244
223
  * Gets worker running tasks.
245
224
  *
246
225
  * @param worker The worker.
247
226
  * @returns The number of tasks currently running on the worker.
248
227
  */
249
- getWorkerRunningTasks(worker: Worker): number | undefined;
228
+ getWorkerRunningTasks: (worker: Worker) => number | undefined;
250
229
  /**
251
230
  * Gets worker average tasks runtime.
252
231
  *
253
232
  * @param worker The worker.
254
233
  * @returns The average tasks runtime on the worker.
255
234
  */
256
- getWorkerAverageTasksRunTime(worker: Worker): number | undefined;
235
+ getWorkerAverageTasksRunTime: (worker: Worker) => number | undefined;
257
236
  }
258
237
  /**
259
238
  * Enumeration of kill behaviors.
@@ -367,7 +346,7 @@ interface PromiseWorkerResponseWrapper<Worker extends IPoolWorker, Response = un
367
346
  */
368
347
  declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Data, Response> {
369
348
  private readonly pool;
370
- private createDynamicallyWorkerCallback;
349
+ private readonly createDynamicallyWorkerCallback;
371
350
  private workerChoiceStrategy;
372
351
  /**
373
352
  * Worker choice strategy context constructor.
@@ -529,7 +508,6 @@ declare abstract class AbstractPool<Worker extends IPoolWorker, Data = unknown,
529
508
  * @param listener The message listener callback.
530
509
  */
531
510
  protected abstract registerWorkerMessageListener<Message extends Data | Response>(worker: Worker, listener: (message: MessageValue<Message>) => void): void;
532
- protected internalExecute(worker: Worker, messageId: number): Promise<Response>;
533
511
  /**
534
512
  * Returns a newly created worker.
535
513
  */
@@ -554,6 +532,7 @@ declare abstract class AbstractPool<Worker extends IPoolWorker, Data = unknown,
554
532
  * @returns The listener function to execute when a message is received from a worker.
555
533
  */
556
534
  protected workerListener(): (message: MessageValue<Response>) => void;
535
+ private internalExecute;
557
536
  private checkAndEmitBusy;
558
537
  /**
559
538
  * Increases the number of tasks that the given worker has applied.
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=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{constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.requiredStatistics={runTime:!1}}}class u extends c{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 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(!1===this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class p extends c{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 c{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 T{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 u(e);case k.WEIGHTED_ROUND_ROBIN:return new g(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}}class W extends c{constructor(e,r,t=k.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=T.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 m{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):T.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,!1===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 m(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(!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 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}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((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??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&&(e.error?r.reject(e.error):r.resolve(e.data),this.afterPromiseWorkerResponseHook(e,r),this.promiseMap.delete(e.id))}}}checkAndEmitBusy(){!0===this.opts.enableEvents&&!0===this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){if(!0===this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.running=t.running+r,this.workersTasksUsage.set(e,t)}}stepWorkerRunTasks(e,r){if(!0===this.checkWorkerTasksUsage(e)){const t=this.workersTasksUsage.get(e);t.run=t.run+r,this.workersTasksUsage.set(e,t)}}updateWorkerTasksRunTime(e,r){if(!0===this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime&&!0===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(!1===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 w extends d{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 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=a.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?!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&&(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(n)}}exports.ClusterWorker=class extends f{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 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=a,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("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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.3.6",
3
+ "version": "2.3.7",
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": {
@@ -15,7 +15,6 @@
15
15
  "test:debug": "npm run build && mocha --no-parallel --inspect 'tests/**/*.test.js'",
16
16
  "coverage": "c8 report --reporter=lcov",
17
17
  "coverage:html": "c8 report --reporter=html",
18
- "format": "prettier --loglevel silent --write .; prettierx --write .",
19
18
  "lint": "eslint . --cache",
20
19
  "lint:fix": "eslint . --cache --fix",
21
20
  "lint:report": "eslint . --cache --format json --output-file reports/eslint.json",
@@ -24,6 +23,15 @@
24
23
  "sonar:properties": "./updateSonarProps.sh",
25
24
  "prepublishOnly": "npm run build:prod"
26
25
  },
26
+ "ts-standard": {
27
+ "ignore": [
28
+ "tests/**/*.js"
29
+ ]
30
+ },
31
+ "engines": {
32
+ "node": ">=16.0.0",
33
+ "npm": ">=8.0.0"
34
+ },
27
35
  "repository": {
28
36
  "type": "git",
29
37
  "url": "git+https://github.com/poolifier/poolifier.git"
@@ -71,14 +79,14 @@
71
79
  "@typescript-eslint/parser": "^5.40.1",
72
80
  "benchmark": "^2.1.4",
73
81
  "c8": "^7.12.0",
74
- "eslint": "^8.25.0",
82
+ "eslint": "^8.26.0",
75
83
  "eslint-config-standard": "^17.0.0",
84
+ "eslint-config-standard-with-typescript": "^23.0.0",
76
85
  "eslint-define-config": "^1.7.0",
77
86
  "eslint-import-resolver-typescript": "^3.5.2",
78
87
  "eslint-plugin-import": "^2.26.0",
79
- "eslint-plugin-jsdoc": "^39.3.14",
88
+ "eslint-plugin-jsdoc": "^39.3.19",
80
89
  "eslint-plugin-n": "^15.3.0",
81
- "eslint-plugin-prettierx": "^0.18.0",
82
90
  "eslint-plugin-promise": "^6.1.1",
83
91
  "eslint-plugin-spellcheck": "^0.0.19",
84
92
  "expect": "^29.2.1",
@@ -89,7 +97,6 @@
89
97
  "mochawesome": "^7.1.3",
90
98
  "prettier": "^2.7.1",
91
99
  "prettier-plugin-organize-imports": "^3.1.1",
92
- "prettierx": "^0.18.3",
93
100
  "release-it": "^15.5.0",
94
101
  "rollup": "^3.2.3",
95
102
  "rollup-plugin-analyzer": "^4.0.0",
@@ -100,11 +107,8 @@
100
107
  "rollup-plugin-ts": "^3.0.2",
101
108
  "sinon": "^14.0.1",
102
109
  "source-map-support": "^0.5.21",
110
+ "ts-standard": "^12.0.1",
103
111
  "typedoc": "^0.23.17",
104
112
  "typescript": "^4.8.4"
105
- },
106
- "engines": {
107
- "node": ">=16.0.0",
108
- "npm": ">=8.0.0"
109
113
  }
110
114
  }