fix: fixes to pool initialization
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 25 Oct 2023 15:20:39 +0000 (17:20 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 25 Oct 2023 15:20:39 +0000 (17:20 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
CHANGELOG.md
src/pools/abstract-pool.ts
src/utils.ts
tests/utils.test.mjs

index 25e94333b5d3dfa89cf6bcc26fe1ab6c463985b1..eb0fa9cd8f46057b48f1ac7908d3920e141d8222 100644 (file)
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Fixed
+
+- Ensure worker ready response can be received only once.
+- Ensure pool ready event can be emitted only once.
+
 ## [3.0.4] - 2023-10-20
 
 ### Changed
index 00cf56171f181184ff93fe9f24b4a107af188fee..1140ca1b4c289e6a31057528d88c6b7e638ae144 100644 (file)
@@ -17,6 +17,7 @@ import {
   max,
   median,
   min,
+  once,
   round
 } from '../utils'
 import { KillBehaviors } from '../worker/worker-options'
@@ -1539,10 +1540,21 @@ export abstract class AbstractPool<
     const workerInfo = this.getWorkerInfo(
       this.getWorkerNodeKeyByWorkerId(message.workerId)
     )
+    if (!this.started && workerInfo.ready) {
+      throw new Error(
+        `Ready response already received by worker ${
+          message.workerId as number
+        }`
+      )
+    }
     workerInfo.ready = message.ready as boolean
     workerInfo.taskFunctionNames = message.taskFunctionNames
     if (this.ready) {
-      this.emitter?.emit(PoolEvents.ready, this.info)
+      const emitPoolReadyEventOnce = once(
+        () => this.emitter?.emit(PoolEvents.ready, this.info),
+        this
+      )
+      emitPoolReadyEventOnce()
     }
   }
 
index d7ce5a210f2dea09290fd90a5f4eb64fcb2162d5..a0a9e530131cbe78df7401dff80452eef16e8634 100644 (file)
@@ -250,3 +250,27 @@ export const min = (...args: number[]): number =>
  */
 export const max = (...args: number[]): number =>
   args.reduce((maximum, num) => (maximum > num ? maximum : num), -Infinity)
+
+/**
+ * Wraps a function so that it can only be called once.
+ *
+ * @param fn - The function to wrap.
+ * @param context - The context to bind the function to.
+ * @returns The wrapped function.
+ * @internal
+ */
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const once = <T, A extends any[], R>(
+  fn: (...args: A) => R,
+  context: T
+): ((...args: A) => R) => {
+  let result: R
+  return (...args: A) => {
+    if (fn != null) {
+      result = fn.apply<T, A, R>(context, args)
+      ;(fn as unknown as undefined) = (context as unknown as undefined) =
+        undefined
+    }
+    return result
+  }
+}
index c048141bf2ffcd7aab57de5d9b7aaa178d630137..ac544f89c5a2f504a6c768c212461e30a8467ee9 100644 (file)
@@ -19,6 +19,7 @@ import {
   max,
   median,
   min,
+  once,
   round,
   secureRandom,
   sleep
@@ -237,4 +238,19 @@ describe('Utils test suite', () => {
     expect(max(2, 1)).toBe(2)
     expect(max(1, 1)).toBe(1)
   })
+
+  it('Verify once()', () => {
+    let called = 0
+    const fn = () => ++called
+    const onceFn = once(fn, this)
+    const result1 = onceFn()
+    expect(called).toBe(1)
+    expect(result1).toBe(1)
+    const result2 = onceFn()
+    expect(called).toBe(1)
+    expect(result2).toBe(1)
+    const result3 = onceFn()
+    expect(called).toBe(1)
+    expect(result3).toBe(1)
+  })
 })