Debugging Deadlocks and Race Conditions with AI
Concurrency bugs don't reproduce on demand and rarely show up in a single stack trace. A structured approach to feeding thread dumps and timing evidence to AI instead of guessing.
A null-reference exception hands you a stack trace pointing at the exact line. A deadlock hands you a frozen application, no exception, and — if you're lucky — a thread dump full of threads waiting on each other. Concurrency bugs are where "paste the error and ask what's wrong" breaks down completely, because there is no single error; there's a timing relationship between multiple threads that has to be reconstructed.
Why concurrency bugs need a different approach
- They often don't reproduce reliably — the bug depends on exact timing/interleaving that varies run to run.
- A single stack trace shows one thread's state, not the relationship between threads that caused the deadlock.
- The fix that "looks right" (adding a lock) can introduce a new deadlock elsewhere if lock ordering isn't reasoned through explicitly.
Pasting one stack trace and asking "why did this deadlock" invites a plausible-sounding guess. Pasting the full thread dump — every thread's state and what it's waiting on — is what actually lets the model (or a human) reconstruct the cycle.
The evidence to gather before asking
| Evidence | Why it matters |
|---|---|
| Full thread dump at the moment of the hang | Shows every thread's state (BLOCKED, WAITING) and exactly which lock/monitor it's waiting on |
| The lock acquisition order in the relevant code paths | A deadlock is fundamentally two threads acquiring the same locks in opposite order — this is the root cause, not the symptom |
| Whether the hang is 100% reproducible or intermittent | Intermittent points to a genuine race; consistent points to a structural lock-ordering bug that will always trigger under a specific code path |
| Recent changes to the affected code paths | Concurrency bugs are frequently introduced by a change that looked safe in isolation but altered lock ordering or added a new shared-state access |
Worked example
Context: Application hung under load. Here is the full thread dump at the time of the hang: [paste complete thread dump, not a single thread]
Here is the code for the two classes involved (OrderProcessor, InventoryLock): [paste code]
Task: Identify the deadlock cycle — which threads are waiting on which locks, and in what order each thread acquired them.
Constraints:
- Show the exact cycle: Thread A holds lock X, waits for lock Y; Thread B holds lock Y, waits for lock X.
- Base this only on the thread dump and code provided — do not speculate about threads or locks not shown in the evidence.
- Propose a fix that establishes a single consistent lock acquisition order across both code paths, not just a workaround for this one occurrence.
Output format: 1) the deadlock cycle explained thread-by-thread, 2) the specific lines where ordering diverges, 3) the fix.Race conditions without a deadlock
Not every concurrency bug hangs — a race condition can silently produce wrong data with no crash at all, which is worse to detect. For these, the useful prompt pattern is the same one used for thread-safety review generally: walk through what happens if two threads execute the suspect code path at the exact same time, step by step, rather than asking a yes/no "is this thread-safe."
A proposed fix for a timing-dependent bug should be verified with a stress test that exercises concurrent access, not just a single-threaded unit test passing. The absence of a crash in one run proves very little for concurrency bugs.
This is one of the harder debugging categories precisely because it resists the "paste the error, get the fix" pattern that works for most bugs — the Debugging module treats it separately from the general scientific-debugging method, with its own evidence checklist before any fix is proposed.