-
Notifications
You must be signed in to change notification settings - Fork 594
HDDS-9871. [Snapshot] Fix Deadlock in SnapshotCache. #5751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Good catch @aswinshakil . Is it possible to fix this by adjusting the lock order in |
|
In |
hemantk-12
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @aswinshakil for catching this nasty bug.
Changes look good to me.
| if (rcOmSnapshot.decrementRefCount() == 0L) { | ||
| synchronized (pendingEvictionList) { | ||
| // v is eligible to be evicted and closed | ||
| pendingEvictionList.add(rcOmSnapshot); | ||
| } | ||
|
|
||
| return v; | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figure I would outline two possible race condition scenarios without the dbMap lock here. Though I believe both are already protected by my existing implementation.
Case 1:
Since pendingEvictionList is a Set, it should be fine if multiple threads are calling release(key) as the same time while some of those threads observed that rcOmSnapshot's ref count reached 0 at the same time. So the logic here would still be correct in this case.
Case 2: Multiple threads interleaving get() and release()
- Thread 1 executes
get(key)right pastrcOmSnapshot.incrementRefCount()(after the change in this PR). ref count is 1 at this point. - Thread 2 executes
release(key)pastrcOmSnapshot.decrementRefCount(). ref count is 0 at this point. (*) - Thread 2 continues to execute
pendingEvictionList.add(rcOmSnapshot), pendingEvictionList now has the snapshot, ifcacheSizeLimitis exceeded,cleanup()will pick up the snapshot immediately. - Thread 1 executes
pendingEvictionList.remove(rcOmSnapshot). snapshot is (supposed to be) removed from eviction list while the snapshot is already removed fromdbMap(cleaned up) bycleanup().
(*) While actually Step 2 in Case 2 above would be prevented because decrementRefCount() throws if a thread tries to release() a snapshot it had not get() before, as a safety mechanism. And this safety, together with atomic ref count should also prevent Case 1 from happening in the first place (for which multiple threads would observe that ref count reached 0 at the same time).
cleanup(), as of this time, is only called at the end of each get() and release(). As it doesn't run in a seperate thread it should not pose extra correctness issue at least for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@smengcl From the code I don't see release() being used anywhere. How do we decrement reference count if we don't use release(). The only other place we decrement reference count is in get()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aswinshakil Indeed SnapshotCache#release is not called directly right now and is only kept to conform to Guava cache interface (and just in case we would need it). Ref count is currently decremented by ReferenceCounted#close because we encourage using try-with-resource on snapshot instances.
|
Thanks @aswinshakil for the patch. Thanks @hemantk-12 for the review. |
What changes were proposed in this pull request?
When accessing the snapshot cache, Multiple threads fall into a deadlock between ConcurrentHashMap(
dbMap) and SynchronizedSet(pendingEvictionList).Scenario:
dbMap.compute()holds the ConcurrentHashMap lock while trying to hold the lock forpendingEvictionListSet.cleanup()method holds the lock forpendingEvictionListSet while trying to hold the lock for the ConcurrentHashMap (dbMap).One way to avoid this is to follow lock ordering.
dbMap.compute()is removed so that we never get lock in the 1st order.What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/HDDS-9871
How was this patch tested?
Existing Test.