Skip to Content
Proof and receiptsThe concurrency test, and its output

The concurrency test, and its output

Credits are a ledger, not a counter claims that two requests racing on the same account cannot spend more credits than the account holds. This page is the evidence for that claim.

It is a description of a test that exists in the private repository, not a copy of it. The point is that you can check the claim is tested at all, and check what it would take to break it.

The test: test_concurrent_deducts_do_not_double_spend, in backend/tests/test_credit_ledger.py.

The invariant

Two concurrent spends against one account, in separate transactions on separate connections, must not both succeed when the balance can only cover one of them.

Note what this is not testing. Retry-safety — the same unit of work being delivered twice — is a different property, guarded by the UNIQUE (job_id, kind) constraint and covered by its own tests. This test deliberately uses two different job ids, so the uniqueness constraint cannot help. It exercises the case the constraint does not cover: two genuinely distinct spends interleaving their balance checks.

Setup

Starting balance10 credits
Concurrent operations2 spends of 7 credits each
Job idsDistinct — one freshly minted per spend
IsolationEach spend runs in its own session, on its own connection, in its own transaction
ConcurrencyThe two spends are awaited together with asyncio.gather, not in sequence

The separate-connection detail is load-bearing. Two spends sharing a session would serialise on the session itself and the test would pass without proving anything. Real concurrency needs real connections, so this test commits for real rather than running inside the suite’s usual per-test rollback — and deletes its user in a finally block, so the committed rows do not leak into another test’s orphan scan.

What it asserts

successes = sum(results) assert successes == 1, "both deducts succeeded — double spend" ... assert balance == 3 # 10 - 7 = 3 (the failed deduct didn't deduct)

The losing call raises InsufficientCreditsError. It does not partially apply, and it does not leave a movement behind.

What regression this catches

Remove the FOR UPDATE row lock taken before the balance check and the test fails immediately, in the way that matters:

  • Both transactions read a balance of 10 under READ COMMITTED, because each takes its snapshot before the other commits.
  • Both find 10 ≥ 7 and insert a spend of 7.
  • The account settles at −4. Both callers were served; one was never paid for.

The assertion that fails is successes == 1, with both spends reporting success. That is a double-spend, and it is silent in production: nothing errors, the balance is simply wrong, and it is discovered later from a support ticket rather than from a log.

This is the test that makes “we use a ledger” a checkable statement rather than a design preference.

Where it runs

The backend CI job, on every push and every pull request. It is an integration test: it runs against a real PostgreSQL service container, not a mock or an in-memory substitute — a fake would not reproduce the isolation-level behaviour the test exists to pin down. The same job runs ruff, mypy --strict, a full Alembic upgrade → downgrade → upgrade round-trip, and a coverage floor.

Current result

$ pytest backend/tests/test_credit_ledger.py::test_concurrent_deducts_do_not_double_spend -q . [100%] 1 passed in 2.73s

Passing at 1ee1bc9cb661 on main — the same commit every generated page on this site was extracted from, so the receipt and the reasoning describe one tree rather than two.

You are reading a vendor’s report of their own test result, which is worth exactly what you think it is worth. The test and its CI history are readable in full on the first day of a licence — that is the point at which this receipt becomes verifiable rather than merely stated.

Last updated on