Apply dependencies update (#485)
[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>
9b2fdd9f 44 implements IWorkerChoiceStrategy<Worker> {
a35560ba
S
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.pool.workers.length - 1 === this.nextWorkerIndex
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 */
77class LessRecentlyUsedWorkerChoiceStrategy<
78 Worker extends IWorker,
79 Data,
80 Response
9b2fdd9f 81> implements IWorkerChoiceStrategy<Worker> {
a35560ba
S
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 {
7c0ba920 93 const isPoolDynamic = this.pool.type === PoolType.DYNAMIC
a35560ba
S
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) {
ff5e76e1 98 if (!isPoolDynamic && numberOfTasks === 0) {
a35560ba
S
99 return worker
100 } else if (numberOfTasks < minNumberOfTasks) {
a35560ba 101 lessRecentlyUsedWorker = worker
7c0ba920 102 minNumberOfTasks = numberOfTasks
a35560ba
S
103 }
104 }
105 return lessRecentlyUsedWorker
106 }
107}
108
a35560ba
S
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 */
116class DynamicPoolWorkerChoiceStrategy<Worker extends IWorker, Data, Response>
9b2fdd9f 117 implements IWorkerChoiceStrategy<Worker> {
a35560ba
S
118 private workerChoiceStrategy: IWorkerChoiceStrategy<Worker>
119
120 /**
121 * Constructs a worker choice strategy for dynamical pools.
122 *
123 * @param pool The pool instance.
4a6952ff 124 * @param createDynamicallyWorkerCallback The worker creation callback for dynamic pool.
7c0ba920 125 * @param workerChoiceStrategy The worker choice strategy when the pull is busy.
a35560ba
S
126 */
127 public constructor (
128 private readonly pool: IPoolInternal<Worker, Data, Response>,
4a6952ff 129 private createDynamicallyWorkerCallback: () => Worker,
a35560ba
S
130 workerChoiceStrategy: WorkerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
131 ) {
9b2fdd9f
JB
132 this.workerChoiceStrategy = SelectionStrategiesUtils.getWorkerChoiceStrategy(
133 this.pool,
134 workerChoiceStrategy
135 )
a35560ba
S
136 }
137
a35560ba
S
138 /** @inheritdoc */
139 public choose (): Worker {
7c0ba920
JB
140 const freeTaskMapEntry = this.pool.findFreeTasksMapEntry()
141 if (freeTaskMapEntry) {
142 return freeTaskMapEntry[0]
a35560ba
S
143 }
144
7c0ba920 145 if (this.pool.busy) {
a35560ba
S
146 return this.workerChoiceStrategy.choose()
147 }
148
149 // All workers are busy, create a new worker
4a6952ff 150 return this.createDynamicallyWorkerCallback()
a35560ba
S
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 */
161export 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.
4a6952ff 173 * @param createDynamicallyWorkerCallback The worker creation callback for dynamic pool.
a35560ba
S
174 * @param workerChoiceStrategy The worker choice strategy.
175 */
176 public constructor (
177 private readonly pool: IPoolInternal<Worker, Data, Response>,
4a6952ff 178 private createDynamicallyWorkerCallback: () => Worker,
a35560ba
S
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> {
7c0ba920 193 if (this.pool.type === PoolType.DYNAMIC) {
a35560ba
S
194 return new DynamicPoolWorkerChoiceStrategy(
195 this.pool,
4a6952ff 196 this.createDynamicallyWorkerCallback,
a35560ba
S
197 workerChoiceStrategy
198 )
199 }
ff5e76e1
JB
200 return SelectionStrategiesUtils.getWorkerChoiceStrategy(
201 this.pool,
202 workerChoiceStrategy
203 )
a35560ba
S
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 {
9b2fdd9f
JB
214 this.workerChoiceStrategy = this.getPoolWorkerChoiceStrategy(
215 workerChoiceStrategy
216 )
a35560ba
S
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}
ff5e76e1
JB
228
229/**
7c0ba920 230 * Worker selection strategies helpers class.
ff5e76e1
JB
231 */
232class SelectionStrategiesUtils {
ff5e76e1
JB
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 `Worker choice strategy '${workerChoiceStrategy}' not found`
256 )
257 }
258 }
259}