Skip to content

Commit

Permalink
WIP: find RWVar race condition using IOSimPOR
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisdral committed Oct 14, 2024
1 parent 23939c1 commit b9e1b11
Showing 1 changed file with 40 additions and 23 deletions.
63 changes: 40 additions & 23 deletions test-control/Test/Control/Concurrent/Class/MonadSTM/RWVar.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ module Test.Control.Concurrent.Class.MonadSTM.RWVar (tests) where
import qualified Control.Concurrent.Class.MonadSTM.RWVar as RW
import Control.Monad.Class.MonadAsync
import Control.Monad.Class.MonadSay (MonadSay (say))
import Control.Monad.Class.MonadTimer
import Control.Monad.Class.MonadTest (MonadTest (exploreRaces))
import Control.Monad.Class.MonadThrow
import Control.Monad.IOSim
import Test.QuickCheck
import Test.Tasty
import Test.Tasty.QuickCheck

tests :: TestTree
tests = testGroup "Test.Control.Concurrent.Class.MonadSTM.RWVar" [
testProperty "prop_noRace" prop_noRace
testProperty "prop_noRace" $ \a1 a2 a3 ->
prop_noRace [a1, a2, a3]
]

data Action a = Read a | Incr a
Expand All @@ -27,34 +29,49 @@ instance Arbitrary (Action (Small Word, Small Word)) where
-- lead to races. We observe this by looking at the trace and seeing that the
-- value inside the 'RWVar' increases monotonically.
prop_noRace :: [Action (Small Word, Small Word)] -> Property
prop_noRace as = exploreSimTrace id prop (\_ st -> sayMonotonic 0 st)
prop_noRace [a1, a2, a3] = exploreSimTrace f prop $ \_ tr ->
case traceResult False tr of
Left e@(FailureDeadlock _) -> counterexample (show e) $ property False
_ -> property True
where
f = id
-- f opts = opts {
-- explorationScheduleBound = 300
-- , explorationBranching = 5
-- }

prop :: IOSim s ()
prop = do
exploreRaces
var <- RW.new (0 :: Int)
forConcurrently_ as $ \case
Read (Small n, Small m) -> do
threadDelay (fromIntegral n)
RW.withReadAccess var $ \x -> do
threadDelay (fromIntegral m)
say (show (Read x))
Incr (Small n, Small m) -> do
threadDelay (fromIntegral n)
RW.withWriteAccess_ var $ \x -> do
threadDelay (fromIntegral m)
let x' = x + 1
say (show (Incr x'))
pure x'
let g = \case
Read (Small _n, Small _m) ->
RW.withReadAccess var $ \x -> do
say (show (Read x))
Incr (Small _n, Small _m) ->
RW.withWriteAccess_ var $ \x -> do
let x' = x + 1
say (show (Incr x'))
pure x'
t1 <- async (g a1)
t2 <- async (g a2)
t3 <- async (g a3)
async (cancel t1) >>= wait
(_ :: Either SomeException ()) <- try (wait t1)
(_ :: Either SomeException ()) <- try (wait t2)
(_ :: Either SomeException ()) <- try (wait t3)
pure ()

sayMonotonic :: Int -> SimTrace () -> Property
sayMonotonic _ (Nil MainReturn{}) = property True
sayMonotonic prev (Cons se st') = (case se of
_sayMonotonic :: Int -> SimTrace () -> Property
_sayMonotonic _ (Nil MainReturn{}) = property True
_sayMonotonic prev (Cons se st') = (case se of
SimPOREvent{seType} -> case seType of
EventSay s -> case read s of
Read x -> counterexample "Unexpected Read result"
$ prev === x .&&. sayMonotonic x st'
$ prev === x .&&. _sayMonotonic x st'
Incr x -> counterexample "Unexpected Write result"
$ prev + 1 === x .&&. sayMonotonic x st'
_ -> sayMonotonic prev st'
$ prev + 1 === x .&&. _sayMonotonic x st'
_ -> _sayMonotonic prev st'
_ -> property False)
sayMonotonic _ _ = property False
_sayMonotonic _ _ = property False
prop_noRace _ = property False

0 comments on commit b9e1b11

Please sign in to comment.