Skip to content

Commit 8ee41f4

Browse files
committed
test(common): Test dirtiness of the cross-process lock.
1 parent f288e5f commit 8ee41f4

File tree

1 file changed

+117
-12
lines changed

1 file changed

+117
-12
lines changed

crates/matrix-sdk-common/src/cross_process_lock.rs

Lines changed: 117 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -596,13 +596,15 @@ pub enum CrossProcessLockError {
596596
mod tests {
597597
use std::{
598598
collections::HashMap,
599+
ops::Not,
599600
sync::{Arc, RwLock, atomic},
600601
};
601602

602603
use assert_matches::assert_matches;
603604
use matrix_sdk_test_macros::async_test;
604605
use tokio::{
605606
spawn,
607+
task::yield_now,
606608
time::{Duration, sleep},
607609
};
608610

@@ -665,17 +667,23 @@ mod tests {
665667

666668
// The lock plain works when used with a single holder.
667669
let guard = lock.try_lock_once().await?.expect("lock must be obtained successfully");
670+
assert_matches!(guard, CrossProcessLockState::Clean(_));
671+
assert!(lock.is_dirty().not());
668672
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 1);
669673

670674
// Releasing works.
671675
release_lock(guard).await;
676+
assert!(lock.is_dirty().not());
672677
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 0);
673678

674679
// Spin locking on the same lock always works, assuming no concurrent access.
675680
let guard = lock.spin_lock(None).await?.expect("spin lock must be obtained successfully");
681+
assert!(lock.is_dirty().not());
682+
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 1);
676683

677684
// Releasing still works.
678685
release_lock(guard).await;
686+
assert!(lock.is_dirty().not());
679687
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 0);
680688

681689
Ok(())
@@ -686,19 +694,23 @@ mod tests {
686694
let store = TestStore::default();
687695
let lock = CrossProcessLock::new(store.clone(), "key".to_owned(), "first".to_owned());
688696

689-
// When a lock is obtained...
690-
let _guard = lock.try_lock_once().await?.expect("lock must be obtained successfully");
697+
// When a lock is obtained…
698+
let guard = lock.try_lock_once().await?.expect("lock must be obtained successfully");
699+
assert_matches!(guard, CrossProcessLockState::Clean(_));
700+
assert!(lock.is_dirty().not());
691701
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 1);
692702

693-
// But then forgotten... (note: no need to release the guard)
703+
// But then forgotten (note: no need to release the guard)
694704
drop(lock);
695705

696-
// And when rematerializing the lock with the same key/value...
706+
// And when rematerializing the lock with the same key/value
697707
let lock = CrossProcessLock::new(store.clone(), "key".to_owned(), "first".to_owned());
698708

699709
// We still got it.
700-
let _guard =
710+
let guard =
701711
lock.try_lock_once().await?.expect("lock (again) must be obtained successfully");
712+
assert_matches!(guard, CrossProcessLockState::Clean(_));
713+
assert!(lock.is_dirty().not());
702714
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 1);
703715

704716
Ok(())
@@ -711,7 +723,10 @@ mod tests {
711723

712724
// Taking the lock twice…
713725
let guard1 = lock.try_lock_once().await?.expect("lock must be obtained successfully");
726+
assert_matches!(guard1, CrossProcessLockState::Clean(_));
714727
let guard2 = lock.try_lock_once().await?.expect("lock must be obtained successfully");
728+
assert_matches!(guard2, CrossProcessLockState::Clean(_));
729+
assert!(lock.is_dirty().not());
715730

716731
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 2);
717732

@@ -722,6 +737,8 @@ mod tests {
722737
release_lock(guard2).await;
723738
assert_eq!(lock.num_holders.load(atomic::Ordering::SeqCst), 0);
724739

740+
assert!(lock.is_dirty().not());
741+
725742
Ok(())
726743
}
727744

