Concurrency Tests¶
Audit finding T-2 · ADR 44
Two threading invariants are central to correctness and are covered by deterministic contention tests that run as part of the default build (no Docker, no flakiness).
Multi-tenant store routing¶
MultiTenantFailoverStore builds one store per tenant lazily via ConcurrentHashMap.computeIfAbsent (see ADR 20). If first access for a tenant raced across threads and built the store more than once, tenants could end up writing to divergent store instances.
MultiTenantFailoverStoreConcurrencyTest proves this cannot happen:
- A
CountDownLatchstart-gate releases 64 threads simultaneously against the same tenant; a countingTenantStoreFactoryassertscreate()ran exactly once, and all 64 writes are found. - A second case drives 4 tenants × 32 threads and asserts exactly 4 factory invocations — one store per tenant, with no cross-tenant leakage.
Async store executor path¶
FailoverStoreAsync offloads writes to a TaskExecutor and swallows executor-side exceptions so a store failure never breaks the business call (see ADR 19). Under load, every submitted write must still be applied exactly once, and a failure must surface as the store-async-failed metric (ADR 41) rather than vanish.
FailoverStoreAsyncConcurrencyTest proves both:
- 500 concurrent
store()submissions through a realThreadPoolTaskExecutor— the executor is drained viaawaitTermination(no polling library) and every write is asserted to have landed exactly once. - An injected failing delegate triggers the
store-async-failedmetric on theObservablePublisher.
Deterministic, not timing-based
Draining is done by shutting the executor and joining it, so these tests assert a completed state rather than sleeping or polling — no Thread.sleep, no await library, no flakiness.