Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lesson 9 · Concurrency and stale decisions

From cognokratos/tauros-revenue · docs/course/09-concurrency.md · pinned revision facbbc927eb4

Part III: Human authority · Course map · Next: Lesson 10

Goal

Know what happens when two decisions, or a decision and a change, race, and why the outcome is always one consistent answer.

Concept

Every invoice command locks the invoice row first, so commands on one invoice run one after another. Approval also locks the destination row, so a deactivation cannot slip in halfway. A unique index allows one decision per revision, whatever the application does.

RaceOutcome
double click / retry after timeoutone approval; both calls succeed
approve vs reject from two tabsfirst wins; the other gets already_decided
agent revises while the human readsthe human's approval is stale_revision
destination retired while pendingapproval is destination_inactive

Code to inspect

  • lib/tauros/revenue/invoice/changes/decide.ex: lock/2, the replay rule
  • approvals_one_decision_per_revision_index in the approvals migration

Run it

Note

Book edition note. The "simultaneous" tests start several Tasks, but they run inside the Ecto SQL sandbox in shared mode. All tasks use one database connection, so their queries execute one after another. The tests demonstrate the replay and conflict rules, not row-lock contention between real connections. The raw-SQL test in "Break it" is a genuine proof of the database-level guarantee (the unique index on approvals.revision_id).

mix test test/tauros/adversarial_test.exs   # describe "concurrency", "revision safety"
mix test test/tauros/adversarial_test.exs --repeat-until-failure 20

Break it

mix test test/tauros/revenue/approval_test.exs   # "the database allows one decision per revision, whatever the code does"

That test inserts a second approval with raw SQL, bypassing Tauros entirely.

Why it fails

Postgres refuses it: the unique index on approvals.revision_id. Locks keep the application consistent; the index keeps the database consistent even if the application were wrong.

What to remember

  • Lock, then check: decide against the current row.
  • Retries are answered, not repeated.
  • Put the last line of defence in the database.

Next: Lesson 10 · Auditability

This chapter is maintained in cognokratos/tauros-revenue beside the code it teaches. The book shows docs/course/09-concurrency.md at revision facbbc927eb4b9090937524ca02486c026f1f025 (branch main). View source at this revision · Report a correction.

Corrections are made upstream against the current main branch and appear here when the book's pin for this source is updated.