Commit | Line | Data |
---|---|---|
d35e5717 | 1 | import type { IPool } from '../pool.js' |
67f3f2d6 | 2 | import type { IWorker } from '../worker.js' |
97231086 | 3 | |
d35e5717 | 4 | import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js' |
9adcefab JB |
5 | import { |
6 | type IWorkerChoiceStrategy, | |
7 | Measurements, | |
39618ede | 8 | type TaskStatisticsRequirements, |
3a502712 | 9 | type WorkerChoiceStrategyOptions, |
d35e5717 | 10 | } from './selection-strategies-types.js' |
23ff945a | 11 | |
23ff945a JB |
12 | /** |
13 | * Selects the next worker with a fair share scheduling algorithm. | |
14 | * Loosely modeled after the fair queueing algorithm: https://en.wikipedia.org/wiki/Fair_queuing. | |
38e795c1 | 15 | * @typeParam Worker - Type of worker which manages the strategy. |
e102732c JB |
16 | * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data. |
17 | * @typeParam Response - Type of execution response. This can only be structured-cloneable data. | |
23ff945a JB |
18 | */ |
19 | export class FairShareWorkerChoiceStrategy< | |
f06e48d8 | 20 | Worker extends IWorker, |
b2b1d84e JB |
21 | Data = unknown, |
22 | Response = unknown | |
bf90656c JB |
23 | > |
24 | extends AbstractWorkerChoiceStrategy<Worker, Data, Response> | |
17393ac8 | 25 | implements IWorkerChoiceStrategy { |
afc003b2 | 26 | /** @inheritDoc */ |
ae85b351 JB |
27 | public override readonly taskStatisticsRequirements: TaskStatisticsRequirements = |
28 | { | |
29 | elu: { | |
30 | aggregate: true, | |
31 | average: true, | |
32 | median: false, | |
33 | }, | |
34 | runTime: { | |
35 | aggregate: true, | |
36 | average: true, | |
37 | median: false, | |
38 | }, | |
39 | waitTime: { | |
40 | aggregate: true, | |
41 | average: true, | |
42 | median: false, | |
43 | }, | |
44 | } | |
10fcfaf4 | 45 | |
2fc5cae3 JB |
46 | /** @inheritDoc */ |
47 | public constructor ( | |
48 | pool: IPool<Worker, Data, Response>, | |
39618ede | 49 | opts?: WorkerChoiceStrategyOptions |
2fc5cae3 JB |
50 | ) { |
51 | super(pool, opts) | |
932fc8be | 52 | this.setTaskStatisticsRequirements(this.opts) |
2fc5cae3 JB |
53 | } |
54 | ||
97231086 JB |
55 | /** |
56 | * Computes the worker node key virtual task end timestamp. | |
57 | * @param workerNodeKey - The worker node key. | |
58 | * @returns The worker node key virtual task end timestamp. | |
59 | */ | |
60 | private computeWorkerNodeVirtualTaskEndTimestamp ( | |
61 | workerNodeKey: number | |
62 | ): number { | |
63 | return this.getWorkerNodeVirtualTaskEndTimestamp( | |
64 | workerNodeKey, | |
65 | this.getWorkerNodeVirtualTaskStartTimestamp(workerNodeKey) | |
66 | ) | |
9b106837 JB |
67 | } |
68 | ||
b1aae695 | 69 | private fairShareNextWorkerNodeKey (): number | undefined { |
f3a91bac JB |
70 | return this.pool.workerNodes.reduce( |
71 | (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => { | |
72 | if (workerNode.strategyData?.virtualTaskEndTimestamp == null) { | |
73 | workerNode.strategyData = { | |
74 | virtualTaskEndTimestamp: | |
3a502712 | 75 | this.computeWorkerNodeVirtualTaskEndTimestamp(workerNodeKey), |
f3a91bac JB |
76 | } |
77 | } | |
ae3ab61d | 78 | return this.isWorkerNodeReady(workerNodeKey) && |
67f3f2d6 JB |
79 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
80 | workerNode.strategyData.virtualTaskEndTimestamp! < | |
81 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
82 | workerNodes[minWorkerNodeKey].strategyData!.virtualTaskEndTimestamp! | |
f3a91bac JB |
83 | ? workerNodeKey |
84 | : minWorkerNodeKey | |
85 | }, | |
86 | 0 | |
87 | ) | |
97a2abc3 JB |
88 | } |
89 | ||
f3a91bac | 90 | private getWorkerNodeVirtualTaskEndTimestamp ( |
b0d6ed8f | 91 | workerNodeKey: number, |
f3a91bac | 92 | workerNodeVirtualTaskStartTimestamp: number |
b0d6ed8f | 93 | ): number { |
e0843544 JB |
94 | const workerNodeTaskExecutionTime = |
95 | this.getWorkerNodeTaskWaitTime(workerNodeKey) + | |
39618ede | 96 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
e0843544 | 97 | (this.opts!.measurement === Measurements.elu |
f3a91bac | 98 | ? this.getWorkerNodeTaskElu(workerNodeKey) |
e0843544 JB |
99 | : this.getWorkerNodeTaskRunTime(workerNodeKey)) |
100 | return workerNodeVirtualTaskStartTimestamp + workerNodeTaskExecutionTime | |
b0d6ed8f JB |
101 | } |
102 | ||
f3a91bac JB |
103 | private getWorkerNodeVirtualTaskStartTimestamp ( |
104 | workerNodeKey: number | |
105 | ): number { | |
f201a0cd JB |
106 | const virtualTaskEndTimestamp = |
107 | this.pool.workerNodes[workerNodeKey]?.strategyData | |
108 | ?.virtualTaskEndTimestamp | |
f3a91bac | 109 | const now = performance.now() |
80115618 | 110 | return now < (virtualTaskEndTimestamp ?? Number.NEGATIVE_INFINITY) |
67f3f2d6 JB |
111 | ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
112 | virtualTaskEndTimestamp! | |
f3a91bac | 113 | : now |
23ff945a | 114 | } |
97231086 JB |
115 | |
116 | /** @inheritDoc */ | |
117 | public choose (): number | undefined { | |
118 | this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey) | |
119 | this.nextWorkerNodeKey = this.fairShareNextWorkerNodeKey() | |
120 | return this.nextWorkerNodeKey | |
121 | } | |
122 | ||
123 | /** @inheritDoc */ | |
124 | public remove (): boolean { | |
125 | return true | |
126 | } | |
127 | ||
128 | /** @inheritDoc */ | |
129 | public reset (): boolean { | |
130 | for (const workerNode of this.pool.workerNodes) { | |
131 | delete workerNode.strategyData?.virtualTaskEndTimestamp | |
132 | } | |
133 | return true | |
134 | } | |
135 | ||
136 | /** @inheritDoc */ | |
137 | public update (workerNodeKey: number): boolean { | |
138 | this.pool.workerNodes[workerNodeKey].strategyData = { | |
139 | virtualTaskEndTimestamp: | |
140 | this.computeWorkerNodeVirtualTaskEndTimestamp(workerNodeKey), | |
141 | } | |
142 | return true | |
143 | } | |
23ff945a | 144 | } |