Skip to content

fix(db): bound the write-lock wait by the worst case it can really produce - #123

Merged
abrichr merged 1 commit into
mainfrom
codex/sqlite-lock-budget-bound
Aug 28, 2026
Merged

fix(db): bound the write-lock wait by the worst case it can really produce#123
abrichr merged 1 commit into
mainfrom
codex/sqlite-lock-budget-bound

Conversation

@abrichr

@abrichr abrichr commented Aug 28, 2026

Copy link
Copy Markdown
Member

Follow-up to #122, from its own post-merge run.

What failed

test-macos on 55658f9a:

AssertionError: a permanently held lock cost 2.05s, over the declared 2.0s budget

Why

The measurement was right and the stated bound was wrong.

_write_with_lock_retry starts its last attempt strictly before BUDGET - CEILING, so the total runs over the budget by however long that one attempt takes. An attempt costs the busy timeout plus whatever SQLite's busy handler overshoots it by, and a loaded runner overshoots more. "Total is at most the budget" therefore holds only while an attempt stays inside the ceiling — which is not a figure to build a release gate on.

The change

State the bound that always holds: at most the budget plus one attempt, BUDGET + CEILING = 22s. recorder.py now computes that figure and checks it, not the budget alone, against the 30s readiness deadline.

No change to the retry behaviour itself — this corrects what is claimed and checked about it.

The test still fails when the helper gives up early, which is the half of the contract a count of attempts cannot express. Verified again with only the old retry policy restored: both new regression tests fail, the other 13 pass.

Ran the corrected test 12 times under load average 8: passed every time, 2.3–3.1s.

Full suite: 721 passed, 33 skipped. ruff clean.

Also seen in that run, and not this

test-windows failed on test_highlevel.py::TestRecorder::test_stop_during_incomplete_startup_returns_promptly, assert (387.781 - 386.765) < 1 — 1.016s against a hard 1s wall-clock bound.

That test monkeypatches recorder_module.record away entirely, and the finalize call added in #122 sits behind a readiness guard that never fires there, so nothing in #122 executes on that path. The test deliberately spends 0.5s in _join_tasks(timeout=0.5) and then allows 1.0s total for thread joins, control-state writes and socket teardown on a shared Windows runner. It has no failure history in the visible runs. Flagging it rather than touching it.

…oduce

The budget test failed on hosted macOS in the post-merge run of 55658f9: a
permanently held lock cost 2.05s against a declared 2.0s budget.

The measurement was right and the stated bound was wrong. The helper starts its
last attempt strictly before BUDGET - CEILING, so the total is over the budget
by however long that one attempt runs. An attempt costs the busy timeout plus
whatever SQLite's busy handler overshoots it by, and a loaded runner overshoots
more, so "total is at most the budget" holds only while an attempt stays inside
the ceiling. It is not the figure to build a release gate on.

State the bound that always holds instead: at most the budget plus one attempt,
which is BUDGET + CEILING. recorder.py now computes that figure and checks it,
rather than the budget alone, against its readiness deadline: 22s against 30s.
The test asserts the same figure, and still fails when the helper gives up
early, which is the half of the contract a count of attempts cannot express.

No change to the retry behaviour itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@abrichr
abrichr merged commit 12842d2 into main Aug 28, 2026
14 checks passed
@abrichr
abrichr deleted the codex/sqlite-lock-budget-bound branch August 28, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant