Data Race Detection
Data race detection finds two concurrent accesses to the same location where at least one is a write, the bug class that produces nondeterministic corruption that only surfaces under specific.
Definition
Data race detection is the analysis — static (type system, ownership), dynamic (ThreadSanitizer, fast-track), or model-based — that identifies two or more concurrent accesses to the same memory location where at least one is a write and no synchronization orders them, the canonical undefined behavior in shared-memory concurrency.
Context and scope
Applied in compiled language runtimes (TSan, Helgrind), Rust borrow checker, and any concurrency-heavy codebase where races are the dominant defect class.
Common misconceptions
- A data race is the same as a race condition — a data race is unsynchronized access to memory; a race condition is the broader semantic bug where outcome depends on interleaving, which can occur without a data race
- Testing under load finds races — it samples a vanishing fraction of interleavings; detection tools reason about the happens-before graph, which is exhaustive
Anti-patterns
- Running the test suite under TSan and treating every warning as a bug, when some are benign races on idempotent flags
- Disabling race detection in CI because it slows the tests, letting races merge and surface only in production
Related concepts
Authoritative source
https://llvm.org/devmtg/2017-10/slides/Serebryany-ThreadSanitizer.pdf
What it is
Data race detection is the analysis — static (type system, ownership), dynamic (ThreadSanitizer, fast-track), or model-based — that identifies two or more concurrent accesses to the same memory location where at least one is a write and no synchronization orders them, the canonical undefined behavior in shared-memory concurrency. Applied in compiled language runtimes (TSan, Helgrind), Rust borrow checker, and any concurrency-heavy codebase where races are the dominant defect class. The central difficulty is that interleaving is nondeterministic: the set of possible executions grows super-exponentially with the number of operations, and testing samples a vanishing fraction.
How it works
Correctness is established by reasoning about happens-before relationships, not by running more tests. Synchronization primitives are chosen for the specific guarantee needed — mutual exclusion, publication, or ordering — and composed only with a documented proof of absence of deadlock. Bounded resources apply backpressure rather than unbounded queuing that hides overload until memory exhausts.
When to apply and pitfalls
Apply whenever multiple flows share mutable state or resources. The dominant pitfall is reasoning sequentially about concurrent code — “this can’t happen because A finishes before B” — when in fact the scheduler can interleave at any instruction boundary. The second is using a coarse lock for simplicity and turning parallelism back into serialization.