From ab93b18417a841783e86b87ac85c6bf0d1ca5894 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 10 Sep 2023 17:41:26 +0200 Subject: [PATCH] feat: randomize startup delays MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/utils/Utils.ts | 6 +++--- src/worker/WorkerDynamicPool.ts | 4 ++-- src/worker/WorkerSet.ts | 9 +++++---- src/worker/WorkerStaticPool.ts | 4 ++-- src/worker/WorkerUtils.ts | 19 +++++++++++++++++++ 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 14a6aa69..808ef380 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,4 +1,4 @@ -import { randomBytes, randomInt, randomUUID } from 'node:crypto'; +import { randomBytes, randomInt, randomUUID, webcrypto } from 'node:crypto'; import { inspect } from 'node:util'; import { @@ -354,10 +354,10 @@ export const promiseWithTimeout = async ( /** * Generates a cryptographically secure random number in the [0,1[ range * - * @returns + * @returns A number in the [0,1[ range */ export const secureRandom = (): number => { - return randomBytes(4).readUInt32LE() / 0x100000000; + return webcrypto.getRandomValues(new Uint32Array(1))[0] / 0x100000000; }; export const JSONStringifyWithMapSupport = ( diff --git a/src/worker/WorkerDynamicPool.ts b/src/worker/WorkerDynamicPool.ts index 5824d8ad..7506a5f1 100644 --- a/src/worker/WorkerDynamicPool.ts +++ b/src/worker/WorkerDynamicPool.ts @@ -2,7 +2,7 @@ import { DynamicThreadPool, type PoolEmitter, type PoolInfo } from 'poolifier'; import { WorkerAbstract } from './WorkerAbstract'; import type { WorkerData, WorkerOptions } from './WorkerTypes'; -import { sleep } from './WorkerUtils'; +import { randomizeDelay, sleep } from './WorkerUtils'; export class WorkerDynamicPool extends WorkerAbstract { private readonly pool: DynamicThreadPool; @@ -54,6 +54,6 @@ export class WorkerDynamicPool extends WorkerAbstract { await this.pool.execute(elementData); // Start element sequentially to optimize memory at startup this.workerOptions.elementStartDelay! > 0 && - (await sleep(this.workerOptions.elementStartDelay!)); + (await sleep(randomizeDelay(this.workerOptions.elementStartDelay!))); } } diff --git a/src/worker/WorkerSet.ts b/src/worker/WorkerSet.ts index 7ddc96c7..82e2e836 100644 --- a/src/worker/WorkerSet.ts +++ b/src/worker/WorkerSet.ts @@ -14,7 +14,7 @@ import { type WorkerSetElement, WorkerSetEvents, } from './WorkerTypes'; -import { sleep } from './WorkerUtils'; +import { randomizeDelay, sleep } from './WorkerUtils'; export class WorkerSet extends WorkerAbstract { public readonly emitter!: EventEmitter; @@ -76,7 +76,8 @@ export class WorkerSet extends WorkerAbstract { public async start(): Promise { this.addWorkerSetElement(); // Add worker set element sequentially to optimize memory at startup - this.workerOptions.workerStartDelay! > 0 && (await sleep(this.workerOptions.workerStartDelay!)); + this.workerOptions.workerStartDelay! > 0 && + (await sleep(randomizeDelay(this.workerOptions.workerStartDelay!))); this.started = true; } @@ -111,7 +112,7 @@ export class WorkerSet extends WorkerAbstract { ++workerSetElement.numberOfWorkerElements; // Add element sequentially to optimize memory at startup if (this.workerOptions.elementStartDelay! > 0) { - await sleep(this.workerOptions.elementStartDelay!); + await sleep(randomizeDelay(this.workerOptions.elementStartDelay!)); } } @@ -170,7 +171,7 @@ export class WorkerSet extends WorkerAbstract { chosenWorkerSetElement = this.addWorkerSetElement(); // Add worker set element sequentially to optimize memory at startup this.workerOptions.workerStartDelay! > 0 && - (await sleep(this.workerOptions.workerStartDelay!)); + (await sleep(randomizeDelay(this.workerOptions.workerStartDelay!))); } return chosenWorkerSetElement; } diff --git a/src/worker/WorkerStaticPool.ts b/src/worker/WorkerStaticPool.ts index acc61b8f..fb70bc43 100644 --- a/src/worker/WorkerStaticPool.ts +++ b/src/worker/WorkerStaticPool.ts @@ -2,7 +2,7 @@ import { FixedThreadPool, type PoolEmitter, type PoolInfo } from 'poolifier'; import { WorkerAbstract } from './WorkerAbstract'; import type { WorkerData, WorkerOptions } from './WorkerTypes'; -import { sleep } from './WorkerUtils'; +import { randomizeDelay, sleep } from './WorkerUtils'; export class WorkerStaticPool extends WorkerAbstract { private readonly pool: FixedThreadPool; @@ -53,6 +53,6 @@ export class WorkerStaticPool extends WorkerAbstract { await this.pool.execute(elementData); // Start element sequentially to optimize memory at startup this.workerOptions.elementStartDelay! > 0 && - (await sleep(this.workerOptions.elementStartDelay!)); + (await sleep(randomizeDelay(this.workerOptions.elementStartDelay!))); } } diff --git a/src/worker/WorkerUtils.ts b/src/worker/WorkerUtils.ts index e930a47f..18747673 100644 --- a/src/worker/WorkerUtils.ts +++ b/src/worker/WorkerUtils.ts @@ -1,3 +1,5 @@ +import { webcrypto } from 'node:crypto'; + import chalk from 'chalk'; export const sleep = async (milliSeconds: number): Promise => { @@ -17,3 +19,20 @@ export const defaultExitHandler = (code: number): void => { export const defaultErrorHandler = (error: Error): void => { console.error(chalk.red('Worker errored: '), error); }; + +export const randomizeDelay = (delay: number): number => { + const random = secureRandom(); + const sign = random < 0.5 ? -1 : 1; + const randomSum = delay * 0.2 * random; // 0-20% of the delay + return delay + sign * randomSum; +}; + +/** + * Generates a cryptographically secure random number in the [0,1[ range + * + * @returns A number in the [0,1[ range + * @internal + */ +const secureRandom = (): number => { + return webcrypto.getRandomValues(new Uint32Array(1))[0] / 0x100000000; +}; -- 2.34.1