(groupJid: string, taskId: string, fn: () => Promise<void>)
| 96 | } |
| 97 | |
| 98 | enqueueTask(groupJid: string, taskId: string, fn: () => Promise<void>): void { |
| 99 | if (this.shuttingDown) return; |
| 100 | |
| 101 | const state = this.getGroup(groupJid); |
| 102 | |
| 103 | // Prevent double-queuing: check both pending and currently-running task |
| 104 | if (state.runningTaskId === taskId) { |
| 105 | logger.debug({ groupJid, taskId }, 'Task already running, skipping'); |
| 106 | return; |
| 107 | } |
| 108 | if (state.pendingTasks.some((t) => t.id === taskId)) { |
| 109 | logger.debug({ groupJid, taskId }, 'Task already queued, skipping'); |
| 110 | return; |
| 111 | } |
| 112 | |
| 113 | if (state.active) { |
| 114 | state.pendingTasks.push({ id: taskId, groupJid, fn }); |
| 115 | if (state.idleWaiting) { |
| 116 | this.closeStdin(groupJid); |
| 117 | } |
| 118 | logger.debug({ groupJid, taskId }, 'Container active, task queued'); |
| 119 | return; |
| 120 | } |
| 121 | |
| 122 | if (this.activeCount >= MAX_CONCURRENT_CONTAINERS) { |
| 123 | state.pendingTasks.push({ id: taskId, groupJid, fn }); |
| 124 | if (!this.waitingGroups.includes(groupJid)) { |
| 125 | this.waitingGroups.push(groupJid); |
| 126 | } |
| 127 | logger.debug( |
| 128 | { groupJid, taskId, activeCount: this.activeCount }, |
| 129 | 'At concurrency limit, task queued', |
| 130 | ); |
| 131 | return; |
| 132 | } |
| 133 | |
| 134 | // Run immediately |
| 135 | this.runTask(groupJid, { id: taskId, groupJid, fn }).catch((err) => |
| 136 | logger.error({ groupJid, taskId, err }, 'Unhandled error in runTask'), |
| 137 | ); |
| 138 | } |
| 139 | |
| 140 | registerProcess( |
| 141 | groupJid: string, |
no test coverage detected