Skip to content

fix(tests): give the metrics concurrency test real connections — 2.1-safe - #139

Merged
MrChengLen merged 1 commit into
mainfrom
pr-metrics-test-isolation
Sep 25, 2026
Merged

MrChengLen merged 1 commit into
mainfrom
pr-metrics-test-isolation

Conversation

@MrChengLen

Copy link
Copy Markdown
Owner

What

tests/test_daily_metrics.py::test_increment_is_safe_under_concurrent_callers now gets its own engine: a file SQLite database in tmp_path with NullPool and a 30 s busy timeout. Each of the 50 writers therefore has a real connection. The test also checks the metrics log before counting, so a lock timeout fails with its own message and doesn't look like a lost increment.

Why

SQLAlchemy 2.1.0 (released 2026-09-24) turned this test red on every branch. 50 concurrent increments landed at 9–48. Examples: #137 runs 36135730761 attempts 1 and 2, and pr-dashboard-key-auth run 36136050957.

  • CI installs requirements.txt unpinned, so it picked up 2.1.0 on its own.
  • Production installs requirements.lock and is still on 2.0.52.

The fault was in the test setup, not in app/core/metrics.py.

  • The test ran all 50 sessions on the module's shared in-memory StaticPool engine, so they shared one connection and one transaction.
  • Under 2.1's aiosqlite adapter, one session's rollback when it returns its connection can land between another session's UPDATE and COMMIT and discard it.
  • On 2.0.x the same setup ran all 50 writes in one transaction with a single commit. So it never actually tested concurrent writers.

Verification

  • Local (2.0.52):
    • The test passed 10 out of 10 runs.
    • The whole module: 27 passed.
    • The full suite: 1177 passed, 60 skipped.
    • ruff and format checks are clean.
  • Reviewer's scratch run on 2.1.0 (only SQLAlchemy changed):
    • The old test got 6, 11 and 15 out of 50.
    • The new test passed 10 out of 10.
  • The test still catches the bug it exists for. On the same setup, with 5 runs each:
    • The real increment() reached 50/50 every time.
    • A naive read-then-write version reached only 1–3 of 50.
  • Reviews: security-auditor passed it and code-reviewer approved it. Both review notes are applied: the log check and precise docstrings.

Rejected alternative

Capping sqlalchemy<2.1. That would hide a flaw in the test behind a version pin, and it would need a Dependabot ignore rule.

This PR also unblocks #137 and every other open PR, which currently fail on this test.

🤖 Generated with Claude Code

…safe

SQLAlchemy 2.1.0 (released 2026-09-24) turned
test_increment_is_safe_under_concurrent_callers red on every branch — 50
concurrent increments landed at 9 to 48 (runs 36135730761, 36136050957). CI
installs requirements.txt unpinned and picked 2.1.0 up by itself; the image
installs requirements.lock, so production is still on 2.0.52.

The defect was in the harness, not in app/core/metrics.py: the test ran its 50
sessions on the module's in-memory StaticPool engine, i.e. one shared
connection and one shared transaction. Under 2.1's aiosqlite adapter a
session's pool-return rollback can land between another session's UPDATE and
COMMIT and throw it away; on 2.0.x the same harness ran all 50 writes in one
transaction with a single commit, so it never exercised concurrent writers at
all. Production never shares a connection between sessions. The test now gets
its own engine — a file database in tmp_path with NullPool and a 30 s busy
timeout — so each writer has a real connection and SQLite serialises them.
Because increment() swallows DB errors, the test checks the metrics log first,
so a lock timeout on a slow runner fails as itself, not as a lost increment.

Checked with a scratch harness on the same setup: increment() reaches 50/50 in
five trials, a naive read-then-write implementation 1 to 3 of 50, so the test
still catches what it exists to catch. The reviewer's controlled A/B (only
SQLAlchemy switched) showed 2.1.0 losing increments on StaticPool (6-15/50)
and the new test passing 10/10 on 2.1.0.

Rejected: capping sqlalchemy<2.1 — it would hide a harness artefact behind a
version pin and need a Dependabot ignore rule; retrying or lowering N — it
would weaken the lost-update guard.

Full suite 1177 green (60 skipped); the test 10/10 locally; ruff + format
clean. Reviewed by security-auditor (pass) and code-reviewer (approve).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MrChengLen
MrChengLen merged commit 7cb26e4 into main Sep 25, 2026
5 checks passed
@MrChengLen
MrChengLen deleted the pr-metrics-test-isolation branch September 25, 2026 13:18
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