@@ -596,13 +596,15 @@ pub enum CrossProcessLockError {
596596mod 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