* Flush all queued work for a context. Jobs with unmet dependencies are retried. * Throws if a pass completes without running any job (dependency cycle).
(contextId: SchedulerContextId)
| 108 | * Throws if a pass completes without running any job (dependency cycle). |
| 109 | */ |
| 110 | flush(contextId: SchedulerContextId): void { |
| 111 | const context = this.contexts.get(contextId) |
| 112 | if (!context) return |
| 113 | |
| 114 | const { queue, jobs, dependencies, completed } = context |
| 115 | |
| 116 | while (queue.length > 0) { |
| 117 | let ranThisPass = false |
| 118 | const jobsThisPass = queue.length |
| 119 | |
| 120 | for (let i = 0; i < jobsThisPass; i++) { |
| 121 | const jobId = queue.shift()! |
| 122 | const run = jobs.get(jobId) |
| 123 | if (!run) { |
| 124 | dependencies.delete(jobId) |
| 125 | completed.delete(jobId) |
| 126 | continue |
| 127 | } |
| 128 | |
| 129 | const deps = dependencies.get(jobId) |
| 130 | let ready = !deps |
| 131 | if (deps) { |
| 132 | ready = true |
| 133 | for (const dep of deps) { |
| 134 | if (dep === jobId) continue |
| 135 | |
| 136 | const depHasPending = |
| 137 | isPendingAwareJob(dep) && dep.hasPendingGraphRun(contextId) |
| 138 | |
| 139 | // Treat dependencies as blocking if the dep has a pending run in this |
| 140 | // context or if it's enqueued and not yet complete. If the dep is |
| 141 | // neither pending nor enqueued, consider it satisfied to avoid deadlocks |
| 142 | // on lazy sources that never schedule work. |
| 143 | if ( |
| 144 | (jobs.has(dep) && !completed.has(dep)) || |
| 145 | (!jobs.has(dep) && depHasPending) |
| 146 | ) { |
| 147 | ready = false |
| 148 | break |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | if (ready) { |
| 154 | jobs.delete(jobId) |
| 155 | dependencies.delete(jobId) |
| 156 | // Run the job. If it throws, we don't mark it complete, allowing the |
| 157 | // error to propagate while maintaining scheduler state consistency. |
| 158 | run() |
| 159 | completed.add(jobId) |
| 160 | ranThisPass = true |
| 161 | } else { |
| 162 | queue.push(jobId) |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | if (!ranThisPass) { |
| 167 | throw new Error( |
no test coverage detected