Fix eslint configuration
[poolifier.git] / src / pools / selection-strategies.ts
1 import type { IWorker } from './abstract-pool'
2 import type { IPoolInternal } from './pool-internal'
3 import { PoolType } from './pool-internal'
4
5 /**
6 * Enumeration of worker choice strategies.
7 */
8 export const WorkerChoiceStrategies = Object.freeze({
9 /**
10 * Round robin worker selection strategy.
11 */
12 ROUND_ROBIN: 'ROUND_ROBIN',
13 /**
14 * Less recently used worker selection strategy.
15 */
16 LESS_RECENTLY_USED: 'LESS_RECENTLY_USED'
17 } as const)
18
19 /**
20 * Worker choice strategy.
21 */
22 export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies
23
24 /**
25 * Worker choice strategy interface.
26 *
27 * @template Worker Type of worker which manages the strategy.
28 */
29 interface IWorkerChoiceStrategy<Worker extends IWorker> {
30 /**
31 * Choose a worker in the pool.
32 */
33 choose(): Worker
34 }
35
36 /**
37 * Selects the next worker in a round robin fashion.
38 *
39 * @template Worker Type of worker which manages the strategy.
40 * @template Data Type of data sent to the worker. This can only be serializable data.
41 * @template Response Type of response of execution. This can only be serializable data.
42 */
43 class RoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data, Response>
44 implements IWorkerChoiceStrategy<Worker> {
45 /**
46 * Index for the next worker.
47 */
48 private nextWorkerIndex: number = 0
49
50 /**
51 * Constructs a worker choice strategy that selects in a round robin fashion.
52 *
53 * @param pool The pool instance.
54 */
55 public constructor (
56 private readonly pool: IPoolInternal<Worker, Data, Response>
57 ) {}
58
59 /** @inheritdoc */
60 public choose (): Worker {
61 const chosenWorker = this.pool.workers[this.nextWorkerIndex]
62 this.nextWorkerIndex =
63 this.nextWorkerIndex === this.pool.workers.length - 1
64 ? 0
65 : this.nextWorkerIndex + 1
66 return chosenWorker
67 }
68 }
69
70 /**
71 * Selects the less recently used worker.
72 *
73 * @template Worker Type of worker which manages the strategy.
74 * @template Data Type of data sent to the worker. This can only be serializable data.
75 * @template Response Type of response of execution. This can only be serializable data.
76 */
77 class LessRecentlyUsedWorkerChoiceStrategy<
78 Worker extends IWorker,
79 Data,
80 Response
81 > implements IWorkerChoiceStrategy<Worker> {
82 /**
83 * Constructs a worker choice strategy that selects based on less recently used.
84 *
85 * @param pool The pool instance.
86 */
87 public constructor (
88 private readonly pool: IPoolInternal<Worker, Data, Response>
89 ) {}
90
91 /** @inheritdoc */
92 public choose (): Worker {
93 const isPoolDynamic = this.pool.type === PoolType.DYNAMIC
94 let minNumberOfTasks = Infinity
95 // A worker is always found because it picks the one with fewer tasks
96 let lessRecentlyUsedWorker!: Worker
97 for (const [worker, numberOfTasks] of this.pool.tasks) {
98 if (!isPoolDynamic && numberOfTasks === 0) {
99 return worker
100 } else if (numberOfTasks < minNumberOfTasks) {
101 lessRecentlyUsedWorker = worker
102 minNumberOfTasks = numberOfTasks
103 }
104 }
105 return lessRecentlyUsedWorker
106 }
107 }
108
109 /**
110 * Dynamically choose a worker.
111 *
112 * @template Worker Type of worker which manages the strategy.
113 * @template Data Type of data sent to the worker. This can only be serializable data.
114 * @template Response Type of response of execution. This can only be serializable data.
115 */
116 class DynamicPoolWorkerChoiceStrategy<Worker extends IWorker, Data, Response>
117 implements IWorkerChoiceStrategy<Worker> {
118 private workerChoiceStrategy: IWorkerChoiceStrategy<Worker>
119
120 /**
121 * Constructs a worker choice strategy for dynamical pools.
122 *
123 * @param pool The pool instance.
124 * @param createDynamicallyWorkerCallback The worker creation callback for dynamic pool.
125 * @param workerChoiceStrategy The worker choice strategy when the pull is busy.
126 */
127 public constructor (
128 private readonly pool: IPoolInternal<Worker, Data, Response>,
129 private createDynamicallyWorkerCallback: () => Worker,
130 workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
131 ) {
132 this.workerChoiceStrategy = SelectionStrategiesUtils.getWorkerChoiceStrategy(
133 this.pool,
134 workerChoiceStrategy
135 )
136 }
137
138 /** @inheritdoc */
139 public choose (): Worker {
140 const freeTaskMapEntry = this.pool.findFreeTasksMapEntry()
141 if (freeTaskMapEntry) {
142 return freeTaskMapEntry[0]
143 }
144
145 if (this.pool.busy) {
146 return this.workerChoiceStrategy.choose()
147 }
148
149 // All workers are busy, create a new worker
150 return this.createDynamicallyWorkerCallback()
151 }
152 }
153
154 /**
155 * The worker choice strategy context.
156 *
157 * @template Worker Type of worker.
158 * @template Data Type of data sent to the worker. This can only be serializable data.
159 * @template Response Type of response of execution. This can only be serializable data.
160 */
161 export class WorkerChoiceStrategyContext<
162 Worker extends IWorker,
163 Data,
164 Response
165 > {
166 // Will be set by setter in constructor
167 private workerChoiceStrategy!: IWorkerChoiceStrategy<Worker>
168
169 /**
170 * Worker choice strategy context constructor.
171 *
172 * @param pool The pool instance.
173 * @param createDynamicallyWorkerCallback The worker creation callback for dynamic pool.
174 * @param workerChoiceStrategy The worker choice strategy.
175 */
176 public constructor (
177 private readonly pool: IPoolInternal<Worker, Data, Response>,
178 private createDynamicallyWorkerCallback: () => Worker,
179 workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
180 ) {
181 this.setWorkerChoiceStrategy(workerChoiceStrategy)
182 }
183
184 /**
185 * Get the worker choice strategy instance specific to the pool type.
186 *
187 * @param workerChoiceStrategy The worker choice strategy.
188 * @returns The worker choice strategy instance for the pool type.
189 */
190 private getPoolWorkerChoiceStrategy (
191 workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
192 ): IWorkerChoiceStrategy<Worker> {
193 if (this.pool.type === PoolType.DYNAMIC) {
194 return new DynamicPoolWorkerChoiceStrategy(
195 this.pool,
196 this.createDynamicallyWorkerCallback,
197 workerChoiceStrategy
198 )
199 }
200 return SelectionStrategiesUtils.getWorkerChoiceStrategy(
201 this.pool,
202 workerChoiceStrategy
203 )
204 }
205
206 /**
207 * Set the worker choice strategy to use in the context.
208 *
209 * @param workerChoiceStrategy The worker choice strategy to set.
210 */
211 public setWorkerChoiceStrategy (
212 workerChoiceStrategy: WorkerChoiceStrategy
213 ): void {
214 this.workerChoiceStrategy = this.getPoolWorkerChoiceStrategy(
215 workerChoiceStrategy
216 )
217 }
218
219 /**
220 * Choose a worker with the underlying selection strategy.
221 *
222 * @returns The chosen one.
223 */
224 public execute (): Worker {
225 return this.workerChoiceStrategy.choose()
226 }
227 }
228
229 /**
230 * Worker selection strategies helpers class.
231 */
232 class SelectionStrategiesUtils {
233 /**
234 * Get the worker choice strategy instance.
235 *
236 * @param pool The pool instance.
237 * @param workerChoiceStrategy The worker choice strategy.
238 * @returns The worker choice strategy instance.
239 */
240 public static getWorkerChoiceStrategy<
241 Worker extends IWorker,
242 Data,
243 Response
244 > (
245 pool: IPoolInternal<Worker, Data, Response>,
246 workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
247 ): IWorkerChoiceStrategy<Worker> {
248 switch (workerChoiceStrategy) {
249 case WorkerChoiceStrategies.ROUND_ROBIN:
250 return new RoundRobinWorkerChoiceStrategy(pool)
251 case WorkerChoiceStrategies.LESS_RECENTLY_USED:
252 return new LessRecentlyUsedWorkerChoiceStrategy(pool)
253 default:
254 throw new Error(
255 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
256 `Worker choice strategy '${workerChoiceStrategy}' not found`
257 )
258 }
259 }
260 }