Skip to main content

concurrent/
chainlock.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::marker::PhantomPinned;
6use core::mem::offset_of;
7use core::ptr::NonNull;
8use core::sync::atomic::Ordering;
9
10use crate::chainlock_transaction::{
11    AtomicToken, ChainLockTransactionToken, ContentionState, Token, Transaction,
12};
13
14// # General Chain Lock Facility
15//
16// Chain locks are a type of spin lock designed for use cases where multiple CPUs need to acquire
17// various sets of locks in different orders, such as in dynamic object graphs where nodes have
18// lock-synchronized states and references to other nodes. Chain locks provide an arbitration
19// mechanism that is particularly useful in operations that require locking multiple nodes
20// simultaneously, such as propagating state changes or altering the graph's connectivity.
21// Operations initiated from different nodes in the graph may require locks on some of the same
22// nodes, but in different orders. If ordinary spin locks were used, such lock order variations
23// could lead to deadlocks.
24//
25// Similar to other spin locks, chain locks use atomic variables for lock state and memory
26// ordering, and require disabling interrupts to guarantee IRQ-safe critical sections. However,
27// chain locks differ from spin locks in that they don't use CPU IDs or other static values to
28// indicate the locked state and identify the owner. Instead, the locked value is derived from a
29// global generation counter that provides a priority invariant used in arbitration. An operation
30// that requires one or more chain locks is performed in a transaction that carries a unique
31// generation count. Within a transaction, a CPU can attempt to lock multiple chain locks using the
32// same generation count, giving rise to the name "chain lock."
33//
34// The generation count provides a fair arbitration mechanism when multiple CPUs attempt to acquire
35// the same locks in potentially different orders. During arbitration, the transaction with the
36// lowest generation count has the highest priority. If a CPU encounters contention and has a
37// transaction with a higher priority than the lock's current owner, it simply spins until the lock
38// is released. However, if the transaction has a lower priority, the CPU must back off by
39// releasing all held chain locks and restarting the locking sequence. This is because the CPU with
40// the higher priority transaction may be waiting to acquire one or more of these locks. Once a
41// transaction is complete its generation count is retired and never reused, ensuring that all
42// transactions eventually make progress, even under heavy contention.
43//
44
45/// Result of attempting to acquire a [`ChainLock`].
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum Result {
48    /// Lock successfully acquired.
49    Ok,
50
51    /// Lock is held by a lower priority transaction. Returned only by internal try-acquire logic.
52    /// Other acquire methods spin internally and do not return this result.
53    Retry,
54
55    /// Lock is held by a higher priority transaction, requiring the current transaction to follow
56    /// the back off protocol.
57    Backoff,
58
59    /// Lock is already held by the current transaction and would otherwise deadlock, requiring the
60    /// current transaction to follow the cycle detected protocol. May be fatal in some
61    /// environments.
62    Cycle,
63}
64
65/// Specifies whether [`ChainLock::try_acquire_with_options`] is allowed when the active
66/// transaction has already been finalized.
67#[repr(u8)]
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum AllowFinalized {
70    /// Disallow acquiring the lock if the active transaction is already finalized.
71    No = 0,
72    /// Allow acquiring the lock even if the active transaction is already finalized.
73    Yes = 1,
74}
75
76/// Specifies whether [`ChainLock::try_acquire_with_options`] should record contention backoff
77/// state if acquisition fails due to a higher-priority transaction holding the lock.
78#[repr(u8)]
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum RecordBackoff {
81    /// Do not record backoff state on contention.
82    No = 0,
83    /// Record backoff state on contention with a higher-priority transaction.
84    Yes = 1,
85}
86
87#[derive(Clone, Copy)]
88struct AttemptResult {
89    lock_result: Result,
90    observed_token: Token,
91}
92
93/// Environment-independent chain lock interface and protocol.
94#[repr(C)]
95pub struct ChainLock<T: Transaction> {
96    state: AtomicToken,
97    contention_state: T::ContentionState,
98    _pin: PhantomPinned,
99}
100
101impl<T: Transaction> ChainLock<T> {
102    const UNLOCKED_TOKEN: Token = Token::UNLOCKED;
103    const MAX_TOKEN: Token = Token::MAX;
104
105    /// Creates a new unlocked `ChainLock`.
106    #[inline]
107    pub const fn new() -> Self {
108        Self {
109            state: AtomicToken::new(Self::UNLOCKED_TOKEN),
110            contention_state: T::ContentionState::INIT,
111            _pin: PhantomPinned,
112        }
113    }
114
115    /// Acquires the lock without backoff, spinning until acquired. This method may be used for the
116    /// first chain lock acquired in a transaction as an optimization, since backoff is unnecessary
117    /// when no other chain locks are held.
118    ///
119    /// Asserts if a cycle is detected or if any chain locks are currently held.
120    #[inline]
121    pub fn acquire_first_in_chain(&self, token: &ChainLockTransactionToken<T>) {
122        let transaction = T::active_ref(token);
123        transaction.assert_not_finalized();
124        transaction.assert_num_locks_held(0);
125
126        let tx_token = transaction.token();
127        loop {
128            let result = self.attempt_acquire_common(tx_token);
129            if result.lock_result == Result::Ok {
130                transaction.on_acquire(token);
131                return;
132            }
133
134            transaction.on_contention(token);
135
136            debug_assert_ne!(result.lock_result, Result::Cycle);
137            T::yield_cpu();
138        }
139    }
140
141    /// Acquires the lock, spinning until acquired or returning failure because the caller should
142    /// back off due to contention with a higher priority transaction. Returns `true` if the lock
143    /// was acquired or `false` if the current transaction should back off. Asserts if a cycle is
144    /// detected.
145    #[inline]
146    #[must_use]
147    pub fn acquire_or_backoff(&self, token: &ChainLockTransactionToken<T>) -> bool {
148        let transaction = T::active_ref(token);
149        transaction.assert_not_finalized();
150
151        let tx_token = transaction.token();
152        loop {
153            let result = self.attempt_acquire_common(tx_token);
154            if result.lock_result == Result::Ok {
155                transaction.on_acquire(token);
156                return true;
157            }
158
159            transaction.on_contention(token);
160
161            // If recording the backoff fails, the transaction owning the lock changed, which could
162            // be either higher or lower priority than the current transaction. In that case, retry
163            // the acquisition instead of returning false and unwinding the transaction.
164            if result.lock_result == Result::Backoff
165                && transaction.record_backoff_or_retry(
166                    result.observed_token,
167                    &self.state,
168                    &self.contention_state,
169                    token,
170                )
171            {
172                return false;
173            }
174
175            debug_assert_ne!(result.lock_result, Result::Cycle);
176            T::yield_cpu();
177        }
178    }
179
180    /// Acquires the lock, spinning until acquired or returning `Err` with [`Result::Backoff`] or
181    /// [`Result::Cycle`]. Returns `Ok(())` if the lock was acquired or `Err(result)` if acquiring
182    /// the lock failed.
183    #[inline]
184    pub fn acquire_or_result(
185        &self,
186        token: &ChainLockTransactionToken<T>,
187    ) -> core::result::Result<(), Result> {
188        let transaction = T::active_ref(token);
189        transaction.assert_not_finalized();
190
191        let tx_token = transaction.token();
192        loop {
193            let mut result = self.attempt_acquire_common(tx_token);
194            if result.lock_result == Result::Ok {
195                transaction.on_acquire(token);
196                return Ok(());
197            }
198
199            transaction.on_contention(token);
200
201            // If recording the backoff fails, the transaction owning the lock changed, which could
202            // be either higher or lower priority than the current transaction. In that case, retry
203            // the acquisition instead of failing and returning the result.
204            if result.lock_result == Result::Backoff
205                && !transaction.record_backoff_or_retry(
206                    result.observed_token,
207                    &self.state,
208                    &self.contention_state,
209                    token,
210                )
211            {
212                result.lock_result = Result::Retry;
213            }
214
215            if result.lock_result != Result::Retry {
216                return Err(result.lock_result);
217            }
218
219            T::yield_cpu();
220        }
221    }
222
223    /// Attempts to acquire the lock with default options (`AllowFinalized::No`,
224    /// `RecordBackoff::Yes`). Returns `true` if the lock was acquired or `false` if acquiring the
225    /// lock failed. Asserts if a cycle is detected.
226    #[inline]
227    #[must_use]
228    pub fn try_acquire(&self, token: &ChainLockTransactionToken<T>) -> bool {
229        self.try_acquire_with_options(AllowFinalized::No, RecordBackoff::Yes, token)
230    }
231
232    /// Attempts to acquire the lock with the specified `allow_finalized` and `record_backoff`
233    /// options. Returns `true` if the lock was acquired or `false` if acquiring the lock failed.
234    /// Asserts if a cycle is detected.
235    #[inline]
236    #[must_use]
237    pub fn try_acquire_with_options(
238        &self,
239        allow_finalized: AllowFinalized,
240        record_backoff: RecordBackoff,
241        token: &ChainLockTransactionToken<T>,
242    ) -> bool {
243        let transaction = T::active_ref(token);
244        if allow_finalized == AllowFinalized::No {
245            transaction.assert_not_finalized();
246        }
247
248        let tx_token = transaction.token();
249        let result = self.attempt_acquire_common(tx_token);
250        if result.lock_result == Result::Ok {
251            transaction.on_acquire(token);
252            return true;
253        }
254
255        transaction.on_contention(token);
256
257        if result.lock_result == Result::Backoff && record_backoff == RecordBackoff::Yes {
258            let _ = transaction.record_backoff_or_retry(
259                result.observed_token,
260                &self.state,
261                &self.contention_state,
262                token,
263            );
264        }
265
266        debug_assert_ne!(result.lock_result, Result::Cycle);
267        false
268    }
269
270    /// Releases the lock.
271    #[inline]
272    pub fn release(&self, token: &ChainLockTransactionToken<T>) {
273        let transaction = T::active_ref(token);
274        debug_assert_eq!(transaction.token(), self.state.load(Ordering::Relaxed));
275
276        transaction.on_release(token);
277
278        // The environment-specific contention backoff strategy may require a careful order of
279        // operations for notifying backed-off contenders of the release and the actual lock
280        // release operation to ensure that BOTH of the following properties hold:
281        //
282        // 1. Races between backing off and releasing the lock do not result in missed
283        //    notifications.
284        // 2. The contention state of the lock is not accessed after the lock is released to avoid
285        //    use-after-free hazards.
286        //
287        // The following protocol ensures these properties hold:
288        //
289        // 1. The lock token is set to the maximum token value to temporarily prevent further
290        //    updates to the contention state by new contenders, since the maximum token value is
291        //    the lowest possible priority for a chain lock transaction.
292        // 2. The lock contention state is sampled to determine which backed-off contenders to
293        //    notify after the lock is released.
294        // 3. The lock is released, allowing contenders to attempt to acquire the lock.
295        // 4. Contenders that backed off prior to the lock release are notified.
296        //
297        // The environment-specific implementation of `release_and_notify` is responsible for
298        // ensuring that steps 2 through 4 are implemented correctly, using the provided closure to
299        // implement step 3.
300        //
301        // TODO(johngro): Look into relaxing the memory ordering being used here.  I
302        // _think_ that we can perform this store to the lock's state using relaxed
303        // semantics, but I'm not going to take that chance without having first built
304        // a model and run it through CDSChecker.
305        //
306        self.state.store(Self::MAX_TOKEN, Ordering::Release);
307        transaction.release_and_notify(
308            &self.contention_state,
309            || {
310                self.state.store(Self::UNLOCKED_TOKEN, Ordering::Release);
311            },
312            token,
313        );
314    }
315
316    /// Asserts in debug builds that the lock is held by the current transaction.
317    #[inline]
318    pub fn assert_held(&self, token: &ChainLockTransactionToken<T>) {
319        let transaction = T::active_ref(token);
320        let tx_token = transaction.token();
321        let lock_token = self.state.load(Ordering::Relaxed);
322        debug_assert_eq!(
323            lock_token,
324            tx_token,
325            "Failed to assert ChainLock is held (lock token {:#x}, our token {:#x}).",
326            lock_token.value(),
327            tx_token.value()
328        );
329    }
330
331    /// Asserts in debug builds that the lock is held by the current transaction and must be
332    /// released by the caller's scope.
333    #[inline]
334    pub fn assert_acquired(&self, token: &ChainLockTransactionToken<T>) {
335        self.assert_held(token);
336    }
337
338    /// Returns `true` if the lock is held by the current transaction.
339    #[inline]
340    pub fn is_held(&self, token: &ChainLockTransactionToken<T>) -> bool {
341        let transaction = T::active_ref(token);
342        let tx_token = transaction.token();
343        self.state.load(Ordering::Relaxed) == tx_token
344    }
345
346    /// Returns `true` if the lock is unlocked.
347    #[inline]
348    pub fn is_unlocked(&self) -> bool {
349        self.state.load(Ordering::Relaxed) == Self::UNLOCKED_TOKEN
350    }
351
352    /// Syncs the held token value of this lock with the current transaction token. Used for saving
353    /// and restoring state in special circumstances that use reserved tokens.
354    #[inline]
355    pub fn sync_token(&self, token: &ChainLockTransactionToken<T>) {
356        let transaction = T::active_ref(token);
357        let tx_token = transaction.token();
358        self.state.store(tx_token, Ordering::Release);
359    }
360
361    /// Returns the currently stored [`Token`] of this lock with `Relaxed` memory ordering for
362    /// testing.
363    #[inline]
364    pub fn state_for_testing(&self) -> Token {
365        self.state.load(Ordering::Relaxed)
366    }
367
368    #[inline]
369    fn attempt_acquire_common(&self, token: Token) -> AttemptResult {
370        match self.state.compare_exchange(
371            Self::UNLOCKED_TOKEN,
372            token,
373            Ordering::Acquire,
374            Ordering::Acquire,
375        ) {
376            Ok(expected) => AttemptResult { lock_result: Result::Ok, observed_token: expected },
377            Err(expected) => {
378                let lock_result = if expected > token {
379                    Result::Retry
380                } else if expected < token {
381                    Result::Backoff
382                } else {
383                    Result::Cycle
384                };
385                AttemptResult { lock_result, observed_token: expected }
386            }
387        }
388    }
389}
390
391impl<T: Transaction> Default for ChainLock<T> {
392    #[inline]
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398impl<T: Transaction> Drop for ChainLock<T> {
399    #[inline]
400    fn drop(&mut self) {
401        let lock_token = self.state.load(Ordering::Relaxed);
402        debug_assert_eq!(
403            lock_token,
404            Self::UNLOCKED_TOKEN,
405            "Destroying locked ChainLock: token={}",
406            lock_token.value()
407        );
408    }
409}
410
411/// Utility to simplify expressions where two chain locks need to be acquired with backoff.
412/// Returns `true` if both locks were acquired or `false` if the current transaction needs to
413/// back off.
414#[inline]
415#[must_use]
416pub fn acquire_both_or_backoff<T: Transaction>(
417    first: &ChainLock<T>,
418    second: &ChainLock<T>,
419    token: &ChainLockTransactionToken<T>,
420) -> bool {
421    if first.acquire_or_backoff(token) {
422        if second.acquire_or_backoff(token) {
423            return true;
424        }
425        first.release(token);
426    }
427    false
428}
429
430// Verify layout equivalence with C++ `concurrent::ChainLock<Transaction>` for both 32-bit
431// (`AtomicU32`, i.e., `ktl::atomic<cpu_mask_t>` in the kernel) and 64-bit (`AtomicU64`) contention
432// states.
433struct DummyTxU32;
434impl Transaction for DummyTxU32 {
435    type ContentionState = core::sync::atomic::AtomicU32;
436    fn active() -> Option<NonNull<Self>> {
437        None
438    }
439    fn active_ref(_: &ChainLockTransactionToken<Self>) -> &Self {
440        unimplemented!()
441    }
442    fn yield_cpu() {}
443    fn token(&self) -> Token {
444        Token::UNLOCKED
445    }
446    fn assert_not_finalized(&self) {}
447    fn assert_num_locks_held(&self, _: u32) {}
448    fn on_acquire(&self, _: &ChainLockTransactionToken<Self>) {}
449    fn on_release(&self, _: &ChainLockTransactionToken<Self>) {}
450    fn on_contention(&self, _: &ChainLockTransactionToken<Self>) {}
451    fn record_backoff_or_retry(
452        &self,
453        _: Token,
454        _: &AtomicToken,
455        _: &Self::ContentionState,
456        _: &ChainLockTransactionToken<Self>,
457    ) -> bool {
458        true
459    }
460    fn release_and_notify<F: FnOnce()>(
461        &self,
462        _: &Self::ContentionState,
463        do_release: F,
464        _: &ChainLockTransactionToken<Self>,
465    ) {
466        do_release();
467    }
468}
469
470struct DummyTxU64;
471impl Transaction for DummyTxU64 {
472    type ContentionState = core::sync::atomic::AtomicU64;
473    fn active() -> Option<NonNull<Self>> {
474        None
475    }
476    fn active_ref(_: &ChainLockTransactionToken<Self>) -> &Self {
477        unimplemented!()
478    }
479    fn yield_cpu() {}
480    fn token(&self) -> Token {
481        Token::UNLOCKED
482    }
483    fn assert_not_finalized(&self) {}
484    fn assert_num_locks_held(&self, _: u32) {}
485    fn on_acquire(&self, _: &ChainLockTransactionToken<Self>) {}
486    fn on_release(&self, _: &ChainLockTransactionToken<Self>) {}
487    fn on_contention(&self, _: &ChainLockTransactionToken<Self>) {}
488    fn record_backoff_or_retry(
489        &self,
490        _: Token,
491        _: &AtomicToken,
492        _: &Self::ContentionState,
493        _: &ChainLockTransactionToken<Self>,
494    ) -> bool {
495        true
496    }
497    fn release_and_notify<F: FnOnce()>(
498        &self,
499        _: &Self::ContentionState,
500        do_release: F,
501        _: &ChainLockTransactionToken<Self>,
502    ) {
503        do_release();
504    }
505}
506
507zr::static_assert!(size_of::<ChainLock<DummyTxU32>>() == 16);
508zr::static_assert!(align_of::<ChainLock<DummyTxU32>>() == 8);
509zr::static_assert!(offset_of!(ChainLock<DummyTxU32>, state) == 0);
510zr::static_assert!(offset_of!(ChainLock<DummyTxU32>, contention_state) == 8);
511
512zr::static_assert!(size_of::<ChainLock<DummyTxU64>>() == 16);
513zr::static_assert!(align_of::<ChainLock<DummyTxU64>>() == 8);
514zr::static_assert!(offset_of!(ChainLock<DummyTxU64>, state) == 0);
515zr::static_assert!(offset_of!(ChainLock<DummyTxU64>, contention_state) == 8);
516
517#[cfg(test)]
518mod tests {
519    use core::cell::Cell;
520    use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
521    use std::boxed::Box;
522    use std::sync::{Arc, Condvar, Mutex};
523    use std::thread;
524    use std::time::Duration;
525
526    use super::*;
527    use crate::chainlock_transaction::{
528        Action, ChainLockTransaction as BaseChainLockTransaction, DONE, Option as TxOption,
529        SelectAccounting, SelectTracing, StateOption, StateSaver, TransactionHooks,
530        TransactionResult,
531    };
532
533    const DEBUG_ENABLED: bool = true;
534    const TRACING_ENABLED: bool = true;
535
536    #[repr(C)]
537    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
538    struct CallsiteInfo {
539        tag: &'static str,
540    }
541
542    const fn clt_tag(tag: &'static str) -> CallsiteInfo {
543        CallsiteInfo { tag }
544    }
545
546    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
547    enum Options {
548        #[default]
549        None,
550        A,
551        B,
552    }
553
554    const OPTION_NONE: TxOption<Options> = TxOption::new(Options::None);
555    const OPTION_A: TxOption<Options> = TxOption::new(Options::A);
556    const OPTION_B: TxOption<Options> = TxOption::new(Options::B);
557
558    struct TestHooks;
559
560    type ChainLockTransaction = BaseChainLockTransaction<TestHooks>;
561    type ChainLockTransactionToken =
562        crate::chainlock_transaction::ChainLockTransactionToken<ChainLockTransaction>;
563    type ChainLock = super::ChainLock<ChainLockTransaction>;
564    type ChainLockGuard<'a> = crate::chainlock_guard::ChainLockGuard<'a, ChainLockTransaction>;
565
566    static RESERVED_TOKEN_ALLOCATOR: AtomicUsize = AtomicUsize::new(0);
567
568    std::thread_local! {
569        static LAST_CONTENTION_START_TICKS: Cell<i64> = const { Cell::new(0) };
570        static LAST_CALLSITE_INFO: Cell<CallsiteInfo> = const {
571            Cell::new(CallsiteInfo { tag: "" })
572        };
573        static ACTIVE_OPTION: Cell<Options> = const { Cell::new(Options::None) };
574        static ACTIVE_TRANSACTION: Cell<Option<NonNull<ChainLockTransaction>>> = const {
575            Cell::new(None)
576        };
577        static SIMULATED_TICKS: Cell<i64> = const { Cell::new(100) };
578    }
579
580    // SAFETY: `ACTIVE_TRANSACTION` is stored in thread-local storage (`std::thread_local!`), and
581    // `active()` returns the exact pointer most recently stored by `set_active()` on the current
582    // thread.
583    unsafe impl TransactionHooks for TestHooks {
584        type ContentionState = AtomicU64;
585        type CallsiteInfo = CallsiteInfo;
586        type Accounting = SelectAccounting<DEBUG_ENABLED>;
587        type Tracing = SelectTracing<TRACING_ENABLED, Self::CallsiteInfo>;
588        type ExtraState = ();
589
590        fn active() -> Option<NonNull<ChainLockTransaction>> {
591            ACTIVE_TRANSACTION.with(Cell::get)
592        }
593
594        unsafe fn set_active(transaction: Option<NonNull<ChainLockTransaction>>) {
595            ACTIVE_TRANSACTION.with(|cell| cell.set(transaction));
596        }
597
598        fn yield_cpu() {}
599
600        fn allocate_reserved_token() -> Token {
601            let reserved_index = RESERVED_TOKEN_ALLOCATOR.fetch_add(1, Ordering::Relaxed);
602            assert!((reserved_index as u64) < ChainLockTransaction::RESERVED_TOKEN_VALUES);
603            ChainLockTransaction::reserved_token(reserved_index)
604        }
605
606        fn get_current_ticks() -> i64 {
607            SIMULATED_TICKS.with(|cell| {
608                let ticks = cell.get();
609                cell.set(ticks + 1);
610                ticks
611            })
612        }
613
614        fn on_finalized(contention_start_ticks: i64, callsite_info: Self::CallsiteInfo) {
615            LAST_CONTENTION_START_TICKS.with(|cell| cell.set(contention_start_ticks));
616            LAST_CALLSITE_INFO.with(|cell| cell.set(callsite_info));
617        }
618
619        fn on_backoff(_transaction: &ChainLockTransaction) {}
620
621        fn record_backoff_or_retry(
622            _transaction: &ChainLockTransaction,
623            _observed_token: Token,
624            _lock_state: &AtomicToken,
625            _contention_state: &Self::ContentionState,
626        ) -> bool {
627            true
628        }
629
630        fn release_and_notify<F: FnOnce()>(
631            _transaction: &ChainLockTransaction,
632            _contention_state: &Self::ContentionState,
633            do_release: F,
634        ) {
635            do_release();
636        }
637    }
638
639    impl TestHooks {
640        fn active_option() -> Options {
641            ACTIVE_OPTION.with(Cell::get)
642        }
643
644        fn last_contention_start_ticks() -> i64 {
645            LAST_CONTENTION_START_TICKS.with(Cell::get)
646        }
647
648        fn last_callsite_info() -> CallsiteInfo {
649            LAST_CALLSITE_INFO.with(Cell::get)
650        }
651    }
652
653    struct TestStateSaver {
654        reset_on_drop: bool,
655    }
656
657    impl Drop for TestStateSaver {
658        fn drop(&mut self) {
659            if self.reset_on_drop {
660                ACTIVE_OPTION.with(|cell| cell.set(Options::None));
661            }
662        }
663    }
664
665    impl StateSaver<ChainLockTransaction> for TestStateSaver {
666        fn relax(&mut self, _transaction: &ChainLockTransaction) {
667            thread::yield_now();
668        }
669    }
670
671    impl StateOption<ChainLockTransaction> for Options {
672        type Saver = TestStateSaver;
673
674        fn save_state(&self) -> Self::Saver {
675            match self {
676                Options::None => TestStateSaver { reset_on_drop: false },
677                Options::A => {
678                    ACTIVE_OPTION.with(|cell| cell.set(Options::A));
679                    TestStateSaver { reset_on_drop: true }
680                }
681                Options::B => {
682                    ACTIVE_OPTION.with(|cell| cell.set(Options::B));
683                    TestStateSaver { reset_on_drop: true }
684                }
685            }
686        }
687    }
688
689    struct HelperShared<S> {
690        lock: Mutex<S>,
691        helper_condition: Condvar,
692        test_condition: Condvar,
693    }
694
695    struct Helper<S> {
696        shared: Arc<HelperShared<S>>,
697    }
698
699    impl<S: Copy + PartialEq> Helper<S> {
700        fn set_state_and_notify_test(&self, state: S) {
701            {
702                let mut guard = self.shared.lock.lock().unwrap();
703                *guard = state;
704            }
705            self.shared.test_condition.notify_one();
706        }
707
708        fn wait_for_test_state(&self, state: S) {
709            let mut guard = self.shared.lock.lock().unwrap();
710            while *guard != state {
711                guard = self.shared.helper_condition.wait(guard).unwrap();
712            }
713        }
714    }
715
716    struct HelperThread<S> {
717        shared: Arc<HelperShared<S>>,
718        helper_thread: Option<thread::JoinHandle<()>>,
719    }
720
721    impl<S: Copy + PartialEq + Send + 'static> HelperThread<S> {
722        fn new<F>(initial: S, callable: F) -> Self
723        where
724            F: FnOnce(Helper<S>) + Send + 'static,
725        {
726            let shared = Arc::new(HelperShared {
727                lock: Mutex::new(initial),
728                helper_condition: Condvar::new(),
729                test_condition: Condvar::new(),
730            });
731            let helper = Helper { shared: Arc::clone(&shared) };
732            let handle = thread::spawn(move || callable(helper));
733            Self { shared, helper_thread: Some(handle) }
734        }
735
736        fn set_state_and_notify_helper(&self, state: S) {
737            {
738                let mut guard = self.shared.lock.lock().unwrap();
739                *guard = state;
740            }
741            self.shared.helper_condition.notify_one();
742        }
743
744        fn wait_for_helper_state(&self, state: S) {
745            let mut guard = self.shared.lock.lock().unwrap();
746            while *guard != state {
747                guard = self.shared.test_condition.wait(guard).unwrap();
748            }
749        }
750    }
751
752    impl<S> Drop for HelperThread<S> {
753        fn drop(&mut self) {
754            if let Some(handle) = self.helper_thread.take() {
755                handle.join().unwrap();
756            }
757        }
758    }
759
760    #[test]
761    fn transaction_options() {
762        assert_eq!(Options::None, TestHooks::active_option());
763
764        ChainLockTransaction::until_done(
765            OPTION_NONE,
766            clt_tag("TransactionOptions"),
767            |_token| -> TransactionResult<()> {
768                assert_eq!(Options::None, TestHooks::active_option());
769                DONE.into()
770            },
771        );
772
773        assert_eq!(Options::None, TestHooks::active_option());
774
775        ChainLockTransaction::until_done(
776            OPTION_A,
777            clt_tag("TransactionOptions"),
778            |_token| -> TransactionResult<()> {
779                assert_eq!(Options::A, TestHooks::active_option());
780                DONE.into()
781            },
782        );
783
784        assert_eq!(Options::None, TestHooks::active_option());
785
786        ChainLockTransaction::until_done(
787            OPTION_B,
788            clt_tag("TransactionOptions"),
789            |_token| -> TransactionResult<()> {
790                assert_eq!(Options::B, TestHooks::active_option());
791                DONE.into()
792            },
793        );
794
795        assert_eq!(Options::None, TestHooks::active_option());
796    }
797
798    #[test]
799    fn transaction_return_value() {
800        let mut value = Some(Box::new(42));
801        let value_pointer: *const i32 = &**value.as_ref().unwrap();
802
803        let return_value: Box<i32> = ChainLockTransaction::until_done(
804            OPTION_NONE,
805            clt_tag("TransactionOptions"),
806            |_token| -> TransactionResult<Box<i32>> {
807                TransactionResult::Value(value.take().unwrap())
808            },
809        );
810
811        assert_eq!(&*return_value as *const i32, value_pointer);
812        assert!(value.is_none());
813    }
814
815    #[test]
816    fn uncontested_acquire() {
817        let lock = ChainLock::new();
818
819        ChainLockTransaction::until_done(
820            OPTION_NONE,
821            clt_tag("UncontestedAcquire"),
822            |token| -> TransactionResult<()> {
823                assert!(!lock.is_held(token));
824                assert!(lock.is_unlocked());
825
826                lock.acquire_first_in_chain(token);
827
828                assert!(lock.is_held(token));
829                assert!(!lock.is_unlocked());
830                assert_eq!(
831                    lock.state_for_testing(),
832                    ChainLockTransaction::active_ref(token).token()
833                );
834
835                lock.release(token);
836
837                assert!(!lock.is_held(token));
838                assert!(lock.is_unlocked());
839                DONE.into()
840            },
841        );
842    }
843
844    #[test]
845    fn cyclic_acquire() {
846        let lock = ChainLock::new();
847        let get_lock = || -> &ChainLock { &lock };
848
849        ChainLockTransaction::until_done(
850            OPTION_NONE,
851            clt_tag("CyclicAcquire"),
852            |token| -> TransactionResult<()> {
853                lock.acquire_first_in_chain(token);
854
855                assert_eq!(Err(Result::Cycle), get_lock().acquire_or_result(token));
856
857                lock.release(token);
858                DONE.into()
859            },
860        );
861    }
862
863    #[test]
864    fn contention_priority() {
865        let locks = Arc::new((ChainLock::new(), ChainLock::new()));
866
867        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
868        enum State {
869            Initial,
870            HelperTransactionStarted,
871            TestTransactionStarted,
872        }
873
874        let helper_locks = Arc::clone(&locks);
875        let helper_thread = HelperThread::new(State::Initial, move |helper| {
876            let (lock_a, lock_b) = &*helper_locks;
877            ChainLockTransaction::until_done(
878                OPTION_NONE,
879                clt_tag("ContentionPriority helper"),
880                |token| -> TransactionResult<()> {
881                    let _guard = ChainLockGuard::new(lock_a, token);
882
883                    helper.set_state_and_notify_test(State::HelperTransactionStarted);
884                    helper.wait_for_test_state(State::TestTransactionStarted);
885
886                    // Acquiring lock_b should spin until the lower priority transaction backs off.
887                    let acquired = lock_b.acquire_or_backoff(token);
888                    if acquired {
889                        lock_b.release(token);
890                    } else {
891                        assert!(acquired);
892                    }
893
894                    DONE.into()
895                },
896            );
897        });
898
899        // Wait for the helper thread to start a transaction so it has higher priority.
900        helper_thread.wait_for_helper_state(State::HelperTransactionStarted);
901
902        let (lock_a, lock_b) = &*locks;
903        ChainLockTransaction::until_done(
904            OPTION_NONE,
905            clt_tag("ContentionPriority"),
906            |token| -> TransactionResult<()> {
907                let _guard = ChainLockGuard::new(lock_b, token);
908
909                helper_thread.set_state_and_notify_helper(State::TestTransactionStarted);
910
911                // The helper transaction should be higher priority than this transaction.
912                assert!(lock_b.state_for_testing() > lock_a.state_for_testing());
913
914                // Let the helper thread spin for a while waiting for lock_b to be released.
915                thread::sleep(Duration::from_millis(50));
916
917                if !lock_a.acquire_or_backoff(token) {
918                    return Action::Backoff.into();
919                }
920                lock_a.release(token);
921
922                DONE.into()
923            },
924        );
925
926        // Verify contention tracing recorded the contention on the test thread.
927        assert!(TestHooks::last_contention_start_ticks() > 0);
928        assert_eq!(TestHooks::last_callsite_info(), clt_tag("ContentionPriority"));
929    }
930
931    struct TypeA {
932        lock: ChainLock,
933        value: i32,
934    }
935
936    impl TypeA {
937        fn new(value: i32) -> Self {
938            Self { lock: ChainLock::new(), value }
939        }
940
941        fn get_lock(&self) -> &ChainLock {
942            &self.lock
943        }
944
945        fn compare(&self, other: &TypeB, token: &ChainLockTransactionToken) -> bool {
946            self.get_lock().assert_held(token);
947            other.get_lock().assert_held(token);
948            self.value == other.value
949        }
950    }
951
952    struct TypeB {
953        lock: ChainLock,
954        value: i32,
955    }
956
957    impl TypeB {
958        fn new(value: i32) -> Self {
959            Self { lock: ChainLock::new(), value }
960        }
961
962        fn get_lock(&self) -> &ChainLock {
963            &self.lock
964        }
965
966        fn compare(&self, other: &TypeA, token: &ChainLockTransactionToken) -> bool {
967            self.get_lock().assert_held(token);
968            other.get_lock().assert_held(token);
969            self.value == other.value
970        }
971    }
972
973    #[test]
974    fn cross_referencing_types() {
975        let value_a = TypeA::new(0);
976        let value_b = TypeB::new(0);
977
978        let do_transaction = |token: &ChainLockTransactionToken| -> TransactionResult<()> {
979            let _guard_a = ChainLockGuard::new(value_a.get_lock(), token);
980            let mut guard_b = ChainLockGuard::defer(value_b.get_lock(), token);
981            if !guard_b.acquire_or_backoff() {
982                return Action::Backoff.into();
983            }
984            assert!(value_a.compare(&value_b, token));
985            assert!(value_b.compare(&value_a, token));
986            DONE.into()
987        };
988
989        ChainLockTransaction::until_done(
990            OPTION_NONE,
991            clt_tag("CrossReferencingTypes"),
992            do_transaction,
993        );
994    }
995
996    #[test]
997    fn guard_operations_and_acquire_both() {
998        let lock_a = ChainLock::new();
999        let lock_b = ChainLock::new();
1000
1001        ChainLockTransaction::until_done(OPTION_NONE, clt_tag("GuardOps"), |token| {
1002            assert!(acquire_both_or_backoff(&lock_a, &lock_b, token));
1003            let guard_a = ChainLockGuard::adopt(&lock_a, token);
1004            let mut guard_b = ChainLockGuard::adopt(&lock_b, token);
1005            assert_eq!(ChainLockTransaction::active_ref(token).locks_held(), 2);
1006
1007            // Transfer guard_a to a new guard via `take` and `from_taken`.
1008            let mut transferred_a = ChainLockGuard::from_taken(guard_a.take(), &lock_a);
1009            assert!(transferred_a.is_held());
1010
1011            // Test `unguard` + manual release on lock_a.
1012            transferred_a.unguard();
1013            assert!(!transferred_a.is_held());
1014            lock_a.release(token);
1015            assert_eq!(ChainLockTransaction::active_ref(token).locks_held(), 1);
1016
1017            // Explicitly release guard_b and re-acquire with `try_acquire_with`.
1018            guard_b.release();
1019            assert_eq!(ChainLockTransaction::active_ref(token).locks_held(), 0);
1020            assert!(guard_b.try_acquire_with(|l| l.try_acquire(token)));
1021            assert_eq!(ChainLockTransaction::active_ref(token).locks_held(), 1);
1022
1023            DONE
1024        });
1025    }
1026
1027    #[test]
1028    fn save_and_restore_state_and_restart() {
1029        let lock = ChainLock::new();
1030        let mut restarted = false;
1031
1032        ChainLockTransaction::until_done(
1033            OPTION_NONE,
1034            clt_tag("SaveRestoreAndRestart"),
1035            |token| -> TransactionResult<()> {
1036                if !restarted {
1037                    lock.acquire_first_in_chain(token);
1038                    ChainLockTransaction::finalize(token);
1039                    assert!(ChainLockTransaction::active_ref(token).is_finalized());
1040
1041                    let original_token = ChainLockTransaction::active_ref(token).token();
1042                    let saved = ChainLockTransaction::save_state_and_use_reserved_token(token);
1043                    assert_eq!(saved.token(), original_token);
1044                    assert!(ChainLockTransaction::active_ref(token).token().is_reserved());
1045
1046                    // Sync lock to the reserved token, then restore the original token and sync
1047                    // back.
1048                    lock.sync_token(token);
1049                    assert!(lock.state_for_testing().is_reserved());
1050
1051                    // SAFETY: `saved` was created from the currently active transaction on this
1052                    // stack.
1053                    unsafe {
1054                        ChainLockTransaction::restore_state(saved);
1055                    }
1056                    assert_eq!(ChainLockTransaction::active_ref(token).token(), original_token);
1057                    lock.sync_token(token);
1058                    assert_eq!(lock.state_for_testing(), original_token);
1059
1060                    lock.release(token);
1061                    restarted = true;
1062                    return Action::Restart.into();
1063                }
1064
1065                assert!(!ChainLockTransaction::active_ref(token).is_finalized());
1066                DONE.into()
1067            },
1068        );
1069        assert!(restarted);
1070    }
1071}