b7e0a4a7d8d80bc98d55a859cc241f1600bfd41f
[poolifier.git] / src / pools / selection-strategies / worker-choice-strategy-context.ts
1 import type { IPool } from '../pool.js'
2 import type { IWorker } from '../worker.js'
3 import { getWorkerChoiceStrategyRetries } from '../../utils.js'
4 import { FairShareWorkerChoiceStrategy } from './fair-share-worker-choice-strategy.js'
5 import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from './interleaved-weighted-round-robin-worker-choice-strategy.js'
6 import { LeastBusyWorkerChoiceStrategy } from './least-busy-worker-choice-strategy.js'
7 import { LeastUsedWorkerChoiceStrategy } from './least-used-worker-choice-strategy.js'
8 import { LeastEluWorkerChoiceStrategy } from './least-elu-worker-choice-strategy.js'
9 import { RoundRobinWorkerChoiceStrategy } from './round-robin-worker-choice-strategy.js'
10 import type {
11 IWorkerChoiceStrategy,
12 StrategyPolicy,
13 TaskStatisticsRequirements,
14 WorkerChoiceStrategy,
15 WorkerChoiceStrategyOptions
16 } from './selection-strategies-types.js'
17 import { WorkerChoiceStrategies } from './selection-strategies-types.js'
18 import { WeightedRoundRobinWorkerChoiceStrategy } from './weighted-round-robin-worker-choice-strategy.js'
19
20 /**
21 * The worker choice strategy context.
22 *
23 * @typeParam Worker - Type of worker.
24 * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
25 * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
26 */
27 export class WorkerChoiceStrategyContext<
28 Worker extends IWorker,
29 Data = unknown,
30 Response = unknown
31 > {
32 /**
33 * The worker choice strategy instances registered in the context.
34 */
35 private readonly workerChoiceStrategies: Map<
36 WorkerChoiceStrategy,
37 IWorkerChoiceStrategy
38 >
39
40 /**
41 * The number of worker choice strategy execution retries.
42 */
43 private readonly retries: number
44
45 /**
46 * Worker choice strategy context constructor.
47 *
48 * @param pool - The pool instance.
49 * @param workerChoiceStrategy - The worker choice strategy.
50 * @param opts - The worker choice strategy options.
51 */
52 public constructor (
53 pool: IPool<Worker, Data, Response>,
54 private workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN,
55 opts?: WorkerChoiceStrategyOptions
56 ) {
57 this.execute = this.execute.bind(this)
58 this.workerChoiceStrategies = new Map<
59 WorkerChoiceStrategy,
60 IWorkerChoiceStrategy
61 >([
62 [
63 WorkerChoiceStrategies.ROUND_ROBIN,
64 new (RoundRobinWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
65 pool,
66 opts
67 )
68 ],
69 [
70 WorkerChoiceStrategies.LEAST_USED,
71 new (LeastUsedWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
72 pool,
73 opts
74 )
75 ],
76 [
77 WorkerChoiceStrategies.LEAST_BUSY,
78 new (LeastBusyWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
79 pool,
80 opts
81 )
82 ],
83 [
84 WorkerChoiceStrategies.LEAST_ELU,
85 new (LeastEluWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
86 pool,
87 opts
88 )
89 ],
90 [
91 WorkerChoiceStrategies.FAIR_SHARE,
92 new (FairShareWorkerChoiceStrategy.bind(this))<Worker, Data, Response>(
93 pool,
94 opts
95 )
96 ],
97 [
98 WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN,
99 new (WeightedRoundRobinWorkerChoiceStrategy.bind(this))<
100 Worker,
101 Data,
102 Response
103 >(pool, opts)
104 ],
105 [
106 WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN,
107 new (InterleavedWeightedRoundRobinWorkerChoiceStrategy.bind(this))<
108 Worker,
109 Data,
110 Response
111 >(pool, opts)
112 ]
113 ])
114 this.retries = getWorkerChoiceStrategyRetries(pool, opts)
115 }
116
117 /**
118 * Gets the strategy policy in the context.
119 *
120 * @returns The strategy policy.
121 */
122 public getStrategyPolicy (): StrategyPolicy {
123 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
124 return this.workerChoiceStrategies.get(this.workerChoiceStrategy)!
125 .strategyPolicy
126 }
127
128 /**
129 * Gets the worker choice strategy in the context task statistics requirements.
130 *
131 * @returns The task statistics requirements.
132 */
133 public getTaskStatisticsRequirements (): TaskStatisticsRequirements {
134 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
135 return this.workerChoiceStrategies.get(this.workerChoiceStrategy)!
136 .taskStatisticsRequirements
137 }
138
139 /**
140 * Sets the worker choice strategy to use in the context.
141 *
142 * @param workerChoiceStrategy - The worker choice strategy to set.
143 */
144 public setWorkerChoiceStrategy (
145 workerChoiceStrategy: WorkerChoiceStrategy
146 ): void {
147 if (this.workerChoiceStrategy !== workerChoiceStrategy) {
148 this.workerChoiceStrategy = workerChoiceStrategy
149 }
150 this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()
151 }
152
153 /**
154 * Updates the worker node key in the worker choice strategy in the context internals.
155 *
156 * @returns `true` if the update is successful, `false` otherwise.
157 */
158 public update (workerNodeKey: number): boolean {
159 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
160 return this.workerChoiceStrategies
161 .get(this.workerChoiceStrategy)!
162 .update(workerNodeKey)
163 }
164
165 /**
166 * Executes the worker choice strategy in the context algorithm.
167 *
168 * @returns The key of the worker node.
169 * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
170 */
171 public execute (): number {
172 return this.executeStrategy(
173 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
174 this.workerChoiceStrategies.get(this.workerChoiceStrategy)!
175 )
176 }
177
178 /**
179 * Executes the given worker choice strategy.
180 *
181 * @param workerChoiceStrategy - The worker choice strategy.
182 * @returns The key of the worker node.
183 * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
184 */
185 private executeStrategy (workerChoiceStrategy: IWorkerChoiceStrategy): number {
186 let workerNodeKey: number | undefined
187 let chooseCount = 0
188 let retriesCount = 0
189 do {
190 workerNodeKey = workerChoiceStrategy.choose()
191 if (workerNodeKey != null && workerNodeKey < 0) {
192 workerNodeKey = undefined
193 }
194 if (workerNodeKey == null && chooseCount > 0) {
195 retriesCount++
196 }
197 chooseCount++
198 } while (workerNodeKey == null && retriesCount < this.retries)
199 if (workerNodeKey == null) {
200 throw new Error(
201 `Worker node key chosen is null or undefined after ${retriesCount} retries`
202 )
203 }
204 return workerNodeKey
205 }
206
207 /**
208 * Removes the worker node key from the worker choice strategy in the context.
209 *
210 * @param workerNodeKey - The worker node key.
211 * @returns `true` if the removal is successful, `false` otherwise.
212 */
213 public remove (workerNodeKey: number): boolean {
214 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
215 return this.workerChoiceStrategies
216 .get(this.workerChoiceStrategy)!
217 .remove(workerNodeKey)
218 }
219
220 /**
221 * Sets the worker choice strategies in the context options.
222 *
223 * @param opts - The worker choice strategy options.
224 */
225 public setOptions (opts: WorkerChoiceStrategyOptions | undefined): void {
226 for (const workerChoiceStrategy of this.workerChoiceStrategies.values()) {
227 workerChoiceStrategy.setOptions(opts)
228 }
229 }
230 }