poolifier 2.6.3 → 2.6.5
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 +9 -8
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/pools/abstract-pool.d.ts +8 -9
- package/lib/pools/cluster/dynamic.d.ts +2 -2
- package/lib/pools/cluster/fixed.d.ts +2 -8
- package/lib/pools/pool.d.ts +3 -3
- package/lib/pools/selection-strategies/abstract-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/fair-share-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/least-busy-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/least-elu-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/least-used-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/round-robin-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/worker-choice-strategy-context.d.ts +2 -2
- package/lib/pools/thread/dynamic.d.ts +2 -2
- package/lib/pools/thread/fixed.d.ts +8 -18
- package/lib/pools/worker.d.ts +9 -5
- package/lib/queue.d.ts +6 -0
- package/lib/utility-types.d.ts +6 -23
- package/lib/worker/abstract-worker.d.ts +7 -6
- package/lib/worker/cluster-worker.d.ts +2 -2
- package/lib/worker/thread-worker.d.ts +2 -2
- package/lib/worker/worker-functions.d.ts +8 -8
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -34,26 +34,27 @@ With poolifier you can improve your **performance** and resolve problems related
|
|
|
34
34
|
Moreover you can execute your tasks using an API designed to improve the **developer experience**.
|
|
35
35
|
Please consult our [general guidelines](#general-guidance).
|
|
36
36
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- Easy to use :couple:
|
|
37
|
+
- Easy to use :white_check_mark:
|
|
38
|
+
- Performance [benchmarks](./benchmarks/README.md) :white_check_mark:
|
|
40
39
|
- Dynamic pool size :white_check_mark:
|
|
41
40
|
- Easy switch from a pool to another :white_check_mark:
|
|
42
41
|
- No runtime dependencies :white_check_mark:
|
|
43
42
|
- Proper async integration with node async hooks :white_check_mark:
|
|
44
|
-
- Support
|
|
43
|
+
- Support CommonJS, ESM, and TypeScript :white_check_mark:
|
|
44
|
+
- Support for worker-threads and cluster node modules :white_check_mark:
|
|
45
45
|
- Support sync and async tasks :white_check_mark:
|
|
46
46
|
- Tasks distribution strategies :white_check_mark:
|
|
47
|
-
- General guidance on
|
|
47
|
+
- General guidance on pool choice :white_check_mark:
|
|
48
48
|
- Widely tested :white_check_mark:
|
|
49
49
|
- Error handling out of the box :white_check_mark:
|
|
50
50
|
- Active community :white_check_mark:
|
|
51
|
-
- Code quality
|
|
51
|
+
- Code quality [](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
|
|
52
52
|
[](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
|
|
53
53
|
[](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
|
|
54
54
|
[](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
|
|
55
55
|
[](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
|
|
56
56
|
[](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
|
|
57
|
+
- Code security [](https://sonarcloud.io/dashboard?id=pioardi_poolifier) [](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
|
|
57
58
|
|
|
58
59
|
## Contents
|
|
59
60
|
|
|
@@ -140,9 +141,9 @@ pool.execute({}).then(res => {
|
|
|
140
141
|
|
|
141
142
|
You can do the same with the classes ClusterWorker, FixedClusterPool and DynamicClusterPool.
|
|
142
143
|
|
|
143
|
-
**See examples folder for more details (in particular if you want to use a pool with [multiple worker functions](./examples/multiFunctionExample.js))**.
|
|
144
|
+
**See [examples](./examples/) folder for more details (in particular if you want to use a pool with [multiple worker functions](./examples/multiFunctionExample.js))**.
|
|
144
145
|
|
|
145
|
-
Remember that workers can only send and receive
|
|
146
|
+
Remember that workers can only send and receive structured-cloneable data.
|
|
146
147
|
|
|
147
148
|
## Node versions
|
|
148
149
|
|
package/lib/index.d.ts
CHANGED
|
@@ -8,13 +8,13 @@ export { Measurements, WorkerChoiceStrategies } from './pools/selection-strategi
|
|
|
8
8
|
export type { IWorkerChoiceStrategy, Measurement, MeasurementOptions, MeasurementStatisticsRequirements, StrategyPolicy, TaskStatisticsRequirements, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types';
|
|
9
9
|
export type { WorkerChoiceStrategyContext } from './pools/selection-strategies/worker-choice-strategy-context';
|
|
10
10
|
export { DynamicThreadPool } from './pools/thread/dynamic';
|
|
11
|
-
export { FixedThreadPool, type ThreadPoolOptions
|
|
11
|
+
export { FixedThreadPool, type ThreadPoolOptions } from './pools/thread/fixed';
|
|
12
12
|
export type { AbstractWorker } from './worker/abstract-worker';
|
|
13
13
|
export { ClusterWorker } from './worker/cluster-worker';
|
|
14
14
|
export { ThreadWorker } from './worker/thread-worker';
|
|
15
15
|
export { KillBehaviors } from './worker/worker-options';
|
|
16
16
|
export type { KillBehavior, WorkerOptions } from './worker/worker-options';
|
|
17
17
|
export type { TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './worker/worker-functions';
|
|
18
|
-
export type {
|
|
18
|
+
export type { MessageValue, PromiseResponseWrapper, TaskError, TaskPerformance, WorkerStatistics } from './utility-types';
|
|
19
19
|
export type { CircularArray } from './circular-array';
|
|
20
20
|
export type { Queue } from './queue';
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),r=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),a=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),h=Object.freeze({cluster:"cluster",thread:"thread"});class u extends e{}const k=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),c=Object.freeze((()=>{})),l={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},d=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class p extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class w{items;offset;size;maxSize;constructor(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}}const T=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),f=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class W{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){e=e??l,this.setTaskStatisticsRequirements(e),this.opts=e}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i.cpus().length)}}class y extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===f.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends W{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class x extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class N extends W{constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class v extends W{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class E extends W{strategyPolicy={useDynamicWorker:!0};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class I extends W{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=T.ROUND_ROBIN,s=l){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[T.ROUND_ROBIN,new(E.bind(this))(e,s)],[T.LEAST_USED,new(N.bind(this))(e,s)],[T.LEAST_BUSY,new(x.bind(this))(e,s)],[T.LEAST_ELU,new(v.bind(this))(e,s)],[T.FAIR_SHARE,new(y.bind(this))(e,s)],[T.WEIGHTED_ROUND_ROBIN,new(I.bind(this))(e,s)],[T.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class b{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??T.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??l,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(T).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(f).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,this.getWorkerUsage(e.worker)),this.setWorkerStatistics(e.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}async execute(e,t){const i=r.performance.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].workerUsage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=d(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.performance.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=d(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=d(e.elu.idle.history),e.elu.active.median=d(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===n.dynamic&&!this.full&&this.internalBusy()}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(k.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=m.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(t.reject(e.taskError.message),null!=this.emitter&&this.emitter.emit(k.taskError,e.taskError)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(k.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(e),tasksQueue:new w})}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){return{tasks:this.getTaskStatistics(e),runTime:{aggregate:0,average:0,median:0,history:new p},waitTime:{aggregate:0,average:0,median:0,history:new p},elu:{idle:{aggregate:0,average:0,median:0,history:new p},active:{aggregate:0,average:0,median:0,history:new p},utilization:0}}}getTaskStatistics(e){const t=this.workerNodes[this.getWorkerNodeKey(e)]?.tasksQueue?.size;return{executed:0,executing:0,get queued(){return t??0},failed:0}}}class C extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return n.fixed}get worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class O extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV,...this.opts.workerOptions})}afterWorkerSetup(e){const{port1:t,port2:s}=new o.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return n.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const q="default",z=6e4,U=m.SOFT;class P extends a.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:U,maxInactiveTime:z}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??z)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??U,this.opts.maxInactiveTime=e.maxInactiveTime??z,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(q,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(q,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill?(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy()):null!=e.statistics&&(this.statistics=e.statistics)}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){r.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??z)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())})).catch(c)}getTaskFunction(e){e=e??q;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return{timestamp:r.performance.now(),...this.statistics.elu&&{elu:r.performance.eventLoopUtilization()}}}endTaskPerformance(e){return{...e,...this.statistics.runTime&&{runTime:r.performance.now()-e.timestamp},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization(e.elu)}}}}exports.ClusterWorker=class extends P{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends C{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends O{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=C,exports.FixedThreadPool=O,exports.KillBehaviors=m,exports.Measurements=f,exports.PoolEvents=k,exports.PoolTypes=n,exports.ThreadWorker=class extends P{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=T,exports.WorkerTypes=h;
|
|
1
|
+
"use strict";var e=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),r=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),a=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),h=Object.freeze({cluster:"cluster",thread:"thread"});class u extends e{}const k=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),c=Object.freeze((()=>{})),l={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},d=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class p extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class w{items;offset;size;maxSize;constructor(){this.clear()}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}clear(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}}const T=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),f=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class W{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){this.opts=e??l,this.setTaskStatisticsRequirements(this.opts)}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.waitTime.median:this.pool.workerNodes[e].workerUsage.waitTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i.cpus().length)}}class y extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===f.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends W{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class x extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class N extends W{constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class v extends W{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class E extends W{strategyPolicy={useDynamicWorker:!0};constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class I extends W{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=l){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=T.ROUND_ROBIN,s=l){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[T.ROUND_ROBIN,new(E.bind(this))(e,s)],[T.LEAST_USED,new(N.bind(this))(e,s)],[T.LEAST_BUSY,new(x.bind(this))(e,s)],[T.LEAST_ELU,new(v.bind(this))(e,s)],[T.FAIR_SHARE,new(y.bind(this))(e,s)],[T.WEIGHTED_ROUND_ROBIN,new(I.bind(this))(e,s)],[T.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class b{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??T.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??l,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(T).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(f).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.queued),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.maxQueued),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const[e,t]of this.workerNodes.entries())this.setWorkerNodeTasksUsage(t,this.getWorkerUsage(e)),this.setWorkerStatistics(t.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}async execute(e,t){const i=r.performance.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].workerUsage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=d(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.performance.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=d(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=d(e.elu.idle.history),e.elu.active.median=d(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===n.dynamic&&!this.full&&this.internalBusy()}registerWorkerMessageListener(e,t){e.on("message",t)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,this.workerListener())}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(k.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=m.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(null!=this.emitter&&this.emitter.emit(k.taskError,e.taskError),t.reject(e.taskError.message)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(k.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(),tasksQueue:new w});const t=this.getWorkerNodeKey(e);return this.setWorkerNodeTasksUsage(this.workerNodes[t],this.getWorkerUsage(t)),this.workerNodes.length}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}tasksMaxQueueSize(e){return this.workerNodes[e].tasksQueue.maxSize}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e));this.workerNodes[e].tasksQueue.clear()}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){const t=e=>null!=e?this.tasksQueueSize(e):0,s=e=>null!=e?this.tasksMaxQueueSize(e):0;return{tasks:{executed:0,executing:0,get queued(){return t(e)},get maxQueued(){return s(e)},failed:0},runTime:{aggregate:0,average:0,median:0,history:new p},waitTime:{aggregate:0,average:0,median:0,history:new p},elu:{idle:{aggregate:0,average:0,median:0,history:new p},active:{aggregate:0,average:0,median:0,history:new p},utilization:0}}}}class C extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.on("disconnect",(()=>{e.kill()})),e.disconnect()}sendToWorker(e,t){e.send(t)}createWorker(){return t.fork(this.opts.env)}get type(){return n.fixed}get worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class O extends b{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV,...this.opts.workerOptions})}get type(){return n.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const q="default",z=6e4,U=m.SOFT;class P extends a.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:U,maxInactiveTime:z}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??z)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??U,this.opts.maxInactiveTime=e.maxInactiveTime??z,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(q,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(q,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.statistics?this.statistics=e.statistics:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker not set");return this.mainWorker}checkAlive(){r.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??z)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())})).catch(c)}getTaskFunction(e){e=e??q;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return this.checkStatistics(),{timestamp:r.performance.now(),...this.statistics.elu&&{elu:r.performance.eventLoopUtilization()}}}endTaskPerformance(e){return this.checkStatistics(),{...e,...this.statistics.runTime&&{runTime:r.performance.now()-e.timestamp},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization(e.elu)}}}checkStatistics(){if(null==this.statistics)throw new Error("Performance statistics computation requirements not set")}}exports.ClusterWorker=class extends P{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends C{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends O{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=C,exports.FixedThreadPool=O,exports.KillBehaviors=m,exports.Measurements=f,exports.PoolEvents=k,exports.PoolTypes=n,exports.ThreadWorker=class extends P{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=T,exports.WorkerTypes=h;
|
package/lib/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:events";import t from"node:cluster";import s from"node:crypto";import{performance as r}from"node:perf_hooks";import{cpus as i}from"node:os";import{isMainThread as o,Worker as a,SHARE_ENV as n,MessageChannel as h,parentPort as u}from"node:worker_threads";import{AsyncResource as k}from"node:async_hooks";const c=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),d=Object.freeze({cluster:"cluster",thread:"thread"});class l extends e{}const g=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),m=Object.freeze((()=>{})),p={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},w=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},T=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),f=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class W extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class y{items;offset;size;maxSize;constructor(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}}const S=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),x=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class N{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=p){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){e=e??p,this.setTaskStatisticsRequirements(e),this.opts=e}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i().length)}}class v extends N{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===x.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class E extends N{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class I extends N{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class R extends N{constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class b extends N{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class O extends N{strategyPolicy={useDynamicWorker:!0};constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class C extends N{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class z{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=S.ROUND_ROBIN,s=p){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[S.ROUND_ROBIN,new(O.bind(this))(e,s)],[S.LEAST_USED,new(R.bind(this))(e,s)],[S.LEAST_BUSY,new(I.bind(this))(e,s)],[S.LEAST_ELU,new(b.bind(this))(e,s)],[S.FAIR_SHARE,new(v.bind(this))(e,s)],[S.WEIGHTED_ROUND_ROBIN,new(C.bind(this))(e,s)],[S.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(E.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class q{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new l),this.workerChoiceStrategyContext=new z(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===c.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!T(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??S.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??p,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(S).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!T(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(x).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!T(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,this.getWorkerUsage(e.worker)),this.setWorkerStatistics(e.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}async execute(e,t){const i=r.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].workerUsage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=w(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=w(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=w(e.elu.idle.history),e.elu.active.median=w(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===c.dynamic&&!this.full&&this.internalBusy()}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??m),e.on("error",this.opts.errorHandler??m),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(g.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??m),e.on("exit",this.opts.exitHandler??m),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=f.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(t.reject(e.taskError.message),null!=this.emitter&&this.emitter.emit(g.taskError,e.taskError)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(g.busy,this.info),this.type===c.dynamic&&this.full&&this.emitter?.emit(g.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(e),tasksQueue:new y})}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){return{tasks:this.getTaskStatistics(e),runTime:{aggregate:0,average:0,median:0,history:new W},waitTime:{aggregate:0,average:0,median:0,history:new W},elu:{idle:{aggregate:0,average:0,median:0,history:new W},active:{aggregate:0,average:0,median:0,history:new W},utilization:0}}}getTaskStatistics(e){const t=this.workerNodes[this.getWorkerNodeKey(e)]?.tasksQueue?.size;return{executed:0,executing:0,get queued(){return t??0},failed:0}}}class U extends q{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return c.fixed}get worker(){return d.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class A extends U{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return c.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}class P extends q{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new a(this.filePath,{env:n,...this.opts.workerOptions})}afterWorkerSetup(e){const{port1:t,port2:s}=new h;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return c.fixed}get worker(){return d.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class Q extends P{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return c.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}const D="default",M=6e4,_=f.SOFT;class V extends k{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:_,maxInactiveTime:M}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??M)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??_,this.opts.maxInactiveTime=e.maxInactiveTime??M,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(D,e.bind(this));else{if(!T(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(D,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill?(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy()):null!=e.statistics&&(this.statistics=e.statistics)}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){r.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??M)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.now())})).catch(m)}getTaskFunction(e){e=e??D;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return{timestamp:r.now(),...this.statistics.elu&&{elu:r.eventLoopUtilization()}}}endTaskPerformance(e){return{...e,...this.statistics.runTime&&{runTime:r.now()-e.timestamp},...this.statistics.elu&&{elu:r.eventLoopUtilization(e.elu)}}}}class L extends V{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class j extends V{constructor(e,t={}){super("worker-thread-pool:poolifier",o,e,u,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{L as ClusterWorker,A as DynamicClusterPool,Q as DynamicThreadPool,U as FixedClusterPool,P as FixedThreadPool,f as KillBehaviors,x as Measurements,g as PoolEvents,c as PoolTypes,j as ThreadWorker,S as WorkerChoiceStrategies,d as WorkerTypes};
|
|
1
|
+
import e from"node:events";import t from"node:cluster";import s from"node:crypto";import{performance as r}from"node:perf_hooks";import{cpus as i}from"node:os";import{isMainThread as o,Worker as a,SHARE_ENV as n,parentPort as h}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";const k=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),c=Object.freeze({cluster:"cluster",thread:"thread"});class d extends e{}const l=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),g=Object.freeze((()=>{})),m={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},p=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},w=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),T=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class f extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class W{items;offset;size;maxSize;constructor(){this.clear()}enqueue(e){return this.items.push(e),++this.size,this.size>this.maxSize&&(this.maxSize=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.offset];return 2*++this.offset>=this.items.length&&(this.items=this.items.slice(this.offset),this.offset=0),--this.size,e}peek(){if(!(this.size<=0))return this.items[this.offset]}clear(){this.items=[],this.offset=0,this.size=0,this.maxSize=0}}const y=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),S=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class x{pool;opts;nextWorkerNodeId=0;strategyPolicy={useDynamicWorker:!1};taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=m){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){this.opts=e??m,this.setTaskStatisticsRequirements(this.opts)}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.waitTime.median:this.pool.workerNodes[e].workerUsage.waitTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i().length)}}class N extends x{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=m){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e=1/0;for(const[t]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[t]&&this.computeWorkerVirtualTaskEndTimestamp(t);const s=this.workersVirtualTaskEndTimestamp[t];s<e&&(e=s,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+(this.opts.measurement===S.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class v extends x{strategyPolicy={useDynamicWorker:!0};roundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=m){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.nextWorkerNodeId=0,this.roundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.roundId;s<this.roundWeights.length;s++)for(let r=this.nextWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.roundId=e??0,this.nextWorkerNodeId=t??0;const s=this.nextWorkerNodeId;return this.nextWorkerNodeId===this.pool.workerNodes.length-1?(this.nextWorkerNodeId=0,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1):this.nextWorkerNodeId=this.nextWorkerNodeId+1,s}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1,this.roundId=this.roundId===this.roundWeights.length-1?0:this.roundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class E extends x{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=m){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===r){this.nextWorkerNodeId=t;break}r<e&&(e=r,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class I extends x{constructor(e,t=m){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage.tasks,i=r.executed+r.executing+r.queued;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class R extends x{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=m){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e=1/0;for(const[t,s]of this.pool.workerNodes.entries()){const r=s.workerUsage,i=r.elu?.active.aggregate??0;if(0===i){this.nextWorkerNodeId=t;break}i<e&&(e=i,this.nextWorkerNodeId=t)}return this.nextWorkerNodeId}remove(){return!0}}class b extends x{strategyPolicy={useDynamicWorker:!0};constructor(e,t=m){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class O extends x{strategyPolicy={useDynamicWorker:!0};taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=m){super(e,t),this.setTaskStatisticsRequirements(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.nextWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class C{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=y.ROUND_ROBIN,s=m){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[y.ROUND_ROBIN,new(b.bind(this))(e,s)],[y.LEAST_USED,new(I.bind(this))(e,s)],[y.LEAST_BUSY,new(E.bind(this))(e,s)],[y.LEAST_ELU,new(R.bind(this))(e,s)],[y.FAIR_SHARE,new(N.bind(this))(e,s)],[y.WEIGHTED_ROUND_ROBIN,new(O.bind(this))(e,s)],[y.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(v.bind(this))(e,s)]])}getStrategyPolicy(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).strategyPolicy}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class z{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new d),this.workerChoiceStrategyContext=new C(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!w(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??y.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??m,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(y).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!w(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node");if(null!=e.measurement&&!Object.values(S).includes(e.measurement))throw new Error(`Invalid worker choice strategy options: invalid measurement '${e.measurement}'`)}checkValidTasksQueueOptions(e){if(null!=e&&!w(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(null!=e?.concurrency&&!Number.isSafeInteger(e.concurrency))throw new TypeError("Invalid worker tasks concurrency: must be an integer");if(null!=e?.concurrency&&e.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.queued),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.maxQueued),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const[e,t]of this.workerNodes.entries())this.setWorkerNodeTasksUsage(t,this.getWorkerUsage(e)),this.setWorkerStatistics(t.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}async execute(e,t){const i=r.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e,t){const s=this.workerNodes[e].workerUsage;++s.tasks.executing,this.updateWaitTimeWorkerUsage(s,t)}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage;this.updateTaskStatisticsWorkerUsage(s,t),this.updateRunTimeWorkerUsage(s,t),this.updateEluWorkerUsage(s,t)}updateTaskStatisticsWorkerUsage(e,t){const s=e.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=p(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const s=r.now(),i=s-(t.timestamp??s);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/(e.tasks.executed-e.tasks.failed)),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=p(e.waitTime.history)))}updateEluWorkerUsage(e,t){if(this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate){if(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed){const t=e.tasks.executed-e.tasks.failed;e.elu.idle.average=e.elu.idle.aggregate/t,e.elu.active.average=e.elu.active.aggregate/t}this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=p(e.elu.idle.history),e.elu.active.median=p(e.elu.active.history))}}chooseWorkerNode(){if(this.shallCreateDynamicWorker()){const e=this.createAndSetupDynamicWorker();if(this.workerChoiceStrategyContext.getStrategyPolicy().useDynamicWorker)return this.getWorkerNodeKey(e)}return this.workerChoiceStrategyContext.execute()}shallCreateDynamicWorker(){return this.type===k.dynamic&&!this.full&&this.internalBusy()}registerWorkerMessageListener(e,t){e.on("message",t)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,this.workerListener())}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??g),e.on("error",this.opts.errorHandler??g),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(l.error,e),!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??g),e.on("exit",this.opts.exitHandler??g),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}createAndSetupDynamicWorker(){const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(t=>{const s=this.getWorkerNodeKey(e);var r;r=T.HARD,(t.kill===r||null!=t.kill&&(!1===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing||!0===this.opts.enableTasksQueue&&0===this.workerNodes[s].workerUsage.tasks.executing&&0===this.tasksQueueSize(s)))&&this.destroyWorker(e)})),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.taskError?(null!=this.emitter&&this.emitter.emit(l.taskError,e.taskError),t.reject(e.taskError.message)):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s)),this.workerChoiceStrategyContext.update(s)}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(l.busy,this.info),this.type===k.dynamic&&this.full&&this.emitter?.emit(l.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(),tasksQueue:new W});const t=this.getWorkerNodeKey(e);return this.setWorkerNodeTasksUsage(this.workerNodes[t],this.getWorkerUsage(t)),this.workerNodes.length}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}tasksMaxQueueSize(e){return this.workerNodes[e].tasksQueue.maxSize}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e));this.workerNodes[e].tasksQueue.clear()}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){const t=e=>null!=e?this.tasksQueueSize(e):0,s=e=>null!=e?this.tasksMaxQueueSize(e):0;return{tasks:{executed:0,executing:0,get queued(){return t(e)},get maxQueued(){return s(e)},failed:0},runTime:{aggregate:0,average:0,median:0,history:new f},waitTime:{aggregate:0,average:0,median:0,history:new f},elu:{idle:{aggregate:0,average:0,median:0,history:new f},active:{aggregate:0,average:0,median:0,history:new f},utilization:0}}}}class q extends z{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.on("disconnect",(()=>{e.kill()})),e.disconnect()}sendToWorker(e,t){e.send(t)}createWorker(){return t.fork(this.opts.env)}get type(){return k.fixed}get worker(){return c.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class U extends q{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return k.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}class Q extends z{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}createWorker(){return new a(this.filePath,{env:n,...this.opts.workerOptions})}get type(){return k.fixed}get worker(){return c.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class A extends Q{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return k.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}const P="default",D=6e4,M=T.SOFT;class _ extends u{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:M,maxInactiveTime:D}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??D)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??M,this.opts.maxInactiveTime=e.maxInactiveTime??D,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(P,e.bind(this));else{if(!w(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(P,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.statistics?this.statistics=e.statistics:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker not set");return this.mainWorker}checkAlive(){r.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??D)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let s=this.beginTaskPerformance();const r=e(t.data);s=this.endTaskPerformance(s),this.sendToMainWorker({data:r,taskPerformance:s,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.now())}}runAsync(e,t){let s=this.beginTaskPerformance();e(t.data).then((e=>(s=this.endTaskPerformance(s),this.sendToMainWorker({data:e,taskPerformance:s,id:t.id}),null))).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({taskError:{message:s,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.now())})).catch(g)}getTaskFunction(e){e=e??P;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return this.checkStatistics(),{timestamp:r.now(),...this.statistics.elu&&{elu:r.eventLoopUtilization()}}}endTaskPerformance(e){return this.checkStatistics(),{...e,...this.statistics.runTime&&{runTime:r.now()-e.timestamp},...this.statistics.elu&&{elu:r.eventLoopUtilization(e.elu)}}}checkStatistics(){if(null==this.statistics)throw new Error("Performance statistics computation requirements not set")}}class V extends _{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class j extends _{constructor(e,t={}){super("worker-thread-pool:poolifier",o,e,h,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{V as ClusterWorker,U as DynamicClusterPool,A as DynamicThreadPool,q as FixedClusterPool,Q as FixedThreadPool,T as KillBehaviors,S as Measurements,l as PoolEvents,k as PoolTypes,j as ThreadWorker,y as WorkerChoiceStrategies,c as WorkerTypes};
|
|
@@ -7,8 +7,8 @@ import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choic
|
|
|
7
7
|
* Base class that implements some shared logic for all poolifier pools.
|
|
8
8
|
*
|
|
9
9
|
* @typeParam Worker - Type of worker which manages this pool.
|
|
10
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
11
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
10
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
11
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
12
12
|
*/
|
|
13
13
|
export declare abstract class AbstractPool<Worker extends IWorker, Data = unknown, Response = unknown> implements IPool<Worker, Data, Response> {
|
|
14
14
|
protected readonly numberOfWorkers: number;
|
|
@@ -110,8 +110,8 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
110
110
|
*/
|
|
111
111
|
protected abstract destroyWorker(worker: Worker): void | Promise<void>;
|
|
112
112
|
/**
|
|
113
|
-
* Setup hook to execute code before worker
|
|
114
|
-
* Can be overridden
|
|
113
|
+
* Setup hook to execute code before worker nodes are created in the abstract constructor.
|
|
114
|
+
* Can be overridden.
|
|
115
115
|
*
|
|
116
116
|
* @virtual
|
|
117
117
|
*/
|
|
@@ -167,7 +167,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
167
167
|
* @param worker - The worker which should register a listener.
|
|
168
168
|
* @param listener - The message listener callback.
|
|
169
169
|
*/
|
|
170
|
-
|
|
170
|
+
private registerWorkerMessageListener;
|
|
171
171
|
/**
|
|
172
172
|
* Creates a new worker.
|
|
173
173
|
*
|
|
@@ -176,12 +176,11 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
176
176
|
protected abstract createWorker(): Worker;
|
|
177
177
|
/**
|
|
178
178
|
* Function that can be hooked up when a worker has been newly created and moved to the pool worker nodes.
|
|
179
|
-
*
|
|
180
|
-
* Can be used to update the `maxListeners` or binding the `main-worker`\<-\>`worker` connection if not bind by default.
|
|
179
|
+
* Can be overridden.
|
|
181
180
|
*
|
|
182
181
|
* @param worker - The newly created worker.
|
|
183
182
|
*/
|
|
184
|
-
protected
|
|
183
|
+
protected afterWorkerSetup(worker: Worker): void;
|
|
185
184
|
/**
|
|
186
185
|
* Creates a new worker and sets it up completely in the pool worker nodes.
|
|
187
186
|
*
|
|
@@ -225,9 +224,9 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
225
224
|
private enqueueTask;
|
|
226
225
|
private dequeueTask;
|
|
227
226
|
private tasksQueueSize;
|
|
227
|
+
private tasksMaxQueueSize;
|
|
228
228
|
private flushTasksQueue;
|
|
229
229
|
private flushTasksQueues;
|
|
230
230
|
private setWorkerStatistics;
|
|
231
231
|
private getWorkerUsage;
|
|
232
|
-
private getTaskStatistics;
|
|
233
232
|
}
|
|
@@ -6,8 +6,8 @@ import { type ClusterPoolOptions, FixedClusterPool } from './fixed';
|
|
|
6
6
|
* This cluster pool creates new workers when the others are busy, up to the maximum number of workers.
|
|
7
7
|
* When the maximum number of workers is reached and workers are busy, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
|
|
8
8
|
*
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
10
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
9
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
11
11
|
* @author [Christopher Quadflieg](https://github.com/Shinigami92)
|
|
12
12
|
* @since 2.0.0
|
|
13
13
|
*/
|
|
@@ -23,10 +23,8 @@ export interface ClusterPoolOptions extends PoolOptions<Worker> {
|
|
|
23
23
|
/**
|
|
24
24
|
* A cluster pool with a fixed number of workers.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
29
|
-
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
26
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
27
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
30
28
|
* @author [Christopher Quadflieg](https://github.com/Shinigami92)
|
|
31
29
|
* @since 2.0.0
|
|
32
30
|
*/
|
|
@@ -49,12 +47,8 @@ export declare class FixedClusterPool<Data = unknown, Response = unknown> extend
|
|
|
49
47
|
/** @inheritDoc */
|
|
50
48
|
protected sendToWorker(worker: Worker, message: MessageValue<Data>): void;
|
|
51
49
|
/** @inheritDoc */
|
|
52
|
-
protected registerWorkerMessageListener<Message extends Data | Response>(worker: Worker, listener: (message: MessageValue<Message>) => void): void;
|
|
53
|
-
/** @inheritDoc */
|
|
54
50
|
protected createWorker(): Worker;
|
|
55
51
|
/** @inheritDoc */
|
|
56
|
-
protected afterWorkerSetup(worker: Worker): void;
|
|
57
|
-
/** @inheritDoc */
|
|
58
52
|
protected get type(): PoolType;
|
|
59
53
|
/** @inheritDoc */
|
|
60
54
|
protected get worker(): WorkerType;
|
package/lib/pools/pool.d.ts
CHANGED
|
@@ -133,8 +133,8 @@ export interface PoolOptions<Worker extends IWorker> {
|
|
|
133
133
|
* Contract definition for a poolifier pool.
|
|
134
134
|
*
|
|
135
135
|
* @typeParam Worker - Type of worker which manages this pool.
|
|
136
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
137
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
136
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
137
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
138
138
|
*/
|
|
139
139
|
export interface IPool<Worker extends IWorker, Data = unknown, Response = unknown> {
|
|
140
140
|
/**
|
|
@@ -159,7 +159,7 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
|
|
|
159
159
|
/**
|
|
160
160
|
* Executes the specified function in the worker constructor with the task data input parameter.
|
|
161
161
|
*
|
|
162
|
-
* @param data - The task input data for the specified worker function. This can only be
|
|
162
|
+
* @param data - The task input data for the specified worker function. This can only be structured-cloneable data.
|
|
163
163
|
* @param name - The name of the worker function to execute. If not specified, the default worker function will be executed.
|
|
164
164
|
* @returns Promise that will be fulfilled when the task is completed.
|
|
165
165
|
*/
|
|
@@ -5,8 +5,8 @@ import type { IWorkerChoiceStrategy, StrategyPolicy, TaskStatisticsRequirements,
|
|
|
5
5
|
* Worker choice strategy abstract base class.
|
|
6
6
|
*
|
|
7
7
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
8
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
9
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
8
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
9
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
10
10
|
*/
|
|
11
11
|
export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> implements IWorkerChoiceStrategy {
|
|
12
12
|
protected readonly pool: IPool<Worker, Data, Response>;
|
|
@@ -7,8 +7,8 @@ import { type IWorkerChoiceStrategy, type TaskStatisticsRequirements, type Worke
|
|
|
7
7
|
* Loosely modeled after the fair queueing algorithm: https://en.wikipedia.org/wiki/Fair_queuing.
|
|
8
8
|
*
|
|
9
9
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
10
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
11
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
10
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
11
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
12
12
|
*/
|
|
13
13
|
export declare class FairShareWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
14
14
|
/** @inheritDoc */
|
package/lib/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ import type { IWorkerChoiceStrategy, StrategyPolicy, WorkerChoiceStrategyOptions
|
|
|
6
6
|
* Selects the next worker with an interleaved weighted round robin scheduling algorithm.
|
|
7
7
|
*
|
|
8
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
10
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
9
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
11
11
|
*/
|
|
12
12
|
export declare class InterleavedWeightedRoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
13
|
/** @inheritDoc */
|
|
@@ -6,8 +6,8 @@ import type { IWorkerChoiceStrategy, TaskStatisticsRequirements, WorkerChoiceStr
|
|
|
6
6
|
* Selects the least busy worker.
|
|
7
7
|
*
|
|
8
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
10
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
9
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
11
11
|
*/
|
|
12
12
|
export declare class LeastBusyWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
13
|
/** @inheritDoc */
|
|
@@ -6,8 +6,8 @@ import type { IWorkerChoiceStrategy, TaskStatisticsRequirements, WorkerChoiceStr
|
|
|
6
6
|
* Selects the worker with the least ELU.
|
|
7
7
|
*
|
|
8
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
10
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
9
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
11
11
|
*/
|
|
12
12
|
export declare class LeastEluWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
13
|
/** @inheritDoc */
|
|
@@ -6,8 +6,8 @@ import type { IWorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selec
|
|
|
6
6
|
* Selects the least used worker.
|
|
7
7
|
*
|
|
8
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
10
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
9
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
11
11
|
*/
|
|
12
12
|
export declare class LeastUsedWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
13
|
/** @inheritDoc */
|
|
@@ -6,8 +6,8 @@ import type { IWorkerChoiceStrategy, StrategyPolicy, WorkerChoiceStrategyOptions
|
|
|
6
6
|
* Selects the next worker in a round robin fashion.
|
|
7
7
|
*
|
|
8
8
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
10
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
9
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
11
11
|
*/
|
|
12
12
|
export declare class RoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
13
|
/** @inheritDoc */
|
|
@@ -7,8 +7,8 @@ import type { IWorkerChoiceStrategy, StrategyPolicy, TaskStatisticsRequirements,
|
|
|
7
7
|
* Loosely modeled after the weighted round robin queueing algorithm: https://en.wikipedia.org/wiki/Weighted_round_robin.
|
|
8
8
|
*
|
|
9
9
|
* @typeParam Worker - Type of worker which manages the strategy.
|
|
10
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
11
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
10
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
11
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
12
12
|
*/
|
|
13
13
|
export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
14
14
|
/** @inheritDoc */
|
|
@@ -5,8 +5,8 @@ import type { StrategyPolicy, TaskStatisticsRequirements, WorkerChoiceStrategy,
|
|
|
5
5
|
* The worker choice strategy context.
|
|
6
6
|
*
|
|
7
7
|
* @typeParam Worker - Type of worker.
|
|
8
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
9
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
8
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
9
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
10
10
|
*/
|
|
11
11
|
export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data = unknown, Response = unknown> {
|
|
12
12
|
private workerChoiceStrategy;
|
|
@@ -6,8 +6,8 @@ import { FixedThreadPool, type ThreadPoolOptions } from './fixed';
|
|
|
6
6
|
* This thread pool creates new threads when the others are busy, up to the maximum number of threads.
|
|
7
7
|
* When the maximum number of threads is reached and workers are busy, an event is emitted. If you want to listen to this event, use the pool's `emitter`.
|
|
8
8
|
*
|
|
9
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
10
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
9
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
10
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
11
11
|
* @author [Alessandro Pio Ardizio](https://github.com/pioardi)
|
|
12
12
|
* @since 0.0.1
|
|
13
13
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
2
|
+
import { Worker, type WorkerOptions } from 'node:worker_threads';
|
|
3
|
+
import type { MessageValue } from '../../utility-types';
|
|
4
4
|
import { AbstractPool } from '../abstract-pool';
|
|
5
5
|
import { type PoolOptions, type PoolType, type WorkerType } from '../pool';
|
|
6
6
|
/**
|
|
@@ -14,21 +14,15 @@ export interface ThreadPoolOptions extends PoolOptions<Worker> {
|
|
|
14
14
|
*/
|
|
15
15
|
workerOptions?: WorkerOptions;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
18
|
-
* A thread worker with message channels for communication between main thread and thread worker.
|
|
19
|
-
*/
|
|
20
|
-
export type ThreadWorkerWithMessageChannel = Worker & Draft<MessageChannel>;
|
|
21
17
|
/**
|
|
22
18
|
* A thread pool with a fixed number of threads.
|
|
23
19
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
27
|
-
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
20
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
21
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
28
22
|
* @author [Alessandro Pio Ardizio](https://github.com/pioardi)
|
|
29
23
|
* @since 0.0.1
|
|
30
24
|
*/
|
|
31
|
-
export declare class FixedThreadPool<Data = unknown, Response = unknown> extends AbstractPool<
|
|
25
|
+
export declare class FixedThreadPool<Data = unknown, Response = unknown> extends AbstractPool<Worker, Data, Response> {
|
|
32
26
|
protected readonly opts: ThreadPoolOptions;
|
|
33
27
|
/**
|
|
34
28
|
* Constructs a new poolifier fixed thread pool.
|
|
@@ -41,15 +35,11 @@ export declare class FixedThreadPool<Data = unknown, Response = unknown> extends
|
|
|
41
35
|
/** @inheritDoc */
|
|
42
36
|
protected isMain(): boolean;
|
|
43
37
|
/** @inheritDoc */
|
|
44
|
-
protected destroyWorker(worker:
|
|
45
|
-
/** @inheritDoc */
|
|
46
|
-
protected sendToWorker(worker: ThreadWorkerWithMessageChannel, message: MessageValue<Data>): void;
|
|
47
|
-
/** @inheritDoc */
|
|
48
|
-
protected registerWorkerMessageListener<Message extends Data | Response>(worker: ThreadWorkerWithMessageChannel, listener: (message: MessageValue<Message>) => void): void;
|
|
38
|
+
protected destroyWorker(worker: Worker): Promise<void>;
|
|
49
39
|
/** @inheritDoc */
|
|
50
|
-
protected
|
|
40
|
+
protected sendToWorker(worker: Worker, message: MessageValue<Data>): void;
|
|
51
41
|
/** @inheritDoc */
|
|
52
|
-
protected
|
|
42
|
+
protected createWorker(): Worker;
|
|
53
43
|
/** @inheritDoc */
|
|
54
44
|
protected get type(): PoolType;
|
|
55
45
|
/** @inheritDoc */
|
package/lib/pools/worker.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import type { Queue } from '../queue';
|
|
|
3
3
|
/**
|
|
4
4
|
* Callback invoked if the worker has received a message.
|
|
5
5
|
*/
|
|
6
|
-
export type MessageHandler<Worker extends IWorker> = (this: Worker,
|
|
6
|
+
export type MessageHandler<Worker extends IWorker> = (this: Worker, message: unknown) => void;
|
|
7
7
|
/**
|
|
8
8
|
* Callback invoked if the worker raised an error.
|
|
9
9
|
*/
|
|
10
|
-
export type ErrorHandler<Worker extends IWorker> = (this: Worker,
|
|
10
|
+
export type ErrorHandler<Worker extends IWorker> = (this: Worker, error: Error) => void;
|
|
11
11
|
/**
|
|
12
12
|
* Callback invoked when the worker has started successfully.
|
|
13
13
|
*/
|
|
@@ -15,11 +15,11 @@ export type OnlineHandler<Worker extends IWorker> = (this: Worker) => void;
|
|
|
15
15
|
/**
|
|
16
16
|
* Callback invoked when the worker exits successfully.
|
|
17
17
|
*/
|
|
18
|
-
export type ExitHandler<Worker extends IWorker> = (this: Worker,
|
|
18
|
+
export type ExitHandler<Worker extends IWorker> = (this: Worker, exitCode: number) => void;
|
|
19
19
|
/**
|
|
20
20
|
* Message object that is passed as a task between main worker and worker.
|
|
21
21
|
*
|
|
22
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
22
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
23
23
|
* @internal
|
|
24
24
|
*/
|
|
25
25
|
export interface Task<Data = unknown> {
|
|
@@ -91,6 +91,10 @@ export interface TaskStatistics {
|
|
|
91
91
|
* Number of queued tasks.
|
|
92
92
|
*/
|
|
93
93
|
readonly queued: number;
|
|
94
|
+
/**
|
|
95
|
+
* Maximum number of queued tasks.
|
|
96
|
+
*/
|
|
97
|
+
readonly maxQueued: number;
|
|
94
98
|
/**
|
|
95
99
|
* Number of failed tasks.
|
|
96
100
|
*/
|
|
@@ -142,7 +146,7 @@ export interface IWorker {
|
|
|
142
146
|
* Worker node interface.
|
|
143
147
|
*
|
|
144
148
|
* @typeParam Worker - Type of worker.
|
|
145
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
149
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
146
150
|
* @internal
|
|
147
151
|
*/
|
|
148
152
|
export interface WorkerNode<Worker extends IWorker, Data = unknown> {
|
package/lib/queue.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
export declare class Queue<T> {
|
|
7
7
|
private items;
|
|
8
8
|
private offset;
|
|
9
|
+
/** The size of the queue. */
|
|
9
10
|
size: number;
|
|
11
|
+
/** The maximum size of the queue. */
|
|
10
12
|
maxSize: number;
|
|
11
13
|
constructor();
|
|
12
14
|
/**
|
|
@@ -28,4 +30,8 @@ export declare class Queue<T> {
|
|
|
28
30
|
* @returns The first item or `undefined` if the queue is empty.
|
|
29
31
|
*/
|
|
30
32
|
peek(): T | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Clear the queue.
|
|
35
|
+
*/
|
|
36
|
+
clear(): void;
|
|
31
37
|
}
|
package/lib/utility-types.d.ts
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
/// <reference types="node" />
|
|
4
|
-
import type { Worker as ClusterWorker } from 'node:cluster';
|
|
5
|
-
import type { MessagePort } from 'node:worker_threads';
|
|
6
2
|
import type { EventLoopUtilization } from 'node:perf_hooks';
|
|
7
3
|
import type { KillBehavior } from './worker/worker-options';
|
|
8
4
|
import type { IWorker, Task } from './pools/worker';
|
|
9
|
-
/**
|
|
10
|
-
* Make all properties in T non-readonly.
|
|
11
|
-
*
|
|
12
|
-
* @typeParam T - Type in which properties will be non-readonly.
|
|
13
|
-
*/
|
|
14
|
-
export type Draft<T> = {
|
|
15
|
-
-readonly [P in keyof T]?: T[P];
|
|
16
|
-
};
|
|
17
5
|
/**
|
|
18
6
|
* Task error.
|
|
19
7
|
*
|
|
20
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
8
|
+
* @typeParam Data - Type of data sent to the worker triggering an error. This can only be structured-cloneable data.
|
|
21
9
|
*/
|
|
22
10
|
export interface TaskError<Data = unknown> {
|
|
23
11
|
/**
|
|
@@ -56,12 +44,11 @@ export interface WorkerStatistics {
|
|
|
56
44
|
/**
|
|
57
45
|
* Message object that is passed between main worker and worker.
|
|
58
46
|
*
|
|
59
|
-
* @typeParam
|
|
60
|
-
* @typeParam
|
|
61
|
-
* @typeParam MainWorker - Type of main worker.
|
|
47
|
+
* @typeParam Data - Type of data sent to the worker or execution response. This can only be structured-cloneable data.
|
|
48
|
+
* @typeParam ErrorData - Type of data sent to the worker triggering an error. This can only be structured-cloneable data.
|
|
62
49
|
* @internal
|
|
63
50
|
*/
|
|
64
|
-
export interface MessageValue<
|
|
51
|
+
export interface MessageValue<Data = unknown, ErrorData = unknown> extends Task<Data> {
|
|
65
52
|
/**
|
|
66
53
|
* Kill code.
|
|
67
54
|
*/
|
|
@@ -69,15 +56,11 @@ export interface MessageValue<MessageData = unknown, Data = unknown, MainWorker
|
|
|
69
56
|
/**
|
|
70
57
|
* Task error.
|
|
71
58
|
*/
|
|
72
|
-
readonly taskError?: TaskError<
|
|
59
|
+
readonly taskError?: TaskError<ErrorData>;
|
|
73
60
|
/**
|
|
74
61
|
* Task performance.
|
|
75
62
|
*/
|
|
76
63
|
readonly taskPerformance?: TaskPerformance;
|
|
77
|
-
/**
|
|
78
|
-
* Reference to main worker.
|
|
79
|
-
*/
|
|
80
|
-
readonly parent?: MainWorker;
|
|
81
64
|
/**
|
|
82
65
|
* Whether to compute the given statistics or not.
|
|
83
66
|
*/
|
|
@@ -87,7 +70,7 @@ export interface MessageValue<MessageData = unknown, Data = unknown, MainWorker
|
|
|
87
70
|
* An object holding the execution response promise resolve/reject callbacks.
|
|
88
71
|
*
|
|
89
72
|
* @typeParam Worker - Type of worker.
|
|
90
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
73
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
91
74
|
* @internal
|
|
92
75
|
*/
|
|
93
76
|
export interface PromiseResponseWrapper<Worker extends IWorker, Response = unknown> {
|
|
@@ -12,12 +12,12 @@ import type { TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunc
|
|
|
12
12
|
* Base class that implements some shared logic for all poolifier workers.
|
|
13
13
|
*
|
|
14
14
|
* @typeParam MainWorker - Type of main worker.
|
|
15
|
-
* @typeParam Data - Type of data this worker receives from pool's execution. This can only be
|
|
16
|
-
* @typeParam Response - Type of response the worker sends back to the main worker. This can only be
|
|
15
|
+
* @typeParam Data - Type of data this worker receives from pool's execution. This can only be structured-cloneable data.
|
|
16
|
+
* @typeParam Response - Type of response the worker sends back to the main worker. This can only be structured-cloneable data.
|
|
17
17
|
*/
|
|
18
18
|
export declare abstract class AbstractWorker<MainWorker extends Worker | MessagePort, Data = unknown, Response = unknown> extends AsyncResource {
|
|
19
19
|
protected readonly isMain: boolean;
|
|
20
|
-
protected mainWorker: MainWorker
|
|
20
|
+
protected readonly mainWorker: MainWorker;
|
|
21
21
|
protected readonly opts: WorkerOptions;
|
|
22
22
|
/**
|
|
23
23
|
* Task function(s) processed by the worker when the pool's `execution` function is invoked.
|
|
@@ -28,7 +28,7 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
28
28
|
*/
|
|
29
29
|
protected lastTaskTimestamp: number;
|
|
30
30
|
/**
|
|
31
|
-
* Performance statistics computation.
|
|
31
|
+
* Performance statistics computation requirements.
|
|
32
32
|
*/
|
|
33
33
|
protected statistics: WorkerStatistics;
|
|
34
34
|
/**
|
|
@@ -44,7 +44,7 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
44
44
|
* @param mainWorker - Reference to main worker.
|
|
45
45
|
* @param opts - Options for the worker.
|
|
46
46
|
*/
|
|
47
|
-
constructor(type: string, isMain: boolean, taskFunctions: WorkerFunction<Data, Response> | TaskFunctions<Data, Response>, mainWorker: MainWorker
|
|
47
|
+
constructor(type: string, isMain: boolean, taskFunctions: WorkerFunction<Data, Response> | TaskFunctions<Data, Response>, mainWorker: MainWorker, opts?: WorkerOptions);
|
|
48
48
|
private checkWorkerOptions;
|
|
49
49
|
/**
|
|
50
50
|
* Checks if the `taskFunctions` parameter is passed to the constructor.
|
|
@@ -57,7 +57,7 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
57
57
|
*
|
|
58
58
|
* @param message - Message received.
|
|
59
59
|
*/
|
|
60
|
-
protected messageListener(message: MessageValue<Data, Data
|
|
60
|
+
protected messageListener(message: MessageValue<Data, Data>): void;
|
|
61
61
|
/**
|
|
62
62
|
* Returns the main worker.
|
|
63
63
|
*
|
|
@@ -103,4 +103,5 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
103
103
|
private getTaskFunction;
|
|
104
104
|
private beginTaskPerformance;
|
|
105
105
|
private endTaskPerformance;
|
|
106
|
+
private checkStatistics;
|
|
106
107
|
}
|
|
@@ -13,8 +13,8 @@ import type { TaskFunctions, WorkerFunction } from './worker-functions';
|
|
|
13
13
|
* If you use a `DynamicClusterPool` the extra workers that were created will be terminated,
|
|
14
14
|
* but the minimum number of workers will be guaranteed.
|
|
15
15
|
*
|
|
16
|
-
* @typeParam Data - Type of data this worker receives from pool's execution. This can only be
|
|
17
|
-
* @typeParam Response - Type of response the worker sends back to the main worker. This can only be
|
|
16
|
+
* @typeParam Data - Type of data this worker receives from pool's execution. This can only be structured-cloneable data.
|
|
17
|
+
* @typeParam Response - Type of response the worker sends back to the main worker. This can only be structured-cloneable data.
|
|
18
18
|
* @author [Christopher Quadflieg](https://github.com/Shinigami92)
|
|
19
19
|
* @since 2.0.0
|
|
20
20
|
*/
|
|
@@ -13,8 +13,8 @@ import type { TaskFunctions, WorkerFunction } from './worker-functions';
|
|
|
13
13
|
* If you use a `DynamicThreadPool` the extra workers that were created will be terminated,
|
|
14
14
|
* but the minimum number of workers will be guaranteed.
|
|
15
15
|
*
|
|
16
|
-
* @typeParam Data - Type of data this worker receives from pool's execution. This can only be
|
|
17
|
-
* @typeParam Response - Type of response the worker sends back to the main thread. This can only be
|
|
16
|
+
* @typeParam Data - Type of data this worker receives from pool's execution. This can only be structured-cloneable data.
|
|
17
|
+
* @typeParam Response - Type of response the worker sends back to the main thread. This can only be structured-cloneable data.
|
|
18
18
|
* @author [Alessandro Pio Ardizio](https://github.com/pioardi)
|
|
19
19
|
* @since 0.0.1
|
|
20
20
|
*/
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worker synchronous function that can be executed.
|
|
3
3
|
*
|
|
4
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
5
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
4
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
5
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
6
6
|
*/
|
|
7
7
|
export type WorkerSyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Response;
|
|
8
8
|
/**
|
|
9
9
|
* Worker asynchronous function that can be executed.
|
|
10
10
|
* This function must return a promise.
|
|
11
11
|
*
|
|
12
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
13
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
12
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
13
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
14
14
|
*/
|
|
15
15
|
export type WorkerAsyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Promise<Response>;
|
|
16
16
|
/**
|
|
17
17
|
* Worker function that can be executed.
|
|
18
18
|
* This function can be synchronous or asynchronous.
|
|
19
19
|
*
|
|
20
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
21
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
20
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
21
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
22
22
|
*/
|
|
23
23
|
export type WorkerFunction<Data = unknown, Response = unknown> = WorkerSyncFunction<Data, Response> | WorkerAsyncFunction<Data, Response>;
|
|
24
24
|
/**
|
|
@@ -27,7 +27,7 @@ export type WorkerFunction<Data = unknown, Response = unknown> = WorkerSyncFunct
|
|
|
27
27
|
* The key is the name of the function.
|
|
28
28
|
* The value is the function itself.
|
|
29
29
|
*
|
|
30
|
-
* @typeParam Data - Type of data sent to the worker. This can only be
|
|
31
|
-
* @typeParam Response - Type of execution response. This can only be
|
|
30
|
+
* @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
|
|
31
|
+
* @typeParam Response - Type of execution response. This can only be structured-cloneable data.
|
|
32
32
|
*/
|
|
33
33
|
export type TaskFunctions<Data = unknown, Response = unknown> = Record<string, WorkerFunction<Data, Response>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poolifier",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.5",
|
|
4
4
|
"description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"pnpm": ">=8.6.0"
|
|
26
26
|
},
|
|
27
27
|
"volta": {
|
|
28
|
-
"node": "20.3.
|
|
29
|
-
"pnpm": "8.6.
|
|
28
|
+
"node": "20.3.1",
|
|
29
|
+
"pnpm": "8.6.5"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
@@ -78,25 +78,25 @@
|
|
|
78
78
|
"lib"
|
|
79
79
|
],
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@commitlint/cli": "^17.6.
|
|
82
|
-
"@commitlint/config-conventional": "^17.6.
|
|
81
|
+
"@commitlint/cli": "^17.6.6",
|
|
82
|
+
"@commitlint/config-conventional": "^17.6.6",
|
|
83
83
|
"@release-it/bumper": "^4.0.2",
|
|
84
84
|
"@release-it/keep-a-changelog": "^3.1.0",
|
|
85
85
|
"@rollup/plugin-terser": "^0.4.3",
|
|
86
86
|
"@rollup/plugin-typescript": "^11.1.1",
|
|
87
|
-
"@types/node": "^20.3.
|
|
88
|
-
"@typescript-eslint/eslint-plugin": "^5.60.
|
|
89
|
-
"@typescript-eslint/parser": "^5.60.
|
|
87
|
+
"@types/node": "^20.3.2",
|
|
88
|
+
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
|
89
|
+
"@typescript-eslint/parser": "^5.60.1",
|
|
90
90
|
"benny": "^3.7.1",
|
|
91
91
|
"c8": "^8.0.0",
|
|
92
92
|
"eslint": "^8.43.0",
|
|
93
93
|
"eslint-config-standard": "^17.1.0",
|
|
94
94
|
"eslint-config-standard-with-typescript": "^35.0.0",
|
|
95
|
-
"eslint-define-config": "^1.
|
|
95
|
+
"eslint-define-config": "^1.21.0",
|
|
96
96
|
"eslint-import-resolver-typescript": "^3.5.5",
|
|
97
97
|
"eslint-plugin-import": "^2.27.5",
|
|
98
|
-
"eslint-plugin-jsdoc": "^46.
|
|
99
|
-
"eslint-plugin-n": "^16.0.
|
|
98
|
+
"eslint-plugin-jsdoc": "^46.3.0",
|
|
99
|
+
"eslint-plugin-n": "^16.0.1",
|
|
100
100
|
"eslint-plugin-promise": "^6.1.1",
|
|
101
101
|
"eslint-plugin-spellcheck": "^0.0.20",
|
|
102
102
|
"eslint-plugin-tsdoc": "^0.2.17",
|
|
@@ -108,11 +108,11 @@
|
|
|
108
108
|
"mochawesome": "^7.1.3",
|
|
109
109
|
"prettier": "^2.8.8",
|
|
110
110
|
"release-it": "^15.11.0",
|
|
111
|
-
"rollup": "^3.25.
|
|
111
|
+
"rollup": "^3.25.3",
|
|
112
112
|
"rollup-plugin-analyzer": "^4.0.0",
|
|
113
113
|
"rollup-plugin-command": "^1.1.3",
|
|
114
114
|
"rollup-plugin-delete": "^2.0.0",
|
|
115
|
-
"sinon": "^15.
|
|
115
|
+
"sinon": "^15.2.0",
|
|
116
116
|
"source-map-support": "^0.5.21",
|
|
117
117
|
"ts-standard": "^12.0.2",
|
|
118
118
|
"typedoc": "^0.24.8",
|
|
@@ -124,9 +124,9 @@
|
|
|
124
124
|
"build:analyze": "rollup --config --environment ANALYZE,BUILD:development",
|
|
125
125
|
"build:typedoc": "rollup --config --environment DOCUMENTATION,BUILD:development",
|
|
126
126
|
"build:prod": "rollup --config",
|
|
127
|
-
"benchmark": "pnpm build && node -r source-map-support/register benchmarks/internal/bench.
|
|
128
|
-
"benchmark:debug": "pnpm build && node -r source-map-support/register --inspect benchmarks/internal/bench.
|
|
129
|
-
"benchmark:prod": "pnpm build:prod && node -r source-map-support/register benchmarks/internal/bench.
|
|
127
|
+
"benchmark": "pnpm build && node -r source-map-support/register benchmarks/internal/bench.mjs",
|
|
128
|
+
"benchmark:debug": "pnpm build && node -r source-map-support/register --inspect benchmarks/internal/bench.mjs",
|
|
129
|
+
"benchmark:prod": "pnpm build:prod && node -r source-map-support/register benchmarks/internal/bench.mjs",
|
|
130
130
|
"test": "pnpm build && c8 mocha 'tests/**/*.test.js'",
|
|
131
131
|
"test:debug": "pnpm build && mocha --no-parallel --inspect 'tests/**/*.test.js'",
|
|
132
132
|
"coverage": "c8 report --reporter=lcov",
|