1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum Result {
48 Ok,
50
51 Retry,
54
55 Backoff,
58
59 Cycle,
63}
64
65#[repr(u8)]
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum AllowFinalized {
70 No = 0,
72 Yes = 1,
74}
75
76#[repr(u8)]
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum RecordBackoff {
81 No = 0,
83 Yes = 1,
85}
86
87#[derive(Clone, Copy)]
88struct AttemptResult {
89 lock_result: Result,
90 observed_token: Token,
91}
92
93#[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 #[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 #[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 #[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 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 #[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 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 #[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 #[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 #[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 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 #[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 #[inline]
334 pub fn assert_acquired(&self, token: &ChainLockTransactionToken<T>) {
335 self.assert_held(token);
336 }
337
338 #[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 #[inline]
348 pub fn is_unlocked(&self) -> bool {
349 self.state.load(Ordering::Relaxed) == Self::UNLOCKED_TOKEN
350 }
351
352 #[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 #[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#[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
430struct 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 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 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 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 assert!(lock_b.state_for_testing() > lock_a.state_for_testing());
913
914 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 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 let mut transferred_a = ChainLockGuard::from_taken(guard_a.take(), &lock_a);
1009 assert!(transferred_a.is_held());
1010
1011 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 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 lock.sync_token(token);
1049 assert!(lock.state_for_testing().is_reserved());
1050
1051 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}