Run the whole graph. Resolves when quiescent.
()
| 219 | |
| 220 | /** Run the whole graph. Resolves when quiescent. */ |
| 221 | async run(): Promise<{ conflicts: ConflictRecord[] }> { |
| 222 | // Initialize: nodes with no deps start ready. |
| 223 | for (const node of this.graph.nodes.values()) { |
| 224 | node.status = node.dependsOn.length === 0 ? 'ready' : 'pending'; |
| 225 | } |
| 226 | |
| 227 | const inFlight = new Set<Promise<void>>(); |
| 228 | |
| 229 | while (!this.quiescent()) { |
| 230 | if (this.opts.signal?.aborted) break; |
| 231 | this.refreshStatuses(); |
| 232 | |
| 233 | const slots = this.opts.maxConcurrency - inFlight.size; |
| 234 | if (slots > 0) { |
| 235 | const runnable = this.selectRunnable(slots); |
| 236 | for (const node of runnable) { |
| 237 | const p = this.processNode(node).finally(() => inFlight.delete(p)); |
| 238 | inFlight.add(p); |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | if (inFlight.size === 0) { |
| 243 | // Nothing running and nothing runnable — either done or deadlocked. |
| 244 | this.refreshStatuses(); |
| 245 | if (this.selectRunnable(1).length === 0) break; |
| 246 | continue; |
| 247 | } |
| 248 | |
| 249 | // Wait for the next node to finish, then re-evaluate. |
| 250 | await Promise.race(inFlight); |
| 251 | } |
| 252 | |
| 253 | // Drain any stragglers. |
| 254 | await Promise.allSettled(inFlight); |
| 255 | |
| 256 | // Final pass: any node still pending whose deps failed/blocked must be |
| 257 | // marked blocked. We didn't reach these inside the loop because quiescent() |
| 258 | // treats a pending-with-broken-deps node as non-advancing. Propagate |
| 259 | // repeatedly so a chain (a fails → b blocked → c blocked) fully resolves. |
| 260 | let changed = true; |
| 261 | while (changed) { |
| 262 | changed = false; |
| 263 | for (const node of this.graph.nodes.values()) { |
| 264 | if (node.status !== 'pending') continue; |
| 265 | const broken = this.depsBroken(node); |
| 266 | if (broken) { |
| 267 | node.status = 'blocked'; |
| 268 | this.emit({ type: 'node-blocked', id: node.id, cause: broken }); |
| 269 | changed = true; |
| 270 | } |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | return { conflicts: this.conflicts }; |
| 275 | } |
| 276 | |
| 277 | statusCounts(): Record<TaskStatus, number> { |
| 278 | const counts = { |
no test coverage detected