Repository navigation
fix(mcp): keep ADR reads nonblocking during reindex - #892
Conversation
42b1878 to
c52e266
Compare
c52e266 to
ba39ffd
Compare
|
Thanks for the process-serialization work. Triage: high-priority stability bug. Review will focus on lock scope and failure behavior: same-project serialization without blocking unrelated projects, no watcher/self-lock deadlocks, safe stale-store reopen behavior, and ADR migration/write ordering across independent MCP/CLI processes. |
|
Thanks — this addresses a real gap (independent MCP/CLI processes sharing one CBM_CACHE_DIR had no cross-process serialization), and the lock hygiene is right: 0600 lock files under the cache dir, FD_CLOEXEC, unlock-before-close, per-project scoping so unrelated indexes don't serialize, and project names are sanitized upstream so the lock filename is traversal-safe. The fork-based cross-process tests are a nice touch. Two changes requested before merge:
Everything else looks merge-ready: 19/19 checks green, clean mergeable state against current main (impressive, given how much |
ba39ffd to
0a8b63e
Compare
|
Addressed both requested changes in 0a8b63e: test setup now fails on mkdtemp errors, and the Windows lock limitation is explicit with a debug log. Also tightened cancellation and test cleanup. |
|
Thanks @SS-42 — reviewed in depth and the mechanism is sound: POSIX
The Windows no-op is fine as documented (it's not a regression — there was no cross-process lock there before). Really appreciate this. |
… reclaimed On-disk write connections set no journal_size_limit, so the -wal file is never physically shrunk: all our checkpoints are PASSIVE (TRUNCATE is deliberately avoided because its ftruncate(fd,0) can SIGBUS a sibling mmap'd process on macOS), and PASSIVE never ftruncates. Under checkpoint starvation the WAL then grows without bound (reported: 115 GB, filled the drive) and even a later successful checkpoint leaves the file at its peak size (DeusData#1083). Set journal_size_limit = 256 MiB on write connections so SQLite truncates the -wal back to that bound whenever a checkpoint can finally reset the log — reclaiming disk opportunistically during a session, not only on clean close. 256 MiB is far above the healthy WAL (~4 MiB under the default 1000-page autocheckpoint), so normal indexing never triggers truncate/regrow churn; the limit only fires after abnormal growth. No TRUNCATE checkpoint is introduced. Scope: this is the *reclaim* half of DeusData#1083. It does not prevent the peak under continuous reader contention (a checkpoint can't reset the log while a reader holds a mark) — the write-amplification driver (concurrent full re-indexes of one repo) is serialized by the cross-process pipeline lock in DeusData#892. Test: a file-backed write store now reports journal_size_limit = 256 MiB (RED on main: -1 / unlimited). Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
0a8b63e to
918ba1e
Compare
|
Rebased onto current main. |
918ba1e to
df6d61e
Compare
… reclaimed On-disk write connections set no journal_size_limit, so the -wal file is never physically shrunk: all our checkpoints are PASSIVE (TRUNCATE is deliberately avoided because its ftruncate(fd,0) can SIGBUS a sibling mmap'd process on macOS), and PASSIVE never ftruncates. Under checkpoint starvation the WAL then grows without bound (reported: 115 GB, filled the drive) and even a later successful checkpoint leaves the file at its peak size (DeusData#1083). Set journal_size_limit = 256 MiB on write connections so SQLite truncates the -wal back to that bound whenever a checkpoint can finally reset the log — reclaiming disk opportunistically during a session, not only on clean close. 256 MiB is far above the healthy WAL (~4 MiB under the default 1000-page autocheckpoint), so normal indexing never triggers truncate/regrow churn; the limit only fires after abnormal growth. No TRUNCATE checkpoint is introduced. Scope: this is the *reclaim* half of DeusData#1083. It does not prevent the peak under continuous reader contention (a checkpoint can't reset the log while a reader holds a mark) — the write-amplification driver (concurrent full re-indexes of one repo) is serialized by the cross-process pipeline lock in DeusData#892. Test: a file-backed write store now reports journal_size_limit = 256 MiB (RED on main: -1 / unlimited). Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Reviewed this properly — the concurrency core is sound, and I traced it rather than trusting the description. One small gap stands between it and a merge. The migration ownership rule is exactly right, and I verified it end to end. Observing Lease accounting is balanced on every path I traced — try-success plus cancelled, try-success plus resolve-NULL, and the corrupt-recovery try inside I also checked the things that would make this dangerous rather than merely clever. Reads now use exactly the store-access class every other query tool has always used, and reindex publication is atomic (temp file plus rename), so a reader either keeps the old inode as a complete snapshot or opens the fully-written new one. No torn read. No lock-ordering inversion — the try path acquires nothing before the lease and never blocks. And reentrancy actually improves: an ADR read from inside an index-holding context can no longer self-deadlock. The tests are a model for this codebase. Deterministic by construction — a deny-Nth-call probe seam, no sleeps, no timeouts, no probabilistic waits — and genuinely binding: both fail on main without the fix. Proving migration persistence through a subsequent public read, rather than a retained borrowed handle, is a detail I appreciated. The one ask: the third embedder isn't wired.
To be clear, this is not a regression — the common case on that surface improves, since UI ADR reads currently fail outright with "blocked by an active index" during a reindex and will now succeed. The gap only affects those two rarer paths. But it leaves the three embedders inconsistent, and it is about three lines: either add a try parameter to the http setter, or call With that wired, this merges. Two things worth knowing, neither blocking:
Minor: on a corrupt store with a busy lease, an ADR read reports "not found / not indexed", which is misleading — "busy, retry" would be truer. Thank you for rebasing this onto the post-daemon-rework internals; that is why it still applies cleanly after a month, and it made the review much easier than it would otherwise have been. |
Signed-off-by: SS-42 <noreply@incogni.to>
df6d61e to
fb7a635
Compare
|
Rebased onto current main and addressed the remaining UI embedding path in fb7a635. The HTTP server now forwards its existing nonblocking mutation callback to embedded MCP. Corrupt-store ADR reads distinguish retryable active lease conflicts from missing nonblocking callback configuration, and legacy migration clears stale local pointers before re-resolving. Updated README and regression coverage. Verified with make -f Makefile.cbm test-focused TEST_SUITES=mcp_mutation_guard (24 passed), bash scripts/lint.sh --ci, and bash scripts/test.sh (6801 passed, 0 failed, 4 skipped; watchdog and security regressions passed). |
|
Checked the delta, and the third embedder is correctly wired. Thank you. I want to be specific about what I verified, because I nearly filed a false finding against you. The cbm_mcp_server_set_project_mutation_try_guard(srv->mcp, begin);Next to The new Where this stands: CI is running (19 checks in flight, none failed). Beyond that, there is one item left that is not about your code at all — a maintainer policy question about accepting a pseudonymous DCO sign-off ( One consequence worth restating for the record, since it changes observable behaviour of a public tool and your PR declares it deliberately: ADR Thanks for running the full suite and reporting the numbers; 6801 passed with the watchdog and security regressions green is a useful thing to have in the thread. |
Signed-off-by: SS-42 <noreply@incogni.to>
|
Documented the cached-read behavior in 2d0307b. The README now states that |
|
Merged as I had asked that the ADR-read staleness trade be on the record rather than left to be discovered later, and you put it in the README rather than in a thread nobody will find again. That paragraph is exactly right: query modes use the cached query store so they can proceed during a same-project reindex, an out-of-process publication can therefore serve the pre-publication ADR until idle eviction, and updates stay serialized through the mutation guard. That is the honest description of the trade, written where a user will actually meet it. On the code itself: the third-embedder wiring came through correctly. I checked the one thing that looked wrong — The Thanks also for running the full suite and posting the numbers — 6801 passed with the watchdog and security regressions green is a genuinely useful thing to have in the thread rather than a claim of "works for me". |
Summary
manage_adr(get|sections)on query-only store access while a same-project reindex holds the mutation leasemanage_adr(update|store)writes through the native project-lock managerEnvironment observed
CBM_CACHE_DIRRegression coverage
Verification
make -f Makefile.cbm test-focused TEST_SUITES=mcp_mutation_guard(24 passed)git diff --checkbash scripts/lint.sh --cibash scripts/test.sh(6801 passed, 0 failed, 4 skipped across 120 suites; watchdog and security regressions passed)