fix: guarantee the minimun number of workers on started pool
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 1 Apr 2024 11:16:22 +0000 (13:16 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 1 Apr 2024 11:16:22 +0000 (13:16 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
CHANGELOG.md
examples/typescript/http-server-pool/express-cluster/pnpm-lock.yaml
examples/typescript/http-server-pool/express-hybrid/pnpm-lock.yaml
examples/typescript/http-server-pool/express-worker_threads/pnpm-lock.yaml
src/pools/abstract-pool.ts
tests/pools/cluster/fixed.test.mjs
tests/pools/thread/fixed.test.mjs

index 6d88f0ba25780bb0a1330f9ed41c84fb371bd413..3e0ca37839193663b12fd61d1b33ac2cb586000d 100644 (file)
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Fixed
+
+- Ensure the minimum number of workers on a started pool is guaranteed.
+
 ## [3.1.27] - 2024-03-27
 
 ### Fixed
index 49bb317db7ca7cfc7e42737bddef539b645da21e..e91a600061c3edbde413a64000495405e59862bb 100644 (file)
@@ -277,8 +277,11 @@ packages:
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
     dev: true
 
-  /@types/mime@3.0.4:
-    resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
+  /@types/mime@4.0.0:
+    resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==}
+    deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed.
+    dependencies:
+      mime: 4.0.1
     dev: true
 
   /@types/minimatch@5.1.2:
@@ -310,7 +313,7 @@ packages:
     resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==}
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/mime': 3.0.4
+      '@types/mime': 4.0.0
       '@types/node': 20.12.2
     dev: true
 
@@ -993,6 +996,12 @@ packages:
     hasBin: true
     dev: false
 
+  /mime@4.0.1:
+    resolution: {integrity: sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==}
+    engines: {node: '>=16'}
+    hasBin: true
+    dev: true
+
   /minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
     dependencies:
index 49bb317db7ca7cfc7e42737bddef539b645da21e..e91a600061c3edbde413a64000495405e59862bb 100644 (file)
@@ -277,8 +277,11 @@ packages:
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
     dev: true
 
-  /@types/mime@3.0.4:
-    resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
+  /@types/mime@4.0.0:
+    resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==}
+    deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed.
+    dependencies:
+      mime: 4.0.1
     dev: true
 
   /@types/minimatch@5.1.2:
@@ -310,7 +313,7 @@ packages:
     resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==}
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/mime': 3.0.4
+      '@types/mime': 4.0.0
       '@types/node': 20.12.2
     dev: true
 
@@ -993,6 +996,12 @@ packages:
     hasBin: true
     dev: false
 
+  /mime@4.0.1:
+    resolution: {integrity: sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==}
+    engines: {node: '>=16'}
+    hasBin: true
+    dev: true
+
   /minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
     dependencies:
index f778f3670815f6309e8eacaf6dcd570378281428..c9fa1b6a1dba0a30b4e87770b645f262c3241aa4 100644 (file)
@@ -78,8 +78,11 @@ packages:
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
     dev: true
 
-  /@types/mime@3.0.4:
-    resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
+  /@types/mime@4.0.0:
+    resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==}
+    deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed.
+    dependencies:
+      mime: 4.0.1
     dev: true
 
   /@types/node@20.12.2:
@@ -107,7 +110,7 @@ packages:
     resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==}
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/mime': 3.0.4
+      '@types/mime': 4.0.0
       '@types/node': 20.12.2
     dev: true
 
@@ -593,6 +596,12 @@ packages:
     hasBin: true
     dev: false
 
+  /mime@4.0.1:
+    resolution: {integrity: sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==}
+    engines: {node: '>=16'}
+    hasBin: true
+    dev: true
+
   /minimist@1.2.8:
     resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
     dev: true
index b7b30161ac7676511f0805d56a5a9284299c7f66..9610f3416d13a368b6f7f5a605d70c4689b30176 100644 (file)
@@ -949,6 +949,21 @@ export abstract class AbstractPool<
     })
   }
 
+  /**
+   * Starts the minimum number of workers.
+   */
+  private startMinimumNumberOfWorkers (): void {
+    while (
+      this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          !workerNode.info.dynamic ? accumulator + 1 : accumulator,
+        0
+      ) < this.minimumNumberOfWorkers
+    ) {
+      this.createAndSetupWorkerNode()
+    }
+  }
+
   /** @inheritdoc */
   public start (): void {
     if (this.started) {
@@ -961,15 +976,7 @@ export abstract class AbstractPool<
       throw new Error('Cannot start a destroying pool')
     }
     this.starting = true
-    while (
-      this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          !workerNode.info.dynamic ? accumulator + 1 : accumulator,
-        0
-      ) < this.minimumNumberOfWorkers
-    ) {
-      this.createAndSetupWorkerNode()
-    }
+    this.startMinimumNumberOfWorkers()
     this.starting = false
     this.started = true
   }
@@ -1266,6 +1273,9 @@ export abstract class AbstractPool<
     )
     workerNode.registerOnceWorkerEventHandler('exit', () => {
       this.removeWorkerNode(workerNode)
+      if (this.started && !this.destroying) {
+        this.startMinimumNumberOfWorkers()
+      }
     })
     const workerNodeKey = this.addWorkerNode(workerNode)
     this.afterWorkerNodeSetup(workerNodeKey)
index 794da1c04ccad4dae92b30f0c66c717734d39629..ad7c003a97e675687b6ceb2df155cbc72a784591 100644 (file)
@@ -327,7 +327,8 @@ describe('Fixed cluster pool test suite', () => {
     await expect(pool.destroyWorkerNode(workerNodeKey)).resolves.toBeUndefined()
     expect(disconnectEvent).toBe(1)
     expect(exitEvent).toBe(1)
-    expect(pool.workerNodes.length).toBe(numberOfWorkers - 1)
+    // Simulates an illegitimate worker node destroy and the minimum number of worker nodes is guaranteed
+    expect(pool.workerNodes.length).toBe(numberOfWorkers)
     await pool.destroy()
   })
 
index 48f19e4d0f3fae3e586242517a172da199675b5b..3da05735588953cf6c0882a30aa86a231dd8b841 100644 (file)
@@ -348,7 +348,8 @@ describe('Fixed thread pool test suite', () => {
     })
     await expect(pool.destroyWorkerNode(workerNodeKey)).resolves.toBeUndefined()
     expect(exitEvent).toBe(1)
-    expect(pool.workerNodes.length).toBe(numberOfThreads - 1)
+    // Simulates an illegitimate worker node destroy and the minimum number of worker nodes is guaranteed
+    expect(pool.workerNodes.length).toBe(numberOfThreads)
     await pool.destroy()
   })