The same symptom of hanging can be caused by lock-order cycles, missed notifications, or simply the lock holder not getting CPU time. First, determine what progress is missing, then select the appropriate evidence.
Target of Investigation
Where to Look
Dependency and race statistics
Sections 1–4
Data races and context
Sections 5–6
Stall types and fix verification
Sections 7–9
Synchronization issues include at least deadlocks, data races, livelocks, long lock holding, and lost wakeups. While they may all manifest as "hanging," the required evidence differs. First, determine who has no progress and what they are waiting for, then choose the tool; do not attribute all alerts to "lock not released."
1. What Each Tool Answers
Tool/Mechanism
Primary Answer
Cannot Prove
lockdep / PROVE_LOCKING
Do observed dependencies potentially form a lock-order or context conflict?
Paths that did not run are definitely correct
lockstat
Which lock classes have high contention, long wait times, or long hold times?
Long waits are necessarily deadlocks
KCSAN
Do sampled memory accesses expose races or related issues?
No report in one run means no race exists
DEBUG_ATOMIC_SLEEP
Is an operation that might sleep incorrectly called in the current context?
All blocking API business waits are correct
hung task
Are certain uninterruptible-waiting tasks making no long-term progress?
The reason for waiting is definitely a mutex
soft/hard lockup
Is CPU scheduling or interrupt progress abnormal?
A specific application definitely holds the wrong lock
RCU stall
Is the grace period or related processing unable to advance for a long time?
An unlock is definitely missing
2. Why Lockdep Reports Before a Deadlock Occurs
When a task holds lock class A and acquires B, lockdep records A→B. If another path exhibits B→A, a potential cycle is formed even if they never actually deadlocked simultaneously; longer dependency chains also require analysis.
Lockdep also tracks usage states such as hardirq/softirq. For example, if a lock is used in an interrupt handler, and another path holds it while allowing that interrupt to preempt, it may cause a self-deadlock on the same CPU. Such context conflicts cannot be discovered solely by checking ABBA orders on two different CPUs.
Add each observed acquisition order to the graph one by one. Edges can come from different times; a closed loop already constitutes a sequence contradiction that needs investigation, without waiting for two threads to be stuck on-site simultaneously.
Preparing the visual
Dependency cycles can be detected even without simultaneous deadlocks
Lockdep merges observed lock-class dependencies. A→B and B→A occurring at different times also form potential cycles; no need to wait for actual tasks to be stuck simultaneously.
{"id":"lockdep-order-graph","title":"Dependency cycles can be detected even without simultaneous deadlocks","summary":"Lockdep merges observed lock-class dependencies. A→B and B→A occurring at different times also form potential cycles; no need to wait for actual tasks to be stuck simultaneously.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label for=\"edge\" data-i18n=\"choose\"></label><select id=\"edge\"><option value=\"AB\">A → B</option><option value=\"BA\">B → A</option><option value=\"AC\">A → C</option><option value=\"CA\">C → A</option><option value=\"BC\">B → C</option><option value=\"CB\">C → B</option></select><div class=\"actions\"><button id=\"record\" data-i18n=\"record\"></button></div><svg viewBox=\"0 0 320 240\" role=\"img\" aria-label=\"A, B, C\"><defs><marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10z\" fill=\"context-stroke\"/></marker></defs><path id=\"edge-AB\" d=\"M80 45 Q160 5 240 45\" class=\"edge\" marker-end=\"url(#arrow)\"/><path id=\"edge-BA\" d=\"M240 70 Q160 105 80 70\" class=\"edge\" marker-end=\"url(#arrow)\"/><path id=\"edge-AC\" d=\"M50 80 Q40 150 140 194\" class=\"edge\" marker-end=\"url(#arrow)\"/><path id=\"edge-CA\" d=\"M135 180 Q95 120 74 81\" class=\"edge\" marker-end=\"url(#arrow)\"/><path id=\"edge-BC\" d=\"M246 83 Q224 140 180 182\" class=\"edge\" marker-end=\"url(#arrow)\"/><path id=\"edge-CB\" d=\"M184 200 Q289 150 270 80\" class=\"edge\" marker-end=\"url(#arrow)\"/><circle cx=\"60\" cy=\"60\" r=\"24\"/><text x=\"60\" y=\"66\" text-anchor=\"middle\">A</text><circle cx=\"260\" cy=\"60\" r=\"24\"/><text x=\"260\" y=\"66\" text-anchor=\"middle\">B</text><circle cx=\"160\" cy=\"200\" r=\"24\"/><text x=\"160\" y=\"206\" text-anchor=\"middle\">C</text></svg><div class=\"track-row\"><span data-i18n=\"edges\"></span><div id=\"edges\" class=\"track\"></div></div><div class=\"card\"><header data-i18n=\"cycle\"></header><div id=\"cycle\" class=\"readout\"></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\nsvg{width:100%;height:auto;max-height:310px;display:block;margin:15px auto}svg circle{fill:var(--surface);stroke:var(--accent);stroke-width:2}svg text{fill:var(--ink);font:18px var(--viz-font-ui)}.edge{fill:none;stroke:var(--rule);stroke-width:2;stroke-dasharray:4 5}.edge.on{stroke:var(--accent);stroke-dasharray:none;stroke-width:3}.edge.cycle{stroke:var(--second)}#edges{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.readout{min-height:5.3em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let edges=new Set;function findCycle(){for(const start of 'ABC'){function walk(path){const n=path.at(-1);for(const to of 'ABC'){if(!edges.has(n+to))continue;if(to===start)return [...path,to];if(!path.includes(to)){const r=walk([...path,to]);if(r)return r;}}return null;}const r=walk([start]);if(r)return r;}return null;}function draw(){const cycle=findCycle(),cs=new Set(cycle?cycle.slice(0,-1).map((v,i)=>v+cycle[i+1]):[]);document.querySelectorAll('.edge').forEach(e=>{const key=e.id.slice(5);e.classList.toggle('on',edges.has(key));e.classList.toggle('cycle',cs.has(key));});cells('edges',['AB','BA','AC','CA','BC','CB'].map(k=>({text:k[0]+' → '+k[1]+(edges.has(k)?' ✓':' —'),cls:cs.has(k)?'gold':edges.has(k)?'on':'empty'})));set('cycle',cycle?cycle.join(' → '):t('none'));$('#record').disabled=edges.has($('#edge').value);set('explanation',t('note'));}$('#record').onclick=()=>{edges.add($('#edge').value);draw();};$('#edge').onchange=draw;$('#reset').onclick=()=>{edges=new Set;draw();};draw();","audio":false,"strings":{"choose":"Hold the left lock, then acquire the right lock","cycle":"Detected cycle","edges":"Observed dependencies","heading":"Dependency cycles can be detected even without simultaneous deadlocks","intro":"Choose \"which lock class is held, then which lock class is acquired,\" and record one independent execution. The graph accumulates historical dependencies to find cycles formed by two or three edges.","none":"No cycle currently","note":"First record A→B, B→C, then record C→A. The last edge closes the known path; the alert involves the entire dependency chain, not just the last locking point.","record":"Record this executed dependency","reset":"Reset","scope":"Three lock classes, no self-recursion; operations are completed each time they are recorded, and the actual lock holder is not retained. This only verifies the normal order graph, not IRQ contexts, subclasses, or the full implementation of lockdep. No cycle only means the currently observed edges are acyclic, not that unexecuted paths are correct.","scopeLabel":"Model Scope and Conventions"}}
Lock Classes and Lock Instances
Lock classes are usually determined by initialization location/explicit class keys, rather than "all spinlocks are one class" or "different acquisition functions must be different classes." Locks in multiple objects of the same class can be grouped into one class; a lock in one object acquired in different functions does not automatically become different classes.
If the algorithm indeed acquires locks of the same class in a parent-child hierarchy simultaneously, you can express the proven order using appropriate subclass/nested APIs. However, adding subclasses only provides more information to the validator; it does not automatically make the actual order correct. Incorrect annotations may hide real problems.
The report provides the current acquisition location, existing dependencies, and related stacks to reconstruct the contradictory path; it is not a complete history recording of every lock hold since boot. See lockdep design.
3. How to Read a Lockdep Report
Reconstruct the dependencies according to the report, rather than just changing the last triggered location:
Identify the lock currently being acquired and the locks already held.
Follow existing dependencies to restore another or multiple opposite paths.
Confirm object instances, concurrency possibilities, and interrupt/preemption states.
These conditions collectively determine how the dependency cycle forms and also determine whether to change the lock order or object relationships.
Do not just change the last lock call that triggered the alert. Common fixes include unifying lock order, narrowing critical sections, phased operations, avoiding holding locks while waiting for callbacks, or redesigning object relationships. If adjusting hierarchy annotations, you must provide the basis for why objects held simultaneously must satisfy that hierarchy.
lockdep_assert_held() can turn the prerequisite "caller must hold a certain lock" for internal functions into a checkable contract. Long-term testing with no reports still requires confirming that the validator is actually enabled, has not stopped checking after an earlier error, and covers relevant errors and exit paths.
4. Lockstat: Look at Contention Count, Wait Time, and Hold Time Together
/proc/lock_stat reports lock-class statistics, including fields for acquisitions, contention, wait time, and hold time, after the corresponding configuration/execution control is enabled. Time units should be read from the version-specific header/documentation; do not fabricate a fixed-field nanosecond table and blindly sort by the second column.
Statistical Manifestation
Next Step Verification
High acquisitions, low contention
Does this lock really have optimization value?
Low contention, long single wait time
Is the lock holder doing slow I/O, being preempted, restricted by scheduling, or waiting for another lock?
High contention, short single hold time
Shared cache line, lock granularity, and sharding opportunities
Counts and durations must be combined; you cannot determine the root cause based on a single maximum number.
If measurements alter scheduling/timing, compare load performance before and after enabling. Sharding to reduce contention may also increase aggregate cost and consistency difficulty; do not solely pursue smaller lockstat numbers. See lockstat documentation.
5. KCSAN: Sampled Detection, Not Full Timestamps for Every Access
KCSAN uses compiler instrumentation and software watchpoints/delays to sample conflicting accesses; it does not build an exact full-history happens-before graph for all memory operations. Configuration can change sampling, delays, and handling of access types, so overhead should not be fixedly stated as ten times.
The address, size, read/write type, and two call stacks in the report are entry points for analysis. Two accesses not sharing a common lock do not necessarily indicate an error, as they may be synchronized via atomic/release protocols; conversely, the presence of a lock in the code does not mean every access is protected by it.
Markers like READ_ONCE/WRITE_ONCE, atomic, and data_race() must reflect the real protocol. Wrapping a report location with data_race() only expresses "this is a judged-allowed race"; it does not fix ordering, atomicity, or lifetime. Exceptions should explain why reading inconsistent values is harmless and why subsequent usage is safe.
For lock-free release problems, you can use litmus tests from the Linux Kernel Memory Model (LKMM) to analyze allowed results, then combine with KCSAN and stress testing. User-space C/C++ atomic model intuition cannot replace the kernel memory model without verification. See KCSAN and LKMM.
6. Atomic Context Sleep and Wait Protocols
Checks like CONFIG_DEBUG_ATOMIC_SLEEP usually report "sleeping function called from invalid context" via locations such as might_sleep. Such diagnostics do not uniformly equal immediate BUG/panic; specific handling is affected by the path and configuration.
Check held locks, preempt count, interrupt status, and call chain to confirm if APIs that might allocate/block are called in raw spinlock, hardirq, or other non-sleepable contexts. A specific allocation hitting a fast path and not blocking does not prove such a call is legal.
Lost wakeups do not necessarily produce lock dependency cycles: a thread might miss the only wakeup after checking the condition but before actually registering for wait. You should use correct protocols such as wait_event/completion, and verify timeouts, cancellation, and object lifetime after exit.
7. Hung Task, Softlockup, and Hardlockup
Hung task focuses on tasks that are in uninterruptible wait for a long time without corresponding progress. D state may be waiting for storage, network file systems, completions, or locks. The timeout threshold is configurable; 120 seconds is not an immutable constant across systems.
Softlockup indicates that the CPU has not allowed watchdog-related tasks to make scheduling progress for a long time, and it may still be able to handle interrupts. Long non-preemptible loops, scheduling starvation, etc., will affect it.
Hardlockup in common NMI/perf implementations is checked by whether normal timer interrupts are advancing, via NMIs that can still enter. It is not judged by "because normal interrupts are disabled, NMIs are also not received." Detector implementations vary by architecture, PMU, configuration, and virtualization support; VM pauses, etc., will also interfere with timing.
All three can appear simultaneously with RCU stall, but they express different missing progress. First, find repeated stacks and wait relationships, then judge the common root cause. See lockup watchdog.
8. Read-Only Check of Detection Capabilities and Thresholds
State files may not exist because configuration is not enabled; permission denial does not equal non-existence of functionality. /proc/<pid>/stack requires corresponding configuration and access permissions; a single stack snapshot for one task is just that—a snapshot; associate with other tasks and repeated samples when necessary.
9. How to Verify After Fixing
Use the same configuration and context as the problem to first cover known trigger paths, then cover concurrent updates, error returns, timeouts, and exit cleanup. For lock-order issues, confirm dependencies no longer conflict; for race issues, confirm invariants and release/lifetime protocols hold; for performance issues, remeasure wait distributions and business latency.
Do not replace explanation by disabling detectors, raising timeouts, or adding annotations. Tool reports are evidence; final conclusions should land on specific invariants, lock orders, and object lifecycles.