Build deterministic race-condition tests - identify shared mutable state, drive interleavings via barriers / latches / manual scheduling; use ThreadSanitizer (clang `-fsanitize=thread`) for C/C++/Go data race detection; use jcstress (`@JCStressTest` + `@Actor` + `@Outcome`) for JVM stress; use Loom virtual-thread interleavings for parallel testing. Use when a defect only reproduces under load on shared in-process state (cache, counter, connection pool, lazy-init singleton), or when writing the regression test for a race-condition incident before the fix lands.
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Race conditions are the canonical "works on my machine, breaks under load" bug. Tests must drive shared-state access deterministically (barriers, latches) AND non-deterministically (sanitizers, stress) to expose them.
Code-review checklist:
For each: ask "what if two threads / goroutines / async tasks hit this concurrently?"
import threading
def test_lazy_init_thread_safe():
target = LazyService() # has private _instance + lazy_get()
barrier = threading.Barrier(parties=2)
results = [None, None]
def worker(idx):
barrier.wait() # both threads stop here, then race
results[idx] = target.lazy_get()
t1 = threading.Thread(target=worker, args=(0,))
t2 = threading.Thread(target=worker, args=(1,))
t1.start(); t2.start()
t1.join(); t2.join()
assert results[0] is results[1], "Lazy init created two instances under race"Barrier ensures both threads start the contended section at the
same time - much higher probability of triggering the race than
naive threading.Thread().
Per the ThreadSanitizer docs, TSan detects data races at runtime with ~5-15× overhead. For C/C++:
clang -fsanitize=thread -g -O1 program.c -o program
./programFor Go, native data race detector:
go test -race ./...
go run -race main.goOutput for a detected race:
WARNING: DATA RACE
Read at 0x... by goroutine 7:
main.read+0x...
main.go:42
Previous write at 0x... by goroutine 6:
main.write+0x...
main.go:38Per the ThreadSanitizer docs, adaptive delay injection
(TSAN_OPTIONS=enable_adaptive_delay=1) helps surface races at
synchronization points.
Per the jcstress docs, jcstress is "the experimental harness ... for the correctness of concurrency support in the JVM."
@JCStressTest
@Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Initial values")
@Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Both writes seen")
@Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Saw partial")
@Outcome(id = "1, 0", expect = FORBIDDEN, desc = "Reordered - bug")
@State
public class CounterTest {
int x, y;
@Actor
public void writer() { x = 1; y = 1; }
@Actor
public void reader(II_Result r) {
r.r1 = y;
r.r2 = x;
}
}@Actor methods run concurrently on different threads; @Outcome
classifies observed (r1, r2) pairs. FORBIDDEN outcomes indicate
a memory-model violation (in this case, reordering allowed under JMM
unless volatile or final).
Run:
java -jar jcstress.jar -m quick CounterTestPer the jcstress docs: tests are probabilistic; longer runs find more reorderings.
Java 21+ Project Loom enables cheap virtual threads. Use to test many-concurrent-task interleavings without OS thread cost:
@Test
void test_handles_10000_concurrent_orders() throws Exception {
var orderService = new OrderService();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var futures = IntStream.range(0, 10_000)
.mapToObj(i -> executor.submit(() -> orderService.place(i)))
.toList();
for (var f : futures) f.get();
}
assertEquals(10_000, orderService.totalProcessed());
}Virtual threads run M:N on OS threads - the JVM scheduler interleaves them aggressively, exposing schedule-dependent races.
Combine Hypothesis (Python) / fast-check (JS) with concurrency:
from hypothesis import given, strategies as st
import threading
@given(operations=st.lists(st.tuples(st.sampled_from(["read", "write"]), st.integers()), min_size=10, max_size=100))
def test_counter_property_under_concurrency(operations):
counter = ThreadSafeCounter()
threads = []
for op, val in operations:
if op == "write":
threads.append(threading.Thread(target=lambda v=val: counter.set(v)))
else:
threads.append(threading.Thread(target=counter.get))
for t in threads: t.start()
for t in threads: t.join()
# Property: final value is one of the written values
assert counter.get() in [v for op, v in operations if op == "write"]Cross-ref qa-property-based plugin for property-based test
authoring patterns.
# Go
- name: Run race detector
run: go test -race ./...
# C/C++
- name: TSan build + test
run: |
cmake -B build -DCMAKE_C_FLAGS="-fsanitize=thread -g -O1"
cmake --build build
./build/test_runner
# Java
- name: jcstress quick run
run: java -jar jcstress.jar -m quick -r results/Note the latency cost: go test -race ~3× slower; jcstress quick
mode ~minutes per test class.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
time.sleep(0.001) to "force" interleaving | Non-deterministic; flake | Use barriers (Step 2) |
| Run race tests once and assume green | Probabilistic; some races take hours | Multiple runs OR longer runs OR sanitizers |
| Skip TSan in CI for "release" builds | Race in release; CI passed without -race | TSan in CI for at least one matrix dimension (Step 7) |
| Depend on assertion in worker thread | Thread death silent; main thread sees pass | Use futures + assert from main |
| Test only the bug-causing race, not similar | Other shared state has same pattern; bugs ship | Code-review checklist (Step 1) |
-race overhead is real (~5-10× memory); CI matrix
budget required.-fsanitize=thread, runtime
options, adaptive delay@JCStressTest, @Actor, @Outcome outcomesdeadlock-detection-harness -
sister skill for deadlock-specific patternsasync-ordering-tests -
sister skill for async/await orderingjepsen-patterns - distributed
consistency testing alternative