Move API SQLite store calls off the event loop; add timed access log - #39
Open
RichSchefren wants to merge 1 commit into
Open
RichSchefren wants to merge 1 commit into
RichSchefren wants to merge 1 commit into
Conversation
uvicorn serves every client from one event loop, and the memory.search,
memory.get, memory.list and ledger.verify_chain handlers ran their SQLite
store calls inline. A busy_timeout wait (up to 5 s) or a large read
stalled every queued request and produced client-side timeouts on
20-40 ms calls (2026-09-14 mcc-atlas-owner incident).
- AtlasMCPServer gains a one-worker ThreadPoolExecutor ("atlas-sqlite")
and _run_in_store_thread; the four handlers await through it. One
worker on purpose: the store calls are GIL-bound in row conversion,
and a measured 20-way memory.list burst took 2.7 s on 32 threads
against 0.37 s serialized.
- New AccessLogMiddleware (pure ASGI) logs every request once with a UTC
timestamp, client, method, path, status and duration_ms; registered
outermost in create_http_app so CORS preflights and 401s are timed
too. Run uvicorn with --no-access-log to avoid double logging.
- 12 new tests (tests/unit/test_mcp_event_loop_offload.py,
tests/unit/test_http_access_log.py), written failing first.
Verified live on the Studio: /health during a heavy batch 6.9 ms (was
38.4 ms); memory.list limit=500 at 20 concurrent p50 205 ms (baseline
204 ms).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.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.
Summary
AtlasMCPServernow runsmemory.search,memory.get,memory.listandledger.verify_chainstore calls on a dedicated one-workerThreadPoolExecutor(atlas-sqlite) via_run_in_store_thread, so a SQLite busy wait or a large read no longer stalls uvicorn's event loop for every queued client.memory.list limit=500burst took 2.7 s on 32 threads against 0.37 s serialized.AccessLogMiddleware(pure ASGI) logs each request once with a UTC timestamp, client, method, path, status andduration_ms. Registered outermost increate_http_app, so CORS preflights and 401s are timed. Run uvicorn with--no-access-logto avoid double logging.Verification
pytest tests/unit: 392 passed (baseline 380; 12 new tests written failing first)ruff check .: clean/healthduring a heavy batch 6.9 ms (was 38.4 ms);memory.list limit=500at 20 concurrent p50 205 ms (baseline 204 ms); the MCC atlas-owner sidecar rode through the deploy with no pid change.Follow-up (not in this PR)
quarantine.upsert,quarantine.list_pendingandmemory.forgetstill call the store inline.🤖 Generated with Claude Code