perf(watch): Build dependency graphs off the event loop - #170
Conversation
|
Went through this one carefully — it's the worker you sketched in #168, and the design holds up. No blocking findings. One request before I merge, at the bottom. What I verifiedThe published graph matches disk. Live daemon on a three-package Go module: Converges every time, no panics or races in the daemon log. The 40-edit burst is the case I most expected to expose a lost rebuild, and it coalesces correctly. The #168-class permanent-degradation guard is load-bearing. This was my main worry — that So the retry path is genuinely covered rather than incidentally working. Concurrency. Shutdown: because the worker never starts a build until its previous result has been both sent and consumed, the cap-1 Two non-blocking notes
For the record, The one thing I needCI is red on I did try to attribute it. Compiling test binaries for both branches up front and alternating them under CPU contention: 0/20 failures on main and 0/20 on this branch, then 0/25 each under heavier load. So I have no evidence this PR worsens it — but I also couldn't reproduce the flake at all locally, which means that's an absence of evidence, not a clean bill of health.
Happy to fold the two notes above into that same push, or leave them for later — your call. Generated by Claude Code |
b542229 to
be85ffc
Compare
Build post-start dependency graphs in one cancellable worker, reject stale snapshots, and retry coalesced invalidations without blocking event handling. Convert worker panics into failed builds and document event-loop ownership of the worker state.
Index case-insensitive subsystem prefixes in path order so bounded routing can stop without collecting and sorting every match. Remove the redundant duplicate guard and cover the indexed order.
Complied
Not fully complied
Other changes
|
be85ffc to
5f1d0a9
Compare
|
Re-verified at The remaining red check is not this PR's, and I can prove it
The proof it's a flake rather than a code failure: #169's PR head And the mechanism, since it's now cost two PRs a red check — const cargoMetadataTimeout = 3 * time.Second
...
ctx, cancel := context.WithTimeout(ctx, cargoMetadataTimeout)
output, err := cmd.Output()
if ctx.Err() != nil {
return nil, ctx.Err() // raw "context deadline exceeded"
}That raw On the extra changes in this pushThe rebase also folded in both nits from my #169 review — dropping the redundant early duplicate check, and replacing the case-insensitive materialize-then-sort with a precomputed They're correct. Output is identical to main on the same boundary fixture I used for #169 ( That works because the map is built from One thing I'd flag: The old code did two binary searches and materialized only matches, so this trades a lazy O(matches) for an eager O(files × depth). It's transient and Linux skips it entirely, so I'm not blocking — but on a 50k-file macOS monorepo that's 28 MB per editor-hook invocation, which is a worse deal than the per-prefix sort it replaced. Bounding it by topK, or building it lazily on first use, would keep the win without the eager cost. Verification on
|
What does this PR do?
This keeps the watcher responsive without publishing stale or mismatched dependency graphs.
Type of change
Checklist
go build && ./codemap .go test ./...andgo test -race ./...go vet ./...Additional notes
Initial dependency graph construction remains synchronous; only post-start rebuilds move off the event loop. No new commands, arguments, or MCP tools are added.