diff-sync: stop reconcile churn spawning git diff back-to-back forever - #127
Open
danperks wants to merge 1 commit into
Open
diff-sync: stop reconcile churn spawning git diff back-to-back forever#127danperks wants to merge 1 commit into
danperks wants to merge 1 commit into
Conversation
An idle checkout could pin a core indefinitely: reconcile runs on every workspace chat row change — including the branch/checkoutId writes sync_entry itself makes — and resolved each chat's checkout identity by spawning git. Under that fd pressure a transient spawn failure made the chat ungroupable, so reconcile tore the entry down and re-added it next pass, and every re-add kicks a full capture whose row writes trigger the next reconcile. On a big checkout each pass costs seconds of CPU. Damp reconcile rather than treat the symptom: memoize identities per cwd (chat-watch passes spawn no git, the repair tick revalidates), tear an entry down only after a full grace period of continuous absence, and serialize passes so two of them can't both add the same entry. Watcher setup also ran inline on the runtime, where the budget walk and FSEvents registration block for seconds and starved workspace watches and presence for everyone. It moves to the blocking pool in both diff-sync and spaces, attaching to the entry when ready with a kick to close the gap. Repair tick, watch budget bail-out and checksum-suppressed publishing are unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What happened
On an idle checkout, the engine spawned
git diffback-to-back forever — each pass burning ~100% of a core for 1–3s, a new one starting the moment the last exited. Enough to starve the machine (WindowServer at 99%, UI hover lag). Nobody was editing files in the checkout.Root cause
Two mechanisms combined into a self-sustaining loop:
reconcileruns on every workspace chat row change — including thebranchandcheckoutIdwritessync_entryitself makes — and resolved each chat's checkout identity by spawninggit rev-parsetwice. Every publish fanned out into a storm ofgitspawns.Teardown discards the entry's checksum state, so the re-capture always republishes, which always triggers the next reconcile. On a large checkout each pass costs seconds of CPU, so the loop never runs out of work.
Separately, watcher setup ran inline on the runtime: the budget walk reads up to
MAX_WATCH_DIRSdirectory entries and FSEvents registration stalls for seconds under contention, starving workspace watches and presence for every checkout whenever entries were rebuilt.The fix
Damp reconcile rather than treat the symptom — the
apply_numstatcomment records a previous fix for the same runaway symptom, so treating symptoms here is a known failure mode.gitat all; the repair tick revalidates. A failed fresh resolve keeps the memo unless the directory is actually gone.REPAIR_INTERVALof continuous absence. A single flapping chat-watch emission can no longer destroy a live entry.add_entryit, the second insert silently discarding the first's checksum state.Repair tick, watch budget bail-out and checksum-suppressed publishing are unchanged. Nothing is special-cased by path or file count — a pathological checkout made this visible, but any checkout can trigger it.
Testing
crates/engine/tests/diff_sync_churn.rscovers all three dampers. Verified they actually pin the fix by mutating each damper away: with pre-fix behaviour restored,entry_survives_transient_identity_failurefails with exactly the incident signature (updated_atmoves — the entry was torn down and re-captured), andchat_flap_keeps_entry_and_sustained_absence_removes_itfails on the entry being removed after a single missed pass.deleted_checkout_is_evicted_after_gracepasses both ways by design: it guards that the dampers don't make legitimate eviction impossible.Full
cargo test -p zeron-engineis green (all suites, including 20/20 inm5_repos_diffs_terminals).cargo fmt --checkclean; clippy adds no new warnings in the touched files.One test deadline moved:
diff_sync_publishes_and_updates_chat_branch's watcher-path wait went 20s → 150s. FSEvents is permitted to drop events, and a dropped one converges only on the 120s repair tick, so the old deadline read normal event loss as failure.What this doesn't cover
Made with Cursor
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.