-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Prevent endless loop in lock cycle detection #1510 #1635
Conversation
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
I signed the contributor agreement, no idea when the bot will detect this (or whether it must be re-triggered). |
OK I understand the problem now.
What this code (I assume) wants to do is:
And specifically, during 2.: 2.1. calls
If the entire sequence was done in 1 synchronized block, there wouldn't be a problem. However, So what can occur (and occurs in the reproducer), is that 1 thread adds to Unfortunately I don't know whether the adding the removal from |
@@ -249,6 +249,9 @@ private ListMultimap<Thread, ID> detectPotentialLocksCycle() { | |||
MultimapBuilder.linkedHashKeys().arrayListValues().build(); | |||
// lock that is a part of a potential locks cycle, starts with current lock | |||
ReentrantCycleDetectingLock<?> lockOwnerWaitingOn = this; | |||
// Its possible the lock owner thread has not removed itself yet from the waiting-on-lock list. | |||
// So we remove it here, to prevent an endless loop. See #1510. | |||
lockThreadIsWaitingOn.remove(lockOwnerWaitingOn.lockOwnerThread); |
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.
this.remove(this.lockOwnerThread);
?
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.
You mean lockThreadIsWaitingOn.remove(this.lockOwnerThread)
?
Sure, why not. I prefer to use lockOwnerWaitingOn
after this
is stored into it, but its whichever.
From my POV this is ready for a merge, IMO it fixes the concurrency problem in a reliable and simple way. |
Looks like there are fails in the cycle detection test, will have to check why:
|
…detectPotentialLocksCycle() Due to how code in ReentrantCycleDetectingLock.lockOrDetectPotentialLocksCycle() is synchronized, its possible for a thread to both own/hold a lock (according to ReentrantCycleDetectingLock.lockOwnerThread) and wait on the same lock (according to CycleDetectingLock.lockThreadIsWaitingOn). In this state, if another thread tries to hold the same lock an endless loop will occur when calling detectPotentialLocksCycle(). With this change detectPotentialLocksCycle() removes the lock owning thread from ReentrantCycleDetectingLock.lockOwnerThread, if it detects that "this" lock is both waited on and owned by the same thread. This prevents the endless loop during cycle detection. Fix for: google#1510
@andrewthehan, @sameb, @cpovirk, @markmarch, anyone : could we please move forward with review? |
…would previously fail: * T1: Enter `lockOrDetectPotentialLocksCycle`: - Lock CAF.class, add itself to `lockThreadIsWaitingOn`, unlock CAF.class - Lock the cycle detecting lock (CDL) - Lock CAF.class, mark itself as `lockOwnerThread`, remove itself from `lockThreadIsWaitingOn` - Exit `lockOrDetectPotentialLocksCycle` * T1: Re-enter `lockOrDetectPotentialLocksCycle`: - Lock CAF.class, add itself to `lockThreadIsWaitingOn`, unlock CAF.class T2: Enter `lockOrDetectPotentialLocksCycle` - Lock CAF.class, invoke `detectPotentialLocksCycle`. At this point, `detectPotentialLocksCycle` will now loop forever, because the `lockOwnerThread` is also in `lockThreadIsWaitingOn`. During the course of looping forever, it will OOM, because it's building up an in-memory structure of what it thinks are cycles. The solution is to avoid the re-entrant T1 from adding itself to `lockThreadIsWaitingOn` if it's already the `lockOwnerThread`. It's guaranteed that it won't relinquish the lock concurrently, because it's the exact same thread that owns it. Fixes #1635 and Fixes #1510 PiperOrigin-RevId: 524376697
…would previously fail: * T1: Enter `lockOrDetectPotentialLocksCycle`: - Lock CAF.class, add itself to `lockThreadIsWaitingOn`, unlock CAF.class - Lock the cycle detecting lock (CDL) - Lock CAF.class, mark itself as `lockOwnerThread`, remove itself from `lockThreadIsWaitingOn` - Exit `lockOrDetectPotentialLocksCycle` * T1: Re-enter `lockOrDetectPotentialLocksCycle`: - Lock CAF.class, add itself to `lockThreadIsWaitingOn`, unlock CAF.class T2: Enter `lockOrDetectPotentialLocksCycle` - Lock CAF.class, invoke `detectPotentialLocksCycle`. At this point, `detectPotentialLocksCycle` will now loop forever, because the `lockOwnerThread` is also in `lockThreadIsWaitingOn`. During the course of looping forever, it will OOM, because it's building up an in-memory structure of what it thinks are cycles. The solution is to avoid the re-entrant T1 from adding itself to `lockThreadIsWaitingOn` if it's already the `lockOwnerThread`. It's guaranteed that it won't relinquish the lock concurrently, because it's the exact same thread that owns it. Fixes #1635 and Fixes #1510 PiperOrigin-RevId: 524376697
This change prevents an endless loop in: ReentrantCycleDetectingLock.detectPotentialLocksCycle()
Due to how code in ReentrantCycleDetectingLock.lockOrDetectPotentialLocksCycle() is synchronized,
its possible for a thread to both own/hold a lock (according to ReentrantCycleDetectingLock.lockOwnerThread)
and wait on the same lock (according to CycleDetectingLock.lockThreadIsWaitingOn).
In this state, if another thread tries to hold the same lock an endless loop will occur when calling detectPotentialLocksCycle().
With this change detectPotentialLocksCycle() removes the lock owning thread from
ReentrantCycleDetectingLock.lockOwnerThread,
if it detects that "this" lock is both waited on and owned by the same thread.
This prevents the endless loop during cycle detection.
Fix for: #1510