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