-import os from 'node:os'
+import * as os from 'node:os'
+import { webcrypto } from 'node:crypto'
import type {
MeasurementStatisticsRequirements,
WorkerChoiceStrategyOptions
} from './pools/selection-strategies/selection-strategies-types'
import type { KillBehavior } from './worker/worker-options'
+import type { MeasurementStatistics } from './pools/worker'
+
+/**
+ * Default task name.
+ */
+export const DEFAULT_TASK_NAME = 'default'
/**
* An intentional empty function.
*/
export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrategyOptions =
{
+ choiceRetries: 6,
runTime: { median: false },
waitTime: { median: false },
elu: { median: false }
* Always returns a value greater than zero.
*
* @returns The host OS optimized maximum pool size.
+ * @internal
*/
export const availableParallelism = (): number => {
let availableParallelism = 1
try {
availableParallelism = os.availableParallelism()
} catch {
- const cpus = os.cpus()
- if (Array.isArray(cpus) && cpus.length > 0) {
- availableParallelism = cpus.length
+ const numberOfCpus = os.cpus()
+ if (Array.isArray(numberOfCpus) && numberOfCpus.length > 0) {
+ availableParallelism = numberOfCpus.length
}
}
return availableParallelism
}
+/**
+ * Sleeps for the given amount of milliseconds.
+ *
+ * @param ms - The amount of milliseconds to sleep.
+ * @returns A promise that resolves after the given amount of milliseconds.
+ */
+export const sleep = async (ms: number): Promise<void> => {
+ await new Promise((resolve) => {
+ setTimeout(resolve, ms)
+ })
+}
+
+/**
+ * Computes the retry delay in milliseconds using an exponential back off algorithm.
+ *
+ * @param retryNumber - The number of retries that have already been attempted
+ * @param delayFactor - The base delay factor in milliseconds
+ * @returns Delay in milliseconds
+ * @internal
+ */
+export const exponentialDelay = (
+ retryNumber = 0,
+ delayFactor = 100
+): number => {
+ const delay = Math.pow(2, retryNumber) * delayFactor
+ const randomSum = delay * 0.2 * secureRandom() // 0-20% of the delay
+ return delay + randomSum
+}
+
+/**
+ * Computes the average of the given data set.
+ *
+ * @param dataSet - Data set.
+ * @returns The average of the given data set.
+ * @internal
+ */
+export const average = (dataSet: number[]): number => {
+ if (Array.isArray(dataSet) && dataSet.length === 0) {
+ return 0
+ }
+ if (Array.isArray(dataSet) && dataSet.length === 1) {
+ return dataSet[0]
+ }
+ return (
+ dataSet.reduce((accumulator, number) => accumulator + number, 0) /
+ dataSet.length
+ )
+}
+
/**
* Computes the median of the given data set.
*
* @param dataSet - Data set.
* @returns The median of the given data set.
+ * @internal
*/
export const median = (dataSet: number[]): number => {
if (Array.isArray(dataSet) && dataSet.length === 0) {
* @param killBehavior - Which kind of kill behavior to detect.
* @param value - Any value.
* @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
+ * @internal
*/
export const isKillBehavior = <KB extends KillBehavior>(
killBehavior: KB,
): fn is (...args: unknown[]) => Promise<unknown> => {
return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction'
}
+
+/**
+ * Updates the given measurement statistics.
+ *
+ * @param measurementStatistics - The measurement statistics to update.
+ * @param measurementRequirements - The measurement statistics requirements.
+ * @param measurementValue - The measurement value.
+ * @param numberOfMeasurements - The number of measurements.
+ * @internal
+ */
+export const updateMeasurementStatistics = (
+ measurementStatistics: MeasurementStatistics,
+ measurementRequirements: MeasurementStatisticsRequirements,
+ measurementValue: number
+): void => {
+ if (measurementRequirements.aggregate) {
+ measurementStatistics.aggregate =
+ (measurementStatistics.aggregate ?? 0) + measurementValue
+ measurementStatistics.minimum = Math.min(
+ measurementValue,
+ measurementStatistics.minimum ?? Infinity
+ )
+ measurementStatistics.maximum = Math.max(
+ measurementValue,
+ measurementStatistics.maximum ?? -Infinity
+ )
+ if (
+ (measurementRequirements.average || measurementRequirements.median) &&
+ measurementValue != null
+ ) {
+ measurementStatistics.history.push(measurementValue)
+ if (measurementRequirements.average) {
+ measurementStatistics.average = average(measurementStatistics.history)
+ }
+ if (measurementRequirements.median) {
+ measurementStatistics.median = median(measurementStatistics.history)
+ }
+ }
+ }
+}
+
+/**
+ * Executes a function once at a time.
+ *
+ * @param fn - The function to execute.
+ * @param context - The context to bind the function to.
+ * @returns The function to execute.
+ */
+export const once = (
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ fn: (...args: any[]) => void,
+ context: unknown
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+): ((...args: any[]) => void) => {
+ let called = false
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return function (...args: any[]): void {
+ if (!called) {
+ called = true
+ fn.apply(context, args)
+ called = false
+ }
+ }
+}
+
+/**
+ * Generate a cryptographically secure random number in the [0,1[ range
+ *
+ * @returns A number in the [0,1[ range
+ */
+export const secureRandom = (): number => {
+ return webcrypto.getRandomValues(new Uint32Array(1))[0] / 0x100000000
+}