1 import * as os from
'node:os'
2 import { getRandomValues
} from
'node:crypto'
3 import { Worker
as ClusterWorker
} from
'node:cluster'
4 import { Worker
as ThreadWorker
} from
'node:worker_threads'
6 InternalWorkerChoiceStrategyOptions
,
7 MeasurementStatisticsRequirements
8 } from
'./pools/selection-strategies/selection-strategies-types'
9 import type { KillBehavior
} from
'./worker/worker-options'
10 import { type IWorker
, type WorkerType
, WorkerTypes
} from
'./pools/worker'
15 export const DEFAULT_TASK_NAME
= 'default'
18 * An intentional empty function.
20 export const EMPTY_FUNCTION
: () => void = Object.freeze(() => {
21 /* Intentionally empty */
25 * Gets default worker choice strategy options.
27 * @param retries - The number of worker choice retries.
28 * @returns The default worker choice strategy options.
30 export const getDefaultInternalWorkerChoiceStrategyOptions
= (
32 ): InternalWorkerChoiceStrategyOptions
=> {
35 runTime
: { median
: false },
36 waitTime
: { median
: false },
37 elu
: { median
: false }
42 * Default measurement statistics requirements.
44 export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
: MeasurementStatisticsRequirements
=
52 * Returns safe host OS optimized estimate of the default amount of parallelism a pool should use.
53 * Always returns a value greater than zero.
55 * @returns The host OS optimized maximum pool size.
57 export const availableParallelism
= (): number => {
58 let availableParallelism
= 1
60 availableParallelism
= os
.availableParallelism()
62 const cpus
= os
.cpus()
63 if (Array.isArray(cpus
) && cpus
.length
> 0) {
64 availableParallelism
= cpus
.length
67 return availableParallelism
71 * Returns the worker type of the given worker.
73 * @param worker - The worker to get the type of.
74 * @returns The worker type of the given worker.
77 export const getWorkerType
= (worker
: IWorker
): WorkerType
| undefined => {
78 if (worker
instanceof ThreadWorker
) {
79 return WorkerTypes
.thread
80 } else if (worker
instanceof ClusterWorker
) {
81 return WorkerTypes
.cluster
86 * Returns the worker id of the given worker.
88 * @param worker - The worker to get the id of.
89 * @returns The worker id of the given worker.
92 export const getWorkerId
= (worker
: IWorker
): number | undefined => {
93 if (worker
instanceof ThreadWorker
) {
94 return worker
.threadId
95 } else if (worker
instanceof ClusterWorker
) {
101 * Sleeps for the given amount of milliseconds.
103 * @param ms - The amount of milliseconds to sleep.
104 * @returns A promise that resolves after the given amount of milliseconds.
107 export const sleep
= async (ms
: number): Promise
<void> => {
108 await new Promise(resolve
=> {
109 setTimeout(resolve
, ms
)
114 * Computes the retry delay in milliseconds using an exponential back off algorithm.
116 * @param retryNumber - The number of retries that have already been attempted
117 * @param delayFactor - The base delay factor in milliseconds
118 * @returns Delay in milliseconds
121 export const exponentialDelay
= (
125 const delay
= Math.pow(2, retryNumber
) * delayFactor
126 const randomSum
= delay
* 0.2 * secureRandom() // 0-20% of the delay
127 return delay
+ randomSum
131 * Computes the average of the given data set.
133 * @param dataSet - Data set.
134 * @returns The average of the given data set.
137 export const average
= (dataSet
: number[]): number => {
138 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
141 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
145 dataSet
.reduce((accumulator
, number) => accumulator
+ number, 0) /
151 * Computes the median of the given data set.
153 * @param dataSet - Data set.
154 * @returns The median of the given data set.
157 export const median
= (dataSet
: number[]): number => {
158 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
161 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
164 const sortedDataSet
= dataSet
.slice().sort((a
, b
) => a
- b
)
166 (sortedDataSet
[(sortedDataSet
.length
- 1) >> 1] +
167 sortedDataSet
[sortedDataSet
.length
>> 1]) /
173 * Rounds the given number to the given scale.
174 * The rounding is done using the "round half away from zero" method.
176 * @param num - The number to round.
177 * @param scale - The scale to round to.
178 * @returns The rounded number.
181 export const round
= (num
: number, scale
= 2): number => {
182 const rounder
= Math.pow(10, scale
)
183 return Math.round(num
* rounder
* (1 + Number.EPSILON
)) / rounder
187 * Is the given object a plain object?
189 * @param obj - The object to check.
190 * @returns `true` if the given object is a plain object, `false` otherwise.
193 export const isPlainObject
= (obj
: unknown
): boolean =>
194 typeof obj
=== 'object' &&
196 obj
?.constructor
=== Object &&
197 Object.prototype
.toString
.call(obj
) === '[object Object]'
200 * Detects whether the given value is a kill behavior or not.
202 * @typeParam KB - Which specific KillBehavior type to test against.
203 * @param killBehavior - Which kind of kill behavior to detect.
204 * @param value - Any value.
205 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
208 export const isKillBehavior
= <KB
extends KillBehavior
>(
212 return value
=== killBehavior
216 * Detects whether the given value is an asynchronous function or not.
218 * @param fn - Any value.
219 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
222 export const isAsyncFunction
= (
224 ): fn
is (...args
: unknown
[]) => Promise
<unknown
> => {
225 return typeof fn
=== 'function' && fn
.constructor
.name
=== 'AsyncFunction'
229 * Generates a cryptographically secure random number in the [0,1[ range
231 * @returns A number in the [0,1[ range
234 export const secureRandom
= (): number => {
235 return getRandomValues(new Uint32Array(1))[0] / 0x100000000
239 * Returns the minimum of the given numbers.
240 * If no numbers are given, `Infinity` is returned.
242 * @param args - The numbers to get the minimum of.
243 * @returns The minimum of the given numbers.
246 export const min
= (...args
: number[]): number =>
247 args
.reduce((minimum
, num
) => (minimum
< num
? minimum
: num
), Infinity)
250 * Returns the maximum of the given numbers.
251 * If no numbers are given, `-Infinity` is returned.
253 * @param args - The numbers to get the maximum of.
254 * @returns The maximum of the given numbers.
257 export const max
= (...args
: number[]): number =>
258 args
.reduce((maximum
, num
) => (maximum
> num
? maximum
: num
), -Infinity)
261 * Wraps a function so that it can only be called once.
263 * @param fn - The function to wrap.
264 * @param context - The context to bind the function to.
265 * @returns The wrapped function.
268 // eslint-disable-next-line @typescript-eslint/no-explicit-any
269 export const once
= <T
, A
extends any[], R
>(
270 fn
: (...args
: A
) => R
,
272 ): ((...args
: A
) => R
) => {
274 return (...args
: A
) => {
276 result
= fn
.apply
<T
, A
, R
>(context
, args
)
277 ;(fn
as unknown
as undefined) = (context
as unknown
as undefined) =