fix(ui): show synthesis as a 'Linking dynamic dispatch' phase; mute node:sqlite warning spam (#1299)
Two first-run UX bugs surfaced by indexing a real 1,342-file C repo: 1. After 'Resolving refs' hit 100%, the ~40 dynamic-dispatch synthesis passes ran with no progress surface, so the bar sat frozen at 100% long enough to read as a hang (the C fn-pointer pass alone can hold for a while on C-heavy repos). Synthesis now reports per-pass progress through a new 'linking' IndexProgress phase, rendered as 'Linking dynamic dispatch'. The step total is pinned by a test to the synthesizer's actual __mark() count so adding a pass without bumping it fails loudly. 2. node:sqlite's ExperimentalWarning is emitted once per THREAD, so the main process plus every parse worker printed it mid-index, interleaved with the progress UI. All launch paths now pass --disable-warning=ExperimentalWarning: both bundle launchers, the Windows npm-shim invocation, and the CLI self-relaunch (NODE_RUNTIME_FLAGS, deliberately excluded from the re-exec gate so an older installed launcher never triggers a pointless re-exec, and version-gated off nodes older than the flag). Verified end-to-end on the same repo: zero warnings, live linking bar, byte-identical graph (50,520 nodes / 148,232 edges). Full suite green. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
243ef1d3e2
commit
ad5300a601
@@ -3446,7 +3446,20 @@ async function laravelEventEdges(ctx: ResolutionContext, onYield: MaybeYield): P
|
||||
* Sidekiq Worker.perform_async → #perform + Laravel event(new X) → listener handle).
|
||||
* Returns the count added. Never throws into indexing — callers wrap in try/catch.
|
||||
*/
|
||||
export async function synthesizeCallbackEdges(queries: QueryBuilder, ctx: ResolutionContext): Promise<number> {
|
||||
|
||||
/**
|
||||
* Number of progress steps synthesizeCallbackEdges reports: one per `__mark()`
|
||||
* call (every synthesis pass, plus the dedupe-merge and edge-insert steps).
|
||||
* Cosmetic only — drift just makes the progress bar end early or jump — and a
|
||||
* test pins it to the actual `__mark(` call count so adding a pass without
|
||||
* bumping this fails loudly instead of silently skewing the bar.
|
||||
*/
|
||||
export const SYNTH_PROGRESS_STEPS = 40;
|
||||
export async function synthesizeCallbackEdges(
|
||||
queries: QueryBuilder,
|
||||
ctx: ResolutionContext,
|
||||
onProgress?: (done: number, total: number) => void
|
||||
): Promise<number> {
|
||||
// Each sub-pass below is a whole-graph scan, and there are ~30 of them, all
|
||||
// running synchronously on the indexer's main thread. Their AGGREGATE can run
|
||||
// for well over a minute on a large repo — long enough for the #850 liveness
|
||||
@@ -3456,6 +3469,14 @@ export async function synthesizeCallbackEdges(queries: QueryBuilder, ctx: Resolu
|
||||
// watchdog still catches that. See ./cooperative-yield.
|
||||
const yieldToLoop = createYielder();
|
||||
|
||||
// Synthesis runs AFTER the resolution progress bar reaches 100%, so without
|
||||
// its own progress the UI freezes at "Resolving refs 100%" for the whole
|
||||
// tail — long enough on big repos that users conclude the index hung and
|
||||
// kill it. Report each completed pass; the caller surfaces it as its own
|
||||
// progress phase. Emit 0/total up front so the phase flips immediately.
|
||||
let passesDone = 0;
|
||||
onProgress?.(0, SYNTH_PROGRESS_STEPS);
|
||||
|
||||
// Per-pass wall-clock timing to stderr, opt-in via CODEGRAPH_SYNTH_TIMINGS
|
||||
// (=1: passes over 250ms; =all: every pass). This is the diagnostic that
|
||||
// located both the #1091/#1122 watchdog stalls and the #1212 OOM — keep it.
|
||||
@@ -3467,6 +3488,8 @@ export async function synthesizeCallbackEdges(queries: QueryBuilder, ctx: Resolu
|
||||
if (process.env.CODEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.CODEGRAPH_SYNTH_TIMINGS === 'all')) {
|
||||
console.error(`[synth-timing] ${label}: ${dt}ms`);
|
||||
}
|
||||
passesDone++;
|
||||
onProgress?.(Math.min(passesDone, SYNTH_PROGRESS_STEPS), SYNTH_PROGRESS_STEPS);
|
||||
};
|
||||
|
||||
// Language gating: one indexed DISTINCT over the files table lets a pass
|
||||
|
||||
@@ -1258,7 +1258,8 @@ export class ReferenceResolver {
|
||||
*/
|
||||
async resolveAndPersistBatched(
|
||||
onProgress?: (current: number, total: number) => void,
|
||||
batchSize: number = 5000
|
||||
batchSize: number = 5000,
|
||||
onSynthesisProgress?: (done: number, total: number) => void
|
||||
): Promise<ResolutionResult> {
|
||||
// Resolution runs on the indexer's MAIN thread, and the #850 liveness
|
||||
// watchdog SIGKILLs a process whose event loop stalls past its window (60s
|
||||
@@ -1375,7 +1376,11 @@ export class ReferenceResolver {
|
||||
// callbacks) that static parsing leaves out. Best-effort — never fail the
|
||||
// index on it. See docs/design/callback-edge-synthesis.md.
|
||||
try {
|
||||
aggregateStats.byMethod['callback-synthesis'] = await synthesizeCallbackEdges(this.queries, this.context);
|
||||
aggregateStats.byMethod['callback-synthesis'] = await synthesizeCallbackEdges(
|
||||
this.queries,
|
||||
this.context,
|
||||
onSynthesisProgress
|
||||
);
|
||||
} catch {
|
||||
// synthesis is additive and optional; ignore failures
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user