Skip to main content

concurrent/
chainlock_guard.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::mem::offset_of;
6use core::ptr::NonNull;
7
8use crate::chainlock::{AllowFinalized, ChainLock, RecordBackoff};
9use crate::chainlock_transaction::{ChainLockTransactionToken, Transaction};
10
11// TODO(eieio): Figure out if this type can be merged with lockdep::Guard to provide some level of
12// runtime consistency analysis between chain locks and other lock classes. Chain locks are
13// essentially externally ordered locks, where the acquisition of any number of chain locks behaves
14// as acquiring a single lock class, having its order dependencies with other lock classes tracked.
15
16/// RAII-style acquire/release guard for [`ChainLock`].
17#[repr(C)]
18#[must_use = "if unused, the ChainLock will immediately be released"]
19pub struct ChainLockGuard<'a, T: Transaction> {
20    lock: &'a ChainLock<T>,
21    held: bool,
22    token: ChainLockTransactionToken<T>,
23}
24
25impl<'a, T: Transaction> ChainLockGuard<'a, T> {
26    /// Unconditionally acquires the given chain lock as the first lock in the chain.
27    #[inline]
28    pub fn new(lock: &'a ChainLock<T>, token: &'a ChainLockTransactionToken<T>) -> Self {
29        lock.acquire_first_in_chain(token);
30        Self { lock, held: true, token: ChainLockTransactionToken::new() }
31    }
32
33    /// Adopts the given chain lock that is already acquired via some other mechanism.
34    #[inline]
35    pub const fn adopt(lock: &'a ChainLock<T>, _token: &'a ChainLockTransactionToken<T>) -> Self {
36        Self { lock, held: true, token: ChainLockTransactionToken::new() }
37    }
38
39    /// Associates the given lock capability with this scoped guard, but does not acquire it.
40    /// This is useful in conjunction with the conditional acquire methods below.
41    ///
42    /// # Example
43    ///
44    /// ```ignore
45    /// let mut guard = ChainLockGuard::defer(&lock, token);
46    /// if guard.acquire_or_backoff() {
47    ///     // Lock is acquired and guarded. It will be released when guard goes out of scope.
48    /// } else {
49    ///     // Lock is not acquired or guarded. It will not be released when guard goes out of
50    ///     // scope.
51    /// }
52    /// ```
53    #[inline]
54    pub const fn defer(lock: &'a ChainLock<T>, _token: &'a ChainLockTransactionToken<T>) -> Self {
55        Self { lock, held: false, token: ChainLockTransactionToken::new() }
56    }
57
58    /// Takes responsibility for `lock` from `other`. This is useful for moving the scope of a
59    /// lock acquisition deeper into the call stack in certain locking patterns.
60    ///
61    /// Asserts in debug builds that `other` actually holds `lock`.
62    #[inline]
63    pub fn from_taken(mut other: Self, lock: &'a ChainLock<T>) -> Self {
64        debug_assert!(other.held);
65        debug_assert!(core::ptr::eq(other.lock, lock));
66        other.held = false;
67        Self { lock, held: true, token: ChainLockTransactionToken::new() }
68    }
69
70    /// Releases the capability guarded by this instance without actually releasing the lock,
71    /// returning a moved guard intended to be passed to [`Self::from_taken`] or a callee.
72    ///
73    /// Asserts in debug builds that this guard currently holds the lock.
74    #[inline]
75    pub fn take(self) -> Self {
76        debug_assert!(self.held);
77        self
78    }
79
80    /// Conditionally acquires the lock or returns `false` because of backoff.
81    ///
82    /// Asserts in debug builds that the lock is not already held by this guard.
83    #[inline]
84    #[must_use]
85    pub fn acquire_or_backoff(&mut self) -> bool {
86        debug_assert!(!self.held);
87        self.held = self.lock.acquire_or_backoff(&self.token);
88        self.held
89    }
90
91    /// Conditionally acquires the lock with default options (`AllowFinalized::No`,
92    /// `RecordBackoff::Yes`) or returns `false` if failed.
93    ///
94    /// Asserts in debug builds that the lock is not already held by this guard.
95    #[inline]
96    #[must_use]
97    pub fn try_acquire(&mut self) -> bool {
98        self.try_acquire_with_options(AllowFinalized::No, RecordBackoff::Yes)
99    }
100
101    /// Conditionally acquires the lock with the specified options or returns `false` if failed.
102    ///
103    /// Asserts in debug builds that the lock is not already held by this guard.
104    #[inline]
105    #[must_use]
106    pub fn try_acquire_with_options(
107        &mut self,
108        allow_finalized: AllowFinalized,
109        record_backoff: RecordBackoff,
110    ) -> bool {
111        debug_assert!(!self.held);
112        self.held =
113            self.lock.try_acquire_with_options(allow_finalized, record_backoff, &self.token);
114        self.held
115    }
116
117    /// Conditionally acquires the lock using the given `callable`, or returns `false` if failed.
118    /// This is useful in certain locking patterns where an additional predicate needs to be
119    /// injected into the lock condition.
120    ///
121    /// Asserts in debug builds that the lock is not already held by this guard.
122    #[inline]
123    #[must_use]
124    pub fn try_acquire_with<F>(&mut self, callable: F) -> bool
125    where
126        F: FnOnce(&'a ChainLock<T>) -> bool,
127    {
128        debug_assert!(!self.held);
129        self.held = callable(self.lock);
130        self.held
131    }
132
133    /// Releases the guarded lock and its capability.
134    ///
135    /// Asserts in debug builds that the lock is currently held by this guard.
136    #[inline]
137    pub fn release(&mut self) {
138        debug_assert!(self.held);
139        self.lock.release(&self.token);
140        self.held = false;
141    }
142
143    /// Prevents this guard from releasing the lock when it goes out of scope, but does not
144    /// release the lock or its capability. This is a temporary workaround to allow guards to be
145    /// used in scopes that will release the guarded lock in a function or method called in the
146    /// guarded scope.
147    ///
148    /// # Example
149    ///
150    /// ```ignore
151    /// {
152    ///     let mut guard = ChainLockGuard::new(object.get_lock(), token);
153    ///
154    ///     // Validation with lock held ...
155    ///
156    ///     guard.unguard(); // Avoid double release when guard destructs.
157    ///     do_operation_and_release_lock(object, token);
158    /// }
159    /// ```
160    ///
161    /// Possible alternatives to explore that could replace this workaround:
162    ///
163    /// 1. Move the guard into the callee that will release the lock:
164    ///
165    /// ```ignore
166    /// do_operation_and_release_lock(object, guard.take());
167    /// ```
168    ///
169    /// 2. Make the guard destructor check that the lock is actually held to avoid the double
170    ///    release:
171    ///
172    /// ```ignore
173    /// impl<T: Transaction> Drop for ChainLockGuard<'_, T> {
174    ///     fn drop(&mut self) {
175    ///         if self.held && self.lock.is_held(&self.token) {
176    ///             self.lock.release(&self.token);
177    ///         }
178    ///     }
179    /// }
180    /// ```
181    ///
182    /// All of these approaches have potential advantages and disadvantages that need to be
183    /// considered.
184    ///
185    /// Asserts in debug builds that the lock is currently held by this guard.
186    #[inline]
187    pub fn unguard(&mut self) {
188        debug_assert!(self.held);
189        self.held = false;
190    }
191
192    /// Returns `true` if this guard currently holds the lock.
193    #[inline]
194    pub const fn is_held(&self) -> bool {
195        self.held
196    }
197
198    /// Returns a reference to the underlying [`ChainLock`].
199    #[inline]
200    pub const fn lock(&self) -> &'a ChainLock<T> {
201        self.lock
202    }
203}
204
205impl<T: Transaction> Drop for ChainLockGuard<'_, T> {
206    #[inline]
207    fn drop(&mut self) {
208        if self.held {
209            self.lock.assert_held(&self.token);
210            self.lock.release(&self.token);
211        }
212    }
213}
214
215// Verify layout equivalence with C++ `concurrent::ChainLockGuard<Transaction>`.
216// In C++: `ChainLock<Transaction>& lock_` (offset 0, size 8) + `bool held_` (offset 8, size 1) ->
217// total size 16, align 8.
218struct DummyTx;
219impl Transaction for DummyTx {
220    type ContentionState = core::sync::atomic::AtomicU32;
221    fn active() -> Option<NonNull<Self>> {
222        None
223    }
224    fn active_ref(_: &ChainLockTransactionToken<Self>) -> &Self {
225        unimplemented!()
226    }
227    fn yield_cpu() {}
228    fn token(&self) -> crate::Token {
229        crate::Token::UNLOCKED
230    }
231    fn assert_not_finalized(&self) {}
232    fn assert_num_locks_held(&self, _: u32) {}
233    fn on_acquire(&self, _: &ChainLockTransactionToken<Self>) {}
234    fn on_release(&self, _: &ChainLockTransactionToken<Self>) {}
235    fn on_contention(&self, _: &ChainLockTransactionToken<Self>) {}
236    fn record_backoff_or_retry(
237        &self,
238        _: crate::Token,
239        _: &crate::AtomicToken,
240        _: &Self::ContentionState,
241        _: &ChainLockTransactionToken<Self>,
242    ) -> bool {
243        true
244    }
245    fn release_and_notify<F: FnOnce()>(
246        &self,
247        _: &Self::ContentionState,
248        do_release: F,
249        _: &ChainLockTransactionToken<Self>,
250    ) {
251        do_release();
252    }
253}
254
255zr::static_assert!(size_of::<ChainLockGuard<'static, DummyTx>>() == 16);
256zr::static_assert!(align_of::<ChainLockGuard<'static, DummyTx>>() == 8);
257zr::static_assert!(offset_of!(ChainLockGuard<'static, DummyTx>, lock) == 0);
258zr::static_assert!(offset_of!(ChainLockGuard<'static, DummyTx>, held) == 8);