(
queries: QueryBuilder,
ctx: ResolutionContext,
onProgress?: (done: number, total: number) => void,
// A live resolver pool to fan the independent passes across (structural type
// so this file never imports the pool — resolver-worker imports THIS file).
// Null/omitted → the sequential path, byte-identical to the pool path.
pool?: { runSynthPass(name: string): Promise<{ edges: Edge[]; ms: number }> } | null,
// WAL-valve writer backstop (WalCheckpointValve.backpressure), called at
// pool-idle points in the edge-insert loops below — the passes themselves
// only read; every write in this function happens with the pool idle.
backpressure?: () => Promise<void> | null
)
| 3594 | const FIXED_SYNTH_STEPS = 4; |
| 3595 | export const SYNTH_PROGRESS_STEPS = SYNTH_PASSES.length + FIXED_SYNTH_STEPS; |
| 3596 | export async function synthesizeCallbackEdges( |
| 3597 | queries: QueryBuilder, |
| 3598 | ctx: ResolutionContext, |
| 3599 | onProgress?: (done: number, total: number) => void, |
| 3600 | // A live resolver pool to fan the independent passes across (structural type |
| 3601 | // so this file never imports the pool — resolver-worker imports THIS file). |
| 3602 | // Null/omitted → the sequential path, byte-identical to the pool path. |
| 3603 | pool?: { runSynthPass(name: string): Promise<{ edges: Edge[]; ms: number }> } | null, |
| 3604 | // WAL-valve writer backstop (WalCheckpointValve.backpressure), called at |
| 3605 | // pool-idle points in the edge-insert loops below — the passes themselves |
| 3606 | // only read; every write in this function happens with the pool idle. |
| 3607 | backpressure?: () => Promise<void> | null |
| 3608 | ): Promise<number> { |
| 3609 | // Each sub-pass below is a whole-graph scan, and there are ~30 of them, all |
| 3610 | // running synchronously on the indexer's main thread. Their AGGREGATE can run |
| 3611 | // for well over a minute on a large repo — long enough for the #850 liveness |
| 3612 | // watchdog to SIGKILL the process mid-index (#1091), since its heartbeat lives |
| 3613 | // on this same thread. Yield between passes so the heartbeat can fire; a pass |
| 3614 | // that itself hangs (a real wedge) never reaches the next yield, so the |
| 3615 | // watchdog still catches that. See ./cooperative-yield. |
| 3616 | const yieldToLoop = createYielder(); |
| 3617 | |
| 3618 | // Synthesis runs AFTER the resolution progress bar reaches 100%, so without |
| 3619 | // its own progress the UI freezes at "Resolving refs 100%" for the whole |
| 3620 | // tail — long enough on big repos that users conclude the index hung and |
| 3621 | // kill it. Report each completed pass; the caller surfaces it as its own |
| 3622 | // progress phase. Emit 0/total up front so the phase flips immediately. |
| 3623 | // Emissions are throttled to whole-percent movement (each consumes a UI |
| 3624 | // message); values may be fractional steps from within-pass reporting. |
| 3625 | let passesDone = 0; |
| 3626 | let lastPct = -1; |
| 3627 | const emit = (value: number): void => { |
| 3628 | if (!onProgress) return; |
| 3629 | const v = Math.min(value, SYNTH_PROGRESS_STEPS); |
| 3630 | const pct = Math.floor((v / SYNTH_PROGRESS_STEPS) * 100); |
| 3631 | if (pct === lastPct) return; |
| 3632 | lastPct = pct; |
| 3633 | onProgress(v, SYNTH_PROGRESS_STEPS); |
| 3634 | }; |
| 3635 | // A single long pass otherwise parks the bar between steps; a pass that |
| 3636 | // takes this callback reports a 0..1 fraction of its own work, surfaced |
| 3637 | // here as fractional progress within its step. |
| 3638 | const subProgress = (fraction: number): void => |
| 3639 | emit(passesDone + Math.max(0, Math.min(fraction, 1))); |
| 3640 | emit(0); |
| 3641 | |
| 3642 | // Per-pass wall-clock timing to stderr, opt-in via CODEGRAPH_SYNTH_TIMINGS |
| 3643 | // (=1: passes over 250ms; =all: every pass). This is the diagnostic that |
| 3644 | // located both the #1091/#1122 watchdog stalls and the #1212 OOM — keep it. |
| 3645 | const markT = { t: Date.now() }; |
| 3646 | const __mark = (label: string): void => { |
| 3647 | const now = Date.now(); |
| 3648 | const dt = now - markT.t; |
| 3649 | markT.t = now; |
| 3650 | if (process.env.CODEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.CODEGRAPH_SYNTH_TIMINGS === 'all')) { |
| 3651 | console.error(`[synth-timing] ${label}: ${dt}ms`); |
| 3652 | } |
| 3653 | passesDone++; |
no test coverage detected