Skip to content

ADR-0007: Deadline budget, timeouts and retries

Status: Accepted · Date: 2026-10-19 · Resolves: OQ-1

Context

The till abandons at ~3 s. The wallet's real latency profile is not known (OQ-1). Implementers need fixed numbers and one unambiguous retry rule, otherwise every adapter invents its own timeouts and retries multiply across layers.

Decision

Working assumption A11: a healthy wallet answers POST /debits and POST /holds with p99.9 ≤ 400 ms. Every limit below is derived from it. All limits are properties in safeledger.budget and safeledger.wallet. Changing a number is a config change, not a design change.

Item Value Rule
Server budget 2,400 ms effective = clamp(Request-Deadline-Ms − 600 ms, 0, 2,400 ms); header absent → 2,400 ms
Decision call (debit / hold) 800 ms deadline.capAt(800 ms) (2× the assumed p99.9)
Lookup by reference 300 ms deadline.capAt(300 ms)
Worker call (credit, capture, release, lookup) 2 s Deadline.in(2 s) per attempt; nobody is waiting
Connect timeout 200 ms The only fixed timeout. It is set on the HTTP client and is below every call limit
Minimum budget to send 50 ms If remaining < 50 ms before a call, do not send. Return Unavailable("deadline spent")
Retry-After on WALLET_UNAVAILABLE / WALLET_TIMEOUT 2 s Fixed
Retry-After on 409 in flight, own 503, 404 PAYMENT_NOT_YET_DECIDED 1 s Fixed

Retries: 1. The decision path never retries a wallet call. A failed or unavailable decision is a retryable decline. The till retries with a new Idempotency-Key (architecture §7). 2. An Ambiguous decision call gets exactly one lookup by reference, if remaining ≥ 50 ms. 3. All other retries happen in the work queue (ADR-0008), which owns the outbound idempotency key. The HTTP client and Resilience4j have no retry configured.

Classification of a wallet call (identical for v1 and v2):

Observation Outcome
2xx Success
409 DUPLICATE_REFERENCE (v1) Success, built from the request plus existingDebitId / existingCreditId. No extra call.
409 INSUFFICIENT_FUNDS, HOLD_NOT_ACTIVE, HOLD_ALREADY_CAPTURED, AMOUNT_EXCEEDS_HOLD; 404; 423; other 4xx Rejected(code)
503, 502, connection refused, connect timeout, breaker open, bulkhead full, remaining < 50 ms Unavailable (certainly not applied)
Request timeout after the request was written, connection reset after send, 504, other 5xx Ambiguous

Consequences

  • + One rule set for all clients. Tests can assert exact budgets (SLOW → Ambiguous in ≤ 800 ms).
  • − If A11 is false (p99.9 > 400 ms), the slow-call breaker trips on a healthy wallet. Revisit trigger: the measured wallet client p99.9 over 7 days exceeds 400 ms. Then retune the limits from the measurement, keeping decision + lookup + 300 ms ≤ server budget.