fix(federation): open peers with mode=ro so a live WAL is honoured (#1198) - #1201
Conversation
There was a problem hiding this comment.
Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Warning Review limit reached
Next review available in: 46 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reviewer's GuideFederated peer SQLite connections are changed to open in plain read-only mode that honours WAL, with an explicit immutable fallback for genuine read-only media, plus targeted tests and docs/changelog updates to capture the behavior and regression being fixed. File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
7776dad to
a4d0c82
Compare
PR-size soft capThis PR is over the advisory size threshold:
Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the |
|
[claim:review:Setr:2026-07-30T17:29:48Z] |
|
Good fix, right diagnosis — two things before merge, one substantive. The core reasoning is correct and the measurements on both sides of the read-only-media question are the best part of this PR. The issue asked for a deliberate decision rather than a later discovery, and it got one backed by a table showing that an unconditional drop would have turned read-only peers from working into silently unreachable. That is the right way to resolve a "which default" question. Verified
1. The fallback triggers on any
|
|
[release:review:Setr:2026-07-30T17:32:52Z] |
|
[claim:review:Setr:2026-07-30T18:07:47Z] |
The immutable fallback is safe because it is reached after an honest read has already failed — but only if the failure is the one that justifies it. A bare `except sqlite3.OperationalError` also catches lock contention and transient filesystem errors, and routing those to `immutable=1` hands back a WAL-blind handle: the defect #1198 exists to remove, restored silently. `MemoryStore._peer_conn` caches the handle and swallows open failures, so it would stay restored for the life of the process. Non-readonly errors now propagate and the caller demotes the peer, which is the honest outcome. Pinned by a test that fakes a locked first statement and asserts no immutable handle is ever opened. Also corrects the laziness claim: `sqlite3.connect` on a URI defers the -shm attempt for a file that exists on read-only media, but a missing file raises `unable to open database file` from `connect` itself, so the general statement was too strong. And annotates the root-probe's `except ... : pass` for CodeQL. Raised in review on #1201.
a4d0c82 to
f934f59
Compare
|
Rebased onto 1. The fallback is now narrowed to Pinned by I could not construct a real lock case in-process — WAL readers don't block on writers, which is most of why this was unlikely rather than likely — so the test fakes the error rather than provoking it. Worth saying plainly: it pins the branch, not the scenario. 2. Corrected the laziness claim. So "a try/except around connect() alone is dead code" was too strong as a general statement. Docstring and changelog now say what was actually measured. 3. CodeQL — annotated the root-probe's Labelling once CI settles. |
…1198) immutable=1 promises SQLite the file cannot change, and SQLite acts on that by skipping locking and ignoring the write-ahead log. Every aelfrice store runs in WAL mode, so a peer held open by a running hook was read without its uncheckpointed commits: no such table when the WAL still held the schema, a silent under-read when it held only rows. Plain mode=ro honours the WAL and keeps the read-only guarantee. immutable=1 stays as a fallback because a WAL-mode database must create a -shm file even when fully checkpointed, so mode=ro fails outright on read-only media; dropping it unconditionally would turn those peers from working into silently unreachable. The fallback runs only after an honest read has failed, so it cannot mask the live-WAL case. sqlite3.connect on a URI is lazy and a read-only medium only errors at the first statement, so the open issues a schema_version pragma to make the pager resolve where the fallback can act on it.
The immutable fallback is safe because it is reached after an honest read has already failed — but only if the failure is the one that justifies it. A bare `except sqlite3.OperationalError` also catches lock contention and transient filesystem errors, and routing those to `immutable=1` hands back a WAL-blind handle: the defect #1198 exists to remove, restored silently. `MemoryStore._peer_conn` caches the handle and swallows open failures, so it would stay restored for the life of the process. Non-readonly errors now propagate and the caller demotes the peer, which is the honest outcome. Pinned by a test that fakes a locked first statement and asserts no immutable handle is ever opened. Also corrects the laziness claim: `sqlite3.connect` on a URI defers the -shm attempt for a file that exists on read-only media, but a missing file raises `unable to open database file` from `connect` itself, so the general statement was too strong. And annotates the root-probe's `except ... : pass` for CodeQL. Raised in review on #1201.
f934f59 to
2f589f8
Compare
|
Rebased onto The earlier |
|
[release:review:Setr:2026-07-30T18:25:23Z] |
|
merge-train: merged 2f589f8 → |
Closes #1198.
federation.open_peer_connectionopened every peer withfile:...?mode=ro&immutable=1.immutable=1promises SQLite the file cannotchange, and SQLite acts on that promise by skipping locking and ignoring the
write-ahead log entirely. Every aelfrice store runs in WAL mode, and a store
held open by a running hook — the normal deployment shape — keeps recent
commits in
memory.db-waluntil something checkpoints.Reproduced
Same file, same instant, 30 committed beliefs, writer still open:
The WAL held the schema as well as the rows, so the peer saw nothing. Where
the schema has been checkpointed but recent rows have not, the failure is
quieter and worse: a smaller row count with no error, so federated retrieval
silently omits beliefs that are committed and durable. Same class as #1173 — a
live WAL means the main database file is not the data.
The read-only-media case cuts the other way
The issue flagged this as needing a deliberate decision rather than a later
discovery. It is not hypothetical, and it argues against an unconditional
drop. Measured on a read-only directory:
mode=roimmutable=1attempt to write a readonly database-shmno such table)A WAL-mode database needs to create a
-shmfile even when fullycheckpointed, so plain
mode=rofails outright on read-only media. Droppingimmutable=1unconditionally would have turned those peers from working intosilently unreachable —
MemoryStore._peer_connswallows open failures anddemotes the peer, so it would have failed quietly.
So:
mode=rois the primary form,immutable=1the fallback. On a genuinelyread-only medium the immutable promise is truthful and is the only way to read
the peer at all. The fallback runs only after an honest read has already
failed, so it cannot mask the live-WAL case above.
One non-obvious mechanic
sqlite3.connecton a URI is lazy: it returns a handle without touching thefile, and a read-only medium only errors at the first statement. A
try/exceptaroundconnect()alone is dead code — verified. The open nowissues a
PRAGMA schema_versionto force the pager (and the-shmattempt) toresolve where the fallback can act on it, instead of at an arbitrary later
query.
Acceptance criteria
immutable=1; keepmode=ro— done, with the fallback abovepeer sees every committed row
immutable=1—git grep immutableconfirms this was the only site
Verification
tests/test_federation_loader.py. Reverting theimplementation to
immutable=1fails two of them(
no such table: beliefs); the third is a regression guard for theread-only-media fallback, which the old code trivially had.
passing vacuously if the directory mode is not enforced (i.e. running as
root), so it cannot silently become a no-op in CI.
Follow-up worth considering (not in this PR)
aelf healthreports peers as reachable/unreachable only. A peer opened viathe immutable fallback is reachable but reads only checkpointed state, which is
worth surfacing there. That needs a
PeerDepfield and a health-output change,so it is outside these acceptance criteria.
Summary by Sourcery
Adjust peer federation to respect SQLite WAL while preserving read-only guarantees and support for read-only media.
Bug Fixes:
Enhancements:
open_peer_connectionbehaviour and WAL/immutable fallback semantics in the architecture overview and changelog.Documentation:
immutable=1fallback for read-only media.Tests: