Siyao Meng created HDDS-16685:
---------------------------------

             Summary: OM deadlock: nested snapshot get() takes SNAPSHOT_DB_LOCK 
stripes out of order against acquireAll
                 Key: HDDS-16685
                 URL: https://issues.apache.org/jira/browse/HDDS-16685
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng


h3. Mechanism
{{OzoneManagerLock.acquireAll}} ({{:158-164}}) takes every stripe of a resource 
in ascending index order, blocking, with no backoff and no all or nothing 
semantics. {{SnapshotCache.lock()}} reaches it through 
{{acquireResourceWriteLock}} ({{:382}}).

{{SnapshotCache.get(UUID)}} instead takes exactly one stripe, 
{{acquireReadLock(SNAPSHOT_DB_LOCK, key.toString())}} at {{:217}}, chosen by 
the UUID hash, in whatever order the caller opens snapshots. A snapdiff holding 
the from snapshot on a high stripe and then requesting the to snapshot on a low 
stripe inverts against a concurrent {{acquireAll}} that has already taken the 
low stripe.

{{MultiSnapshotLocks}} exists and uses the ordered bulk path, so the ordered 
primitive was written on purpose and the snapdiff callers bypass it. The DAG 
tracker added by HDDS-13960 orders resources, not stripes within a resource, so 
it does not cover this case.

h3. Impact
Permanent deadlock between the snapdiff thread and the checkpoint or follower 
bootstrap thread. Every later snapshot operation that needs one of the already 
taken stripes blocks behind it. Untimed on both sides, restart only, and 
invisible to JVM deadlock detection because read holds have no owner.

h3. Reproduction
{{repro/test_bugMC-5_stripe_inversion_deadlock.sh}} with 
{{repro/java/SpeculaMc5StripeInversionTest.java}}. Evidence in 
{{repro/logs/ev-mc5.log}}: the Level 0 sequential case completes as expected, 
then the two thread case leaves both threads unfinished after 60s, with 
{{findDeadlockedThreads()=null}} and thread dumps showing the snapdiff thread 
parked in {{SnapshotCache.get}} at {{:217}} and the checkpoint thread parked in 
{{acquireAll}} under {{SnapshotCache.lock}}.

h3. No patch attached
Fixing this means deciding how nested snapshot acquisition is ordered. 
MultiSnapshotLocks already wraps the ordered bulk path, so routing the snapdiff 
callers through it is the natural fix, but that changes how those callers hold 
snapshots across a diff and the alternative is to order the stripes inside 
SnapshotCache.get itself. A regression test for the deadlock would be slow and 
timing dependent in CI. Filing the analysis so the approach can be agreed first.

Found by TLA+ model checking and code review of the OM snapshot cache under 
HDDS-15926, on commit 20e6a1c0f6039248373efd3eb1d7f2afcf7f1535. Generated with 
Specula (Claude Opus 5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to