Cache-server concurrent writers: threaded requests, first-writer-wins verdicts - #89
Merged
Conversation
…ns verdicts (v0.5 theme, item 2) ThreadingHTTPServer with one lock-guarded sqlite connection: network IO and JSON handling run concurrently while db work stays serialised, so every write remains an atomic single-statement commit. daemon_threads is False so server_close joins in-flight requests before the store closes. Verdict publishes are first-writer-wins (CacheStore, ON CONFLICT DO NOTHING): two publishes for one verification key are re-runs of a bit-identical closure, so a duplicate must not refresh ran_at (freshness honesty) or reset stale — closing in advance the race where a duplicate publish resurrects a row cross-graph invalidation just marked. Handler errors now return structured JSON 500s, never a bare stack trace. Wire protocol unchanged; Store protocol and contracts untouched. WAL + per-thread connections stay the documented throughput follow-up.
This was referenced Jul 26, 2026
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.
Second item of the v0.5 hosted-store theme (ROADMAP: "Concurrent writers — the cache server is single-threaded-serialised; real teams need atomic verdict/blob writes under concurrency (CAS or equivalent), not politeness").
Threading —
python -m hashloom.cache_serveris now aThreadingHTTPServer:threading.Lockserialises the single SQLite connection, so every db write stays an atomic single-statement commit — the exact semantics the store'scheck_same_thread=Falsecomment promises, now enforced by the lock rather than by single-threading. (An explicit lock rather than trusting SQLite serialized mode:sqlite3.threadsafetyintrospection is unreliable on the 3.10 floor.)daemon_threads = False, soserver_close()joins in-flight request threads beforeserve()closes the store — the shutdown use-after-close that threading would otherwise introduce never exists.{"error": {"code": "internal", ...}}) instead of a bare stack trace swallowed by the silenced request log — the_responddiscipline, extended to the cache server.First-writer-wins verdicts — the "CAS or equivalent", resolved by observing that the verification key is the compare:
pass(the server rejects the rest) and summaries are empty on green. Last-writer-wins buys nothing.ON CONFLICT DO UPDATErefreshedran_at(a verdict looking fresher than the run that produced it) and resetstale=0— which becomes a served-stale-green race the moment cross-graph invalidation (theme item 3) can mark shared rows stale.CacheStore(SqliteStore)subclass in cache_server.py:ON CONFLICT(key) DO NOTHING. Duplicates are no-ops. Local stores keep their overwrite behavior; theStoreprotocol and its contract are untouched by construction.Deferred, documented: WAL journal mode + per-thread connections + bounded busy-retry — the throughput path if a team ever saturates the lock (
docs/hosted-store.mditem 4 names it).Tests (4 new in
test_remote_store.py, fixtures now exerciseCacheStore): deterministic first-writer-wins proof (duplicate leavessummaryandran_atuntouched); the soundness pin — a duplicate publish cannot un-stale a rowmark_stalejust marked; a 16-worker mixed publish/read storm with no 5xx, every key landing exactly once, and one whole winner on the contested key; structured-500 with noTracebackin the body.Live smoke: 250 parallel requests against the real server process — status codes exactly as designed (100×204, 100×200, 50×403, zero 5xx), 51 rows for 51 keys, one whole winner on the 50-way contested key.
Full suite 243 passed, coverage 90.49% (gate 85), benchmark ≥5x. Wire protocol unchanged — clients need no update.
Next theme items: cross-graph invalidation, then the dependency set in the verification key.
🤖 Generated with Claude Code