fix(common): Dropping CrossProcessLock must not release the lock if a CrossProcessGuard is still alive#6674
Merged
Hywan merged 5 commits intoJun 25, 2026
Conversation
…er of cross-process lock holders. This patch revisits the way the number of holders is calculated for a cross-process lock. The postulate is the following: Prior to this patch, we used an `Arc<AtomicU32>`. However, `Arc` already does atomic counting. So we do a double atomic counting to calculate the number of holders. It seems a bit a waste of work. This patch replaces `Arc<AtomicU32>` by `Arc<()>`. This patch abstracts this with a new type: `Holders`, with the following method: - `clone` to act as `Arc`, and - `count` to replace the `AtomicU32::load`, it instead calls `Arc::strong_count`, which is the same. Note: the atomic ordering isn't the same: it was `SeqCst` before, now it is `Relaxed`. I've reviewed the code and it does not seem to make any relevant difference. A nice side-effect is that we can implement `CrossProcessLockGuard::lock_has_been_dropped`, because, now, we can know when the lock has been dropped and the guard is “detached”, so it won't any effect at all. It can be convenient! All tests have been updated to test the `count()` in more details, making `test_number_of_holders_follows_the_number_of_guards` useless: this patch removes it.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6674 +/- ##
==========================================
+ Coverage 89.88% 89.89% +0.01%
==========================================
Files 396 396
Lines 110236 110256 +20
Branches 110236 110256 +20
==========================================
+ Hits 99083 99114 +31
+ Misses 7385 7380 -5
+ Partials 3768 3762 -6 ☔ View full report in Codecov by Harness. |
Hywan
commented
Jun 18, 2026
…w_task`. This patch replaces a manual management of a `JoinHandle` by `AbortOnDrop`. At least it makes things explicit and we re-use existing mechanisms.
972cadd to
b354f74
Compare
… a `CrossProcessGuard` is still alive. This patch fixes an important problem with the `CrossProcessLock`. I noticed that when the `CrossProcessLock` is dropped, all `CrossProcessLockGuard`s report 0 holder, and thus, the lock is released, but it's wrong. As long as guards are alive, the lock **MUST NOT** be released. Dropping all the guards releases the lock, but dropping the `CrossProcessLock` —which is not the lock in itself— must not drop the acquired lock. Previously, the number of holders was calculated based on `Arc::strong_count`. The new strategy is to use `Arc` combined with `Weak` because we can count both separately (!): - `CrossProcessLock` holds an `Arc` (the `inner` field). - The `renew_task` holds an `Arc` (a clone of the `inner` field). - `CrossProcessLockGuard` holds a `Weak`. - Counting holders = counting the number of `Weak` pointers. - It is safe to upgrade the `Weak` pointer to an `Arc` (to get information about dirtiness) in a guard because the `renew_task` holds a clone of the `Arc` and will not exit as long as all guards have been dropped. - It is always possible to create a `Weak` pointer (i) either from `CrossProcessLock` by using `Arc::downgrade`, (ii) or from `CrossProcessLockGuard` by cloning it. The `Holders` type is removed as it becomes useless. The new `CrossProcessLockInner` type holds all data useful for the lock to be alive. Counting the number of holders boils down to count the weak pointers of `Weak<CrossProcessLockInner>`.
b354f74 to
fe9d65d
Compare
AtomicU32 to calculate the number of cross-process lock holdersCrossProcessLock must not release the lock if a CrossProcessGuard is still alive
stefanceriu
reviewed
Jun 23, 2026
stefanceriu
left a comment
Member
There was a problem hiding this comment.
This makes sense to me in as much as I understand it but I'm sure somebody else should review it.
Member
Author
|
Let's invite @andybalaam to the party! |
andybalaam
approved these changes
Jun 24, 2026
andybalaam
left a comment
Member
There was a problem hiding this comment.
A few questions in comments, but I think it looks good. I don't understand this as well as I'd like - maybe you could talk me through it @Hywan ?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This patch revisits the way the number of holders is calculated for a cross-process lock. The postulate is the following: Prior to this patch, we used an
Arc<AtomicU32>. However,Arcalready does atomic counting. So we do a double atomic counting to calculate the number of holders. It seems a bit a waste of work. This patch replacesArc<AtomicU32>byArc<()>. This patch abstracts this with a new type:Holders, with the following method:cloneto act asArc, andcountto replace theAtomicU32::load, it instead callsArc::strong_count, which is almost the same. The only difference is that the memory order isn't the same: it wasOrdering::SeqCstbefore, now it isOrdering::Relaxed. I've reviewed the code and it does not seem to make any relevant difference.A nice side-effect is that we can implement
CrossProcessLockGuard::lock_has_been_dropped, because, now, we can know when the lock has been dropped and the guard is “detached”, so it won't have any effect at all. It can be convenient!All tests have been updated to test the
count()in more details, makingtest_number_of_holders_follows_the_number_of_guardsuseless: this patch removes it.Oh wait, now I've realised that we have a big bug :-].
CrossProcessLockGuard::lock_has_been_droppedis wrroooooong! As long as the guard is alive, the lock must be kept alive, of course. The last patch revisits the design of theCrossProcessLockto fix this problem.This last patch fixes an important problem with the
CrossProcessLock. I noticed that when theCrossProcessLockis dropped, allCrossProcessLockGuards report 0 holder, and thus, the lock is released, but it's wrong. As long as guards are alive, the lock MUST NOT be released. Dropping all the guards releases the lock, but dropping theCrossProcessLock—which is not the lock in itself— must not drop the acquired lock.Previously, the number of holders was calculated based on
Arc::strong_count. The new strategy is to useArccombined withWeakbecause we can count both separately (!):CrossProcessLockholds anArc(theinnerfield).renew_taskholds anArc(a clone of theinnerfield).CrossProcessLockGuardholds aWeak.Weakpointers.Weakpointer to anArc(to get information about dirtiness) in a guard because therenew_taskholds a clone of theArcand will not exit as long as all guards have been dropped.Weakpointer (i) either fromCrossProcessLockby usingArc::downgrade, (ii) or fromCrossProcessLockGuardby cloning it.The
Holderstype is removed as it becomes useless. The newCrossProcessLockInnertype holds all data useful for the lock to be alive. Counting the number of holders boils down to counting the weak pointers ofWeak<CrossProcessLockInner>.(see Writing changelog entries).
Signed-off-by: