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);