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 MeasurementStatisticsRequirements
,
7 WorkerChoiceStrategyOptions
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 * Default worker choice strategy options.
27 export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
: WorkerChoiceStrategyOptions
=
30 runTime
: { median
: false },
31 waitTime
: { median
: false },
32 elu
: { median
: false }
36 * Default measurement statistics requirements.
38 export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
: MeasurementStatisticsRequirements
=
46 * Returns safe host OS optimized estimate of the default amount of parallelism a pool should use.
47 * Always returns a value greater than zero.
49 * @returns The host OS optimized maximum pool size.
51 export const availableParallelism
= (): number => {
52 let availableParallelism
= 1
54 availableParallelism
= os
.availableParallelism()
56 const cpus
= os
.cpus()
57 if (Array.isArray(cpus
) && cpus
.length
> 0) {
58 availableParallelism
= cpus
.length
61 return availableParallelism
65 * Returns the worker type of the given worker.
67 * @param worker - The worker to get the type of.
68 * @returns The worker type of the given worker.
71 export const getWorkerType
= (worker
: IWorker
): WorkerType
| undefined => {
72 if (worker
instanceof ThreadWorker
) {
73 return WorkerTypes
.thread
74 } else if (worker
instanceof ClusterWorker
) {
75 return WorkerTypes
.cluster
80 * Returns the worker id of the given worker.
82 * @param worker - The worker to get the id of.
83 * @returns The worker id of the given worker.
86 export const getWorkerId
= (worker
: IWorker
): number | undefined => {
87 if (worker
instanceof ThreadWorker
) {
88 return worker
.threadId
89 } else if (worker
instanceof ClusterWorker
) {
95 * Sleeps for the given amount of milliseconds.
97 * @param ms - The amount of milliseconds to sleep.
98 * @returns A promise that resolves after the given amount of milliseconds.
101 export const sleep
= async (ms
: number): Promise
<void> => {
102 await new Promise(resolve
=> {
103 setTimeout(resolve
, ms
)
108 * Computes the retry delay in milliseconds using an exponential back off algorithm.
110 * @param retryNumber - The number of retries that have already been attempted
111 * @param delayFactor - The base delay factor in milliseconds
112 * @returns Delay in milliseconds
115 export const exponentialDelay
= (
119 const delay
= Math.pow(2, retryNumber
) * delayFactor
120 const randomSum
= delay
* 0.2 * secureRandom() // 0-20% of the delay
121 return delay
+ randomSum
125 * Computes the average of the given data set.
127 * @param dataSet - Data set.
128 * @returns The average of the given data set.
131 export const average
= (dataSet
: number[]): number => {
132 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
135 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
139 dataSet
.reduce((accumulator
, number) => accumulator
+ number, 0) /
145 * Computes the median of the given data set.
147 * @param dataSet - Data set.
148 * @returns The median of the given data set.
151 export const median
= (dataSet
: number[]): number => {
152 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
155 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
158 const sortedDataSet
= dataSet
.slice().sort((a
, b
) => a
- b
)
160 (sortedDataSet
[(sortedDataSet
.length
- 1) >> 1] +
161 sortedDataSet
[sortedDataSet
.length
>> 1]) /
167 * Rounds the given number to the given scale.
168 * The rounding is done using the "round half away from zero" method.
170 * @param num - The number to round.
171 * @param scale - The scale to round to.
172 * @returns The rounded number.
175 export const round
= (num
: number, scale
= 2): number => {
176 const rounder
= Math.pow(10, scale
)
177 return Math.round(num
* rounder
* (1 + Number.EPSILON
)) / rounder
181 * Is the given object a plain object?
183 * @param obj - The object to check.
184 * @returns `true` if the given object is a plain object, `false` otherwise.
187 export const isPlainObject
= (obj
: unknown
): boolean =>
188 typeof obj
=== 'object' &&
190 obj
?.constructor
=== Object &&
191 Object.prototype
.toString
.call(obj
) === '[object Object]'
194 * Detects whether the given value is a kill behavior or not.
196 * @typeParam KB - Which specific KillBehavior type to test against.
197 * @param killBehavior - Which kind of kill behavior to detect.
198 * @param value - Any value.
199 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
202 export const isKillBehavior
= <KB
extends KillBehavior
>(
206 return value
=== killBehavior
210 * Detects whether the given value is an asynchronous function or not.
212 * @param fn - Any value.
213 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
216 export const isAsyncFunction
= (
218 ): fn
is (...args
: unknown
[]) => Promise
<unknown
> => {
219 return typeof fn
=== 'function' && fn
.constructor
.name
=== 'AsyncFunction'
223 * Generates a cryptographically secure random number in the [0,1[ range
225 * @returns A number in the [0,1[ range
228 export const secureRandom
= (): number => {
229 return getRandomValues(new Uint32Array(1))[0] / 0x100000000
233 * Returns the minimum of the given numbers.
234 * If no numbers are given, `Infinity` is returned.
236 * @param args - The numbers to get the minimum of.
237 * @returns The minimum of the given numbers.
240 export const min
= (...args
: number[]): number =>
241 args
.reduce((minimum
, num
) => (minimum
< num
? minimum
: num
), Infinity)
244 * Returns the maximum of the given numbers.
245 * If no numbers are given, `-Infinity` is returned.
247 * @param args - The numbers to get the maximum of.
248 * @returns The maximum of the given numbers.
251 export const max
= (...args
: number[]): number =>
252 args
.reduce((maximum
, num
) => (maximum
> num
? maximum
: num
), -Infinity)
255 * Wraps a function so that it can only be called once.
257 * @param fn - The function to wrap.
258 * @param context - The context to bind the function to.
259 * @returns The wrapped function.
262 // eslint-disable-next-line @typescript-eslint/no-explicit-any
263 export const once
= <T
, A
extends any[], R
>(
264 fn
: (...args
: A
) => R
,
266 ): ((...args
: A
) => R
) => {
268 return (...args
: A
) => {
270 result
= fn
.apply
<T
, A
, R
>(context
, args
)
271 ;(fn
as unknown
as undefined) = (context
as unknown
as undefined) =