chore: v2.6.32
[poolifier.git] / src / pools / abstract-pool.ts
index 1926a4a426fbcb84724ab55749778ff95c5910ee..a4d83b593cd52b4927aa480f557227e788d6cfeb 100644 (file)
@@ -93,6 +93,10 @@ export abstract class AbstractPool<
    * Whether the pool is starting or not.
    */
   private readonly starting: boolean
+  /**
+   * Whether the pool is started or not.
+   */
+  private started: boolean
   /**
    * The start timestamp of the pool.
    */
@@ -141,6 +145,7 @@ export abstract class AbstractPool<
     this.starting = true
     this.startPool()
     this.starting = false
+    this.started = true
 
     this.startTimestamp = performance.now()
   }
@@ -723,6 +728,9 @@ export abstract class AbstractPool<
     transferList?: TransferListItem[]
   ): Promise<Response> {
     return await new Promise<Response>((resolve, reject) => {
+      if (!this.started) {
+        reject(new Error('Cannot execute a task on destroyed pool'))
+      }
       if (name != null && typeof name !== 'string') {
         reject(new TypeError('name argument must be a string'))
       }
@@ -783,6 +791,7 @@ export abstract class AbstractPool<
       })
     )
     this.emitter?.emit(PoolEvents.destroy, this.info)
+    this.started = false
   }
 
   protected async sendKillMessageToWorker (
@@ -912,13 +921,6 @@ export abstract class AbstractPool<
       workerTaskStatistics.executing > 0
     ) {
       --workerTaskStatistics.executing
-    } else if (
-      workerTaskStatistics.executing != null &&
-      workerTaskStatistics.executing < 0
-    ) {
-      throw new Error(
-        'Worker usage statistic for tasks executing cannot be negative'
-      )
     }
     if (message.taskError == null) {
       ++workerTaskStatistics.executed
@@ -1050,7 +1052,11 @@ export abstract class AbstractPool<
       workerInfo.ready = false
       this.workerNodes[workerNodeKey].closeChannel()
       this.emitter?.emit(PoolEvents.error, error)
-      if (this.opts.restartWorkerOnError === true && !this.starting) {
+      if (
+        this.opts.restartWorkerOnError === true &&
+        !this.starting &&
+        this.started
+      ) {
         if (workerInfo.dynamic) {
           this.createAndSetupDynamicWorkerNode()
         } else {
@@ -1142,6 +1148,10 @@ export abstract class AbstractPool<
     this.sendStartupMessageToWorker(workerNodeKey)
     // Send the statistics message to worker.
     this.sendStatisticsMessageToWorker(workerNodeKey)
+    if (this.opts.enableTasksQueue === true) {
+      this.workerNodes[workerNodeKey].onBackPressure =
+        this.tasksStealingOnBackPressure.bind(this)
+    }
   }
 
   /**
@@ -1170,29 +1180,26 @@ export abstract class AbstractPool<
   }
 
   private redistributeQueuedTasks (workerNodeKey: number): void {
+    const workerNodes = this.workerNodes.filter(
+      (_, workerNodeId) => workerNodeId !== workerNodeKey
+    )
     while (this.tasksQueueSize(workerNodeKey) > 0) {
       let targetWorkerNodeKey: number = workerNodeKey
       let minQueuedTasks = Infinity
       let executeTask = false
-      for (const [workerNodeId, workerNode] of this.workerNodes.entries()) {
-        const workerInfo = this.getWorkerInfo(workerNodeId) as WorkerInfo
+      for (const [workerNodeId, workerNode] of workerNodes.entries()) {
         if (
-          workerNodeId !== workerNodeKey &&
-          workerInfo.ready &&
-          workerNode.usage.tasks.queued === 0
+          workerNode.usage.tasks.executing <
+          (this.opts.tasksQueueOptions?.concurrency as number)
         ) {
-          if (
-            this.workerNodes[workerNodeId].usage.tasks.executing <
-            (this.opts.tasksQueueOptions?.concurrency as number)
-          ) {
-            executeTask = true
-          }
+          executeTask = true
+        }
+        if (workerNode.info.ready && workerNode.usage.tasks.queued === 0) {
           targetWorkerNodeKey = workerNodeId
           break
         }
         if (
-          workerNodeId !== workerNodeKey &&
-          workerInfo.ready &&
+          workerNode.info.ready &&
           workerNode.usage.tasks.queued < minQueuedTasks
         ) {
           minQueuedTasks = workerNode.usage.tasks.queued
@@ -1213,6 +1220,39 @@ export abstract class AbstractPool<
     }
   }
 
+  private tasksStealingOnBackPressure (workerId: number): void {
+    const sourceWorkerNode =
+      this.workerNodes[this.getWorkerNodeKeyByWorkerId(workerId)]
+    const workerNodes = this.workerNodes
+      .filter((workerNode) => workerNode.info.id !== workerId)
+      .sort(
+        (workerNodeA, workerNodeB) =>
+          workerNodeA.usage.tasks.queued - workerNodeB.usage.tasks.queued
+      )
+    for (const [workerNodeKey, workerNode] of workerNodes.entries()) {
+      if (
+        workerNode.info.ready &&
+        sourceWorkerNode.usage.tasks.queued > 0 &&
+        !workerNode.hasBackPressure()
+      ) {
+        if (
+          workerNode.usage.tasks.executing <
+          (this.opts.tasksQueueOptions?.concurrency as number)
+        ) {
+          this.executeTask(
+            workerNodeKey,
+            sourceWorkerNode.popTask() as Task<Data>
+          )
+        } else {
+          this.enqueueTask(
+            workerNodeKey,
+            sourceWorkerNode.popTask() as Task<Data>
+          )
+        }
+      }
+    }
+  }
+
   /**
    * This method is the listener registered for each worker message.
    *