# How to test concurrent code for races and deadlocks

> Concurrency testing combines race detectors, controlled execution orders, invariant checks, and stress runs. Learn how to scope a test around shared state, reproduce a race, and review agent-written concurrent code with Factory before merge.

URL: https://factory.com/articles/concurrency-testing-coding-agent-changes
Published: 2026-09-27
Updated: 2026-10-23

To test concurrent code, run focused tests under a race detector such as Go's -race flag or ThreadSanitizer, force the risky execution order with barriers or a controllable executor, assert the business invariant, and add bounded stress and shutdown tests. Save the seed and command so any failure can be repeated.

Concurrency testing gives reviewers evidence about behavior that ordinary unit tests often miss. A coding agent can make a local synchronization change that compiles, passes once, and still introduces a data race, deadlock, lost update, or stale read under a different schedule.

Dynamic detectors are a strong first gate. Go's official [race detector](https://go.dev/doc/articles/race_detector) instruments memory access when tests run with `-race`. Clang's [ThreadSanitizer documentation](https://clang.llvm.org/docs/ThreadSanitizer.html) describes a similar detector for C and C++. Both observe executed paths, so a clean report covers the tested workload rather than every possible execution order.

## Scope concurrency testing around invariants

State the property that must remain true across workers. A counter must equal committed updates. A cache must never expose a partially initialized value. A job may have one active owner. A shutdown path must stop accepting work before shared resources close.

Map every shared value to its synchronization mechanism and ownership. Locks, channels, atomics, database constraints, and leases protect different boundaries. Ask the coding agent to explain which boundary changed and why the existing mechanism still covers every access.

Keep timing out of correctness assertions where possible. Sleeping for a fixed interval makes a test depend on machine speed. Barriers, latches, fake clocks, and controllable executors can place operations at the contested point and make the failure reproducible.

## Run concurrency testing in layers

Start with focused tests under the language's race detector or sanitizer. Exercise repeated access, cancellation, error returns, and shutdown. Run enough iterations to explore schedules, but save the random seed and command so a failure can be repeated.

Add a controlled test for the reported or plausible execution order. Pause one worker after it reads state, let another update that state, then resume the first. Assert the business invariant, not the internal order, unless ordering is itself the contract.

A controlled test makes the race happen on purpose. The flow below is the shape of a lost-update test.

Worker A reads shared state</span>"]
    B["<span style='color:#FAFAFA !important;font-weight:600'>Worker A waits on a latch</span><br/><span style='color:#8A8380 !important'>test controls the pause</span>"]
    C["<span style='color:#FAFAFA !important;font-weight:600'>Worker B updates the same state</span>"]
    D["<span style='color:#FAFAFA !important;font-weight:600'>Release the latch</span><br/><span style='color:#8A8380 !important'>Worker A resumes and writes</span>"]
    E["<span style='color:#FAFAFA !important;font-weight:600'>Assert the invariant</span><br/><span style='color:#8A8380 !important'>for example, count equals committed updates</span>"]
    A --> B --> C --> D --> E
`}
/>

Stress tests can reveal starvation, lock contention, and resource leaks that short tests miss. Record worker count, duration, CPU allocation, and detector settings. Treat a timeout as evidence to investigate rather than rerunning until green.

Race detectors do not establish freedom from deadlock. Add bounded completion tests for lock ordering, cancellation, and shutdown paths. When the language exposes blocked-thread or goroutine diagnostics, retain that output with a failure so the next run starts from evidence.

Each layer covers a different failure and has its own blind spot.

| Layer | What it finds | Limit |
| --- | --- | --- |
| Race detector or sanitizer | Data races on executed paths | Only paths the test ran |
| Controlled execution order | Lost updates and stale reads at a known point | Needs a hypothesis about the order |
| Stress run | Starvation, contention, and resource leaks | Nondeterministic, so record the settings |
| Bounded completion test | Deadlocks in lock ordering, cancellation, and shutdown | Covers the chosen paths only |

## Make the result reviewable

The pull request should identify shared state, the synchronization decision, detector commands, controlled execution orders, and stress results. Include a regression test that fails without the fix when practical. Do not weaken assertions or add broad retries to hide nondeterminism.

Some failures remain environment-sensitive. Stop and report the limitation when a required architecture, kernel facility, sanitizer runtime, or production workload cannot be reproduced. A documented boundary is more useful than a confident result from an unrepresentative test.

## How Factory reviews concurrent changes

Factory is a platform for running a software factory, where agents called Droids take work from a signal to a reviewed pull request. For concurrent code, Factory adds a reviewer that looks for race patterns, and keeps the exact verification commands in front of the Droid.

**Code review on each pull request.** Factory's [automated code review](https://docs.factory.com/software-factory/code-review-ci) lists race conditions, async and await mistakes, and resource leaks among the bugs it looks for. It runs on pull request events, skips drafts, and posts inline comments. A `review-guidelines` skill can add rules for the repository, such as requiring a named synchronization mechanism for every new shared value.

**Exact commands in AGENTS.md.** [AGENTS.md](https://docs.factory.com/harness/agents-md) gives Droid the project briefing it carries into every session, including how to run and verify tests. Putting the detector command, iteration count, and seed handling there keeps a Droid from choosing an easier substitute.

**Clear task specs.** Factory's [delegation guides](https://docs.factory.com/delegations) recommend putting the repository, desired outcome, acceptance criteria, and verification steps in the issue before assigning it to Droid. For a race fix, the acceptance criteria are the invariant and the controlled test that fails without the fix.

## Frequently asked questions

### What is concurrency testing?

Concurrency testing checks behavior under different execution orders, including data races, deadlocks, lost updates, and stale reads that ordinary unit tests miss.

### Does a clean race detector run mean the code is safe?

No. Detectors such as Go's race detector and ThreadSanitizer only observe the paths the test executed. They also do not establish freedom from deadlock, which needs bounded completion tests.

### How do you reproduce a race condition reliably?

Use barriers, latches, fake clocks, or a controllable executor to pause one worker after it reads state, let another update that state, then resume the first and assert the invariant.

### Why avoid sleeps in concurrency tests?

A fixed sleep ties the test to machine speed, so it can pass or fail for reasons unrelated to the code. Explicit synchronization points make the failure reproducible.

### How does Factory review concurrent code?

Factory automated code review lists race conditions among the bugs it checks for on pull requests, and a review-guidelines skill can add repository rules. AGENTS.md keeps the exact detector commands in front of Droid.

## Further reading

- [Queue consumer safety](https://factory.com/articles/queue-consumer-safety-coding-agents)
- [Retry safety](https://factory.com/articles/retry-safety-agent-generated-code)
- [Flaky test triage](https://factory.com/articles/flaky-test-triage-coding-agents)
- [Reproducible environments](https://factory.com/articles/reproducible-coding-agent-environments)
- [Go race detector](https://go.dev/doc/articles/race_detector)