@@ -731,34 +748,122 @@ mod tests {
731748
let lock1 = CrossProcessLock::new(store.clone(), "key".to_owned(), "first".to_owned());
732749
let lock2 = CrossProcessLock::new(store, "key".to_owned(), "second".to_owned());
733750

734-
// When the first process takes the lock...
751+
// `lock1` acquires the lock.
735752
let guard1 = lock1.try_lock_once().await?.expect("lock must be obtained successfully");
753+
assert_matches!(guard1, CrossProcessLockState::Clean(_));
754+
assert!(lock1.is_dirty().not());
736755

737-
// The second can't take it immediately.
738-
let _err2 = lock2.try_lock_once().await?.expect_err("lock must NOT be obtained");
756+
// `lock2` cannot acquire the lock.
757+
let err = lock2.try_lock_once().await?.expect_err("lock must NOT be obtained");
758+
assert_matches!(err, CrossProcessLockUnobtained::Busy);
739759

760+
// `lock2` is waiting in a task.
740761
let lock2_clone = lock2.clone();
741-
let handle = spawn(async move { lock2_clone.spin_lock(Some(1000)).await });
762+
let task = spawn(async move { lock2_clone.spin_lock(Some(500)).await });
742763

743764
sleep(Duration::from_millis(100)).await;
744765

745766
drop(guard1);
746767

747-
// lock2 in the background manages to get the lock at some point.
748-
let _guard2 = handle
768+
// Once `lock1` is released, `lock2` managed to obtain it.
769+
let guard2 = task
749770
.await
750771
.expect("join handle is properly awaited")
751772
.expect("lock is successfully attempted")
752773
.expect("lock must be obtained successfully");
774+
assert_matches!(guard2, CrossProcessLockState::Clean(_));
775+
776+
// `lock1` and `lock2` are both clean!
777+
assert!(lock1.is_dirty().not());
778+
assert!(lock2.is_dirty().not());
753779

754-
// Now if lock1 tries to get the lock with a small timeout, it will fail.
780+
// Now if `lock1` tries to obtain the lock with a small timeout, it will fail.
755781
assert_matches!(
756782
lock1.spin_lock(Some(200)).await,
757783
Ok(Err(CrossProcessLockUnobtained::TimedOut))
758784
);
759785

760786
Ok(())
761787
}
788+
789+
#[async_test]
790+
async fn test_multiple_processes_up_to_dirty() -> TestResult {
791+
let store = TestStore::default();
792+
let lock1 = CrossProcessLock::new(store.clone(), "key".to_owned(), "first".to_owned());
793+
let lock2 = CrossProcessLock::new(store, "key".to_owned(), "second".to_owned());
794+
795+
// Obtain `lock1` once.
796+
{
797+
let guard = lock1.try_lock_once().await?.expect("lock must be obtained successfully");
798+
assert_matches!(guard, CrossProcessLockState::Clean(_));
799+
assert!(lock1.is_dirty().not());
800+
drop(guard);
801+
802+
yield_now().await;
803+
}
804+
805+
// Obtain `lock2` once.
806+
{
807+
let guard = lock2.try_lock_once().await?.expect("lock must be obtained successfully");
808+
assert_matches!(guard, CrossProcessLockState::Clean(_));
809+
assert!(lock1.is_dirty().not());
810+
drop(guard);
811+
812+
yield_now().await;
813+
}
814+
815+
for _ in 0..3 {
816+
// Obtain `lock1` once more. Now it's dirty because `lock2` has acquired the
817+
// lock meanwhile.
818+
{
819+
let guard =
820+
lock1.try_lock_once().await?.expect("lock must be obtained successfully");
821+
assert_matches!(guard, CrossProcessLockState::Dirty(_));
822+
assert!(lock1.is_dirty());
823+
824+
drop(guard);
825+
yield_now().await;
826+
}
827+
828+
// Obtain `lock1` once more! It still dirty because it has not been marked as
829+
// non-dirty.
830+
{
831+
let guard =
832+
lock1.try_lock_once().await?.expect("lock must be obtained successfully");
833+
assert_matches!(guard, CrossProcessLockState::Dirty(_));
834+
assert!(lock1.is_dirty());
835+
lock1.clear_dirty();
836+
837+
drop(guard);
838+
yield_now().await;
839+
}
840+
841+
// Obtain `lock1` once more. Now it's clear!
842+
{
843+
let guard =
844+
lock1.try_lock_once().await?.expect("lock must be obtained successfully");
845+
assert_matches!(guard, CrossProcessLockState::Clean(_));
846+
assert!(lock1.is_dirty().not());
847+
848+
drop(guard);
849+
yield_now().await;
850+
}
851+
852+
// Same dance with `lock2`!
853+
{
854+
let guard =
855+
lock2.try_lock_once().await?.expect("lock must be obtained successfully");
856+
assert_matches!(guard, CrossProcessLockState::Dirty(_));
857+
assert!(lock2.is_dirty());
858+
lock2.clear_dirty();
859+
860+
drop(guard);
861+
yield_now().await;
862+
}
863+
}
864+
865+
Ok(())
866+
}
762867
}
763868

764869
/// Some code that is shared by almost all `MemoryStore` implementations out

0 commit comments

Comments
 (0)