packages feed

rawlock 0.1.1.0 → 0.1.2.0

raw patch · 4 files changed

+342/−308 lines, 4 filesdep −strict-mvardep −strict-stmdep ~basedep ~io-classesdep ~nothunksPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies removed: strict-mvar, strict-stm

Dependency ranges changed: base, io-classes, nothunks

API changes (from Hackage documentation)

- Control.RAWLock: instance GHC.Base.Functor Control.RAWLock.Poisonable
- Control.RAWLock: instance GHC.Enum.Enum Control.RAWLock.Appenders
- Control.RAWLock: instance GHC.Enum.Enum Control.RAWLock.Readers
- Control.RAWLock: instance GHC.Enum.Enum Control.RAWLock.Writers
- Control.RAWLock: instance GHC.Generics.Generic (Control.RAWLock.Poisonable st)
- Control.RAWLock: instance GHC.Generics.Generic (Control.RAWLock.RAWLock m st)
- Control.RAWLock: instance GHC.Generics.Generic Control.RAWLock.RAWState
- Control.RAWLock: instance GHC.Num.Num Control.RAWLock.Appenders
- Control.RAWLock: instance GHC.Num.Num Control.RAWLock.Readers
- Control.RAWLock: instance GHC.Num.Num Control.RAWLock.Writers
- Control.RAWLock: instance GHC.Show.Show Control.RAWLock.Appenders
- Control.RAWLock: instance GHC.Show.Show Control.RAWLock.RAWState
- Control.RAWLock: instance GHC.Show.Show Control.RAWLock.Readers
- Control.RAWLock: instance GHC.Show.Show Control.RAWLock.Writers
+ Control.RAWLock: instance GHC.Internal.Base.Functor Control.RAWLock.Poisonable
+ Control.RAWLock: instance GHC.Internal.Enum.Enum Control.RAWLock.Appenders
+ Control.RAWLock: instance GHC.Internal.Enum.Enum Control.RAWLock.Readers
+ Control.RAWLock: instance GHC.Internal.Enum.Enum Control.RAWLock.Writers
+ Control.RAWLock: instance GHC.Internal.Generics.Generic (Control.RAWLock.Poisonable st)
+ Control.RAWLock: instance GHC.Internal.Generics.Generic (Control.RAWLock.RAWLock m st)
+ Control.RAWLock: instance GHC.Internal.Generics.Generic Control.RAWLock.RAWState
+ Control.RAWLock: instance GHC.Internal.Num.Num Control.RAWLock.Appenders
+ Control.RAWLock: instance GHC.Internal.Num.Num Control.RAWLock.Readers
+ Control.RAWLock: instance GHC.Internal.Num.Num Control.RAWLock.Writers
+ Control.RAWLock: instance GHC.Internal.Show.Show Control.RAWLock.Appenders
+ Control.RAWLock: instance GHC.Internal.Show.Show Control.RAWLock.RAWState
+ Control.RAWLock: instance GHC.Internal.Show.Show Control.RAWLock.Readers
+ Control.RAWLock: instance GHC.Internal.Show.Show Control.RAWLock.Writers
- Control.RAWLock: data RAWLock m st
+ Control.RAWLock: data RAWLock (m :: Type -> Type) st
- Control.RAWLock: read :: (MonadSTM m, MonadThrow (STM m)) => RAWLock m st -> STM m st
+ Control.RAWLock: read :: forall (m :: Type -> Type) st. (MonadSTM m, MonadThrow (STM m)) => RAWLock m st -> STM m st
- Control.RAWLock: unsafeAcquireReadAccess :: (MonadThrow (STM m), MonadSTM m) => RAWLock m st -> STM m st
+ Control.RAWLock: unsafeAcquireReadAccess :: forall (m :: Type -> Type) st. (MonadThrow (STM m), MonadSTM m) => RAWLock m st -> STM m st
- Control.RAWLock: unsafeReleaseReadAccess :: MonadSTM m => RAWLock m st -> STM m ()
+ Control.RAWLock: unsafeReleaseReadAccess :: forall (m :: Type -> Type) st. MonadSTM m => RAWLock m st -> STM m ()

Files

CHANGELOG.md view
@@ -2,6 +2,10 @@  # Changelog entries +## 0.1.2.0 — 2025-05-15++* Use `io-classes-1.8`.+ ## 0.1.1.0 -- 2024-11-22  * Added `Generic` and `NoThunks` instance for `RAWState`.
rawlock.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: rawlock-version: 0.1.1.0+version: 0.1.2.0 synopsis: A writer-biased RAW lock. description:   A writer-biased RAW lock.@@ -26,7 +26,7 @@   README.md  bug-reports: https://github.com/IntersectMBO/io-classes-extra/issues-tested-with: ghc ==8.10 || ==9.2 || ==9.4 || ==9.6 || ==9.8 || ==9.10+tested-with: ghc ==9.6 || ==9.8 || ==9.10 || ==9.12  source-repository head   type: git@@ -37,7 +37,7 @@   type: git   location: https://github.com/IntersectMBO/io-classes-extra   subdir: rawlock-  tag: rawlock-0.1.0.0+  tag: rawlock-0.1.2.0  common warnings   ghc-options:@@ -59,11 +59,9 @@   exposed-modules: Control.RAWLock   default-extensions: ImportQualifiedPost   build-depends:-    base >=4.14 && <4.21,-    io-classes ^>=1.5,+    base >=4.18 && <4.22,+    io-classes:{io-classes, strict-mvar, strict-stm} ^>=1.8,     nothunks ^>=0.2,-    strict-mvar ^>=1.5,-    strict-stm ^>=1.5,  test-suite rawlock-test   import: warnings@@ -74,10 +72,9 @@   build-depends:     QuickCheck,     base,-    io-classes,+    io-classes:{io-classes, strict-stm},     io-sim,     mtl,     rawlock,-    strict-stm,     tasty,     tasty-quickcheck,
src/Control/RAWLock.hs view
@@ -109,8 +109,8 @@ --    'NoThunks' check when enabled. -- --  * All public functions are exception-safe.-module Control.RAWLock (-    -- * API+module Control.RAWLock+  ( -- * API     RAWLock   , new   , poison@@ -118,6 +118,7 @@   , withAppendAccess   , withReadAccess   , withWriteAccess+     -- * Unsafe API     -- $unsafe-api   , unsafeAcquireAppendAccess@@ -139,31 +140,32 @@ -- | Any non-negative number of readers newtype Readers = Readers Word   deriving newtype (Eq, Ord, Enum, Num, NoThunks)-  deriving stock (Show)+  deriving stock Show  -- | Any non-negative number of writers newtype Writers = Writers Word   deriving newtype (Eq, Ord, Enum, Num, NoThunks)-  deriving stock (Show)+  deriving stock Show  -- | Any non-negative number of appenders newtype Appenders = Appenders Word   deriving newtype (Eq, Ord, Enum, Num, NoThunks)-  deriving stock (Show)+  deriving stock Show -data RAWState = RAWState {-    waitingReaders   :: !Readers+data RAWState = RAWState+  { waitingReaders :: !Readers   , waitingAppenders :: !Appenders-  , waitingWriters   :: !Writers-  } deriving (Show, Generic, NoThunks)+  , waitingWriters :: !Writers+  }+  deriving (Show, Generic, NoThunks)  noWriters :: Poisonable RAWState -> Bool noWriters (Healthy (RAWState _ _ w)) = w == 0-noWriters _                          = True+noWriters _ = True  onlyWriters :: Poisonable RAWState -> Bool onlyWriters (Healthy (RAWState r a _)) = r == 0 && a == 0-onlyWriters _                          = True+onlyWriters _ = True  pushReader :: Poisonable RAWState -> Poisonable RAWState pushReader = fmap (\(RAWState r a w) -> RAWState (r + 1) a w)@@ -185,28 +187,31 @@  -- | Data that can be replaced with an exception that should be thrown when -- found.-data Poisonable st =-    Healthy !st+data Poisonable st+  = Healthy !st   | Poisoned !(AllowThunk SomeException)   deriving (Generic, NoThunks, Functor) -data RAWLock m st = RAWLock {-    resource :: !(StrictTMVar m (Poisonable st))+data RAWLock m st = RAWLock+  { resource :: !(StrictTMVar m (Poisonable st))   , appender :: !(StrictMVar m ())-  , queues   :: !(StrictTVar m (Poisonable RAWState))-  } deriving (Generic)+  , queues :: !(StrictTVar m (Poisonable RAWState))+  }+  deriving Generic -deriving instance ( NoThunks (StrictTMVar m (Poisonable st))-                  , NoThunks (StrictMVar m ())-                  , NoThunks (StrictTVar m (Poisonable RAWState))-                  ) => NoThunks (RAWLock m st)+deriving instance+  ( NoThunks (StrictTMVar m (Poisonable st))+  , NoThunks (StrictMVar m ())+  , NoThunks (StrictTVar m (Poisonable RAWState))+  ) =>+  NoThunks (RAWLock m st)  new ::-     ( MonadMVar m-     , MonadLabelledSTM m-     )-  => st-  -> m (RAWLock m st)+  ( MonadMVar m+  , MonadLabelledSTM m+  ) =>+  st ->+  m (RAWLock m st) new !st = do   s <- newTMVarIO (Healthy st)   atomically $ labelTMVar s "state"@@ -226,23 +231,24 @@ -- There is no need (although it is harmless) to release again the current -- actor once it has poisoned the lock. poison ::-     (Exception e, MonadMVar m, MonadSTM m, MonadThrow (STM m), HasCallStack)-  => RAWLock m st-  -> (CallStack -> e)-  -> m (Maybe st)+  (Exception e, MonadMVar m, MonadSTM m, MonadThrow (STM m), HasCallStack) =>+  RAWLock m st ->+  (CallStack -> e) ->+  m (Maybe st) poison (RAWLock var apm q) mkExc = do-  st <- atomically $-    tryReadTMVar var >>= \case-      -- Keep original exception-      Just (Poisoned (AllowThunk exc)) -> throwIO exc-      Just (Healthy st) -> do-        writeTMVar var (Poisoned (AllowThunk (toException (mkExc callStack))))-        writeTVar q (Poisoned (AllowThunk (toException (mkExc callStack))))-        pure (Just st)-      Nothing -> do-        writeTMVar var (Poisoned (AllowThunk (toException (mkExc callStack))))-        writeTVar q (Poisoned (AllowThunk (toException (mkExc callStack))))-        pure Nothing+  st <-+    atomically $+      tryReadTMVar var >>= \case+        -- Keep original exception+        Just (Poisoned (AllowThunk exc)) -> throwIO exc+        Just (Healthy st) -> do+          writeTMVar var (Poisoned (AllowThunk (toException (mkExc callStack))))+          writeTVar q (Poisoned (AllowThunk (toException (mkExc callStack))))+          pure (Just st)+        Nothing -> do+          writeTMVar var (Poisoned (AllowThunk (toException (mkExc callStack))))+          writeTVar q (Poisoned (AllowThunk (toException (mkExc callStack))))+          pure Nothing   _ <- tryPutMVar apm ()   pure st @@ -256,10 +262,10 @@ -- Will block when there is a writer or when a writer is waiting to take the -- lock. withReadAccess ::-     (MonadSTM m, MonadCatch m, MonadThrow (STM m))-  => RAWLock m st-  -> (st -> m a)-  -> m a+  (MonadSTM m, MonadCatch m, MonadThrow (STM m)) =>+  RAWLock m st ->+  (st -> m a) ->+  m a withReadAccess lock =   bracket     (atomically (unsafeAcquireReadAccess lock))@@ -269,42 +275,44 @@ -- -- Will block when there is another writer, readers or appenders. withWriteAccess ::-     (MonadSTM m, MonadCatch m, MonadThrow (STM m))-  => RAWLock m st-  -> (st -> m (a, st))-  -> m a+  (MonadSTM m, MonadCatch m, MonadThrow (STM m)) =>+  RAWLock m st ->+  (st -> m (a, st)) ->+  m a withWriteAccess lock f =-  fst . fst <$> generalBracket-   (unsafeAcquireWriteAccess lock)-   (\orig -> \case-       ExitCaseSuccess (_, st) -> unsafeReleaseWriteAccess lock st-       _ -> unsafeReleaseWriteAccess lock orig-   )-   f+  fst . fst+    <$> generalBracket+      (unsafeAcquireWriteAccess lock)+      ( \orig -> \case+          ExitCaseSuccess (_, st) -> unsafeReleaseWriteAccess lock st+          _ -> unsafeReleaseWriteAccess lock orig+      )+      f  -- | Acquire the 'RAWLock' as an appender. -- -- Will block when there is a writer or when there is another appender. withAppendAccess ::-     (MonadThrow (STM m), MonadSTM m, MonadCatch m, MonadMVar m)-  => RAWLock m st-  -> (st -> m (a, st))-  -> m a+  (MonadThrow (STM m), MonadSTM m, MonadCatch m, MonadMVar m) =>+  RAWLock m st ->+  (st -> m (a, st)) ->+  m a withAppendAccess lock f = do-  fst . fst <$> generalBracket-   (unsafeAcquireAppendAccess lock)-   (\orig -> \case-       ExitCaseSuccess (_, st) -> unsafeReleaseAppendAccess lock st-       _ -> unsafeReleaseAppendAccess lock orig-   )-   f+  fst . fst+    <$> generalBracket+      (unsafeAcquireAppendAccess lock)+      ( \orig -> \case+          ExitCaseSuccess (_, st) -> unsafeReleaseAppendAccess lock st+          _ -> unsafeReleaseAppendAccess lock orig+      )+      f  {-------------------------------------------------------------------------------   Unsafe API -------------------------------------------------------------------------------}  throwPoisoned :: MonadThrow m => Poisonable st -> m st-throwPoisoned (Healthy st)                = pure st+throwPoisoned (Healthy st) = pure st throwPoisoned (Poisoned (AllowThunk exc)) = throwIO exc  -- $unsafe-api@@ -320,9 +328,9 @@ -- presence of an exception!  unsafeAcquireReadAccess ::-     (MonadThrow (STM m), MonadSTM m)-  => RAWLock m st-  -> STM m st+  (MonadThrow (STM m), MonadSTM m) =>+  RAWLock m st ->+  STM m st unsafeAcquireReadAccess (RAWLock var _ qs) = do   -- wait until there are no writers   readTVar qs >>= check . noWriters@@ -337,9 +345,9 @@   modifyTVar qs popReader  unsafeAcquireWriteAccess ::-     (MonadThrow (STM m), MonadCatch m, MonadSTM m)-  => RAWLock m st-  -> m st+  (MonadThrow (STM m), MonadCatch m, MonadSTM m) =>+  RAWLock m st ->+  m st unsafeAcquireWriteAccess (RAWLock var _ qs) = do   -- queue myself if there are no other writers   atomically $ do@@ -347,18 +355,21 @@     readTVar qs >>= check . noWriters     -- queue myself     modifyTVar qs pushWriter-  atomically (do-    -- wait until there are no readers (and as I queued myself above, I'm the-    -- only waiting writer)-    readTVar qs >>= check . onlyWriters-    -- acquire the state-    throwPoisoned =<< takeTMVar var) `onException` atomically (modifyTVar qs popWriter)+  atomically+    ( do+        -- wait until there are no readers (and as I queued myself above, I'm the+        -- only waiting writer)+        readTVar qs >>= check . onlyWriters+        -- acquire the state+        throwPoisoned =<< takeTMVar var+    )+    `onException` atomically (modifyTVar qs popWriter)  unsafeReleaseWriteAccess ::-     (MonadThrow (STM m), MonadSTM m)-  => RAWLock m st-  -> st-  -> m ()+  (MonadThrow (STM m), MonadSTM m) =>+  RAWLock m st ->+  st ->+  m () unsafeReleaseWriteAccess (RAWLock var _ qs) !st =   atomically $ do     -- write the new state@@ -370,27 +381,28 @@     modifyTVar qs popWriter  unsafeAcquireAppendAccess ::-     (MonadThrow (STM m), MonadCatch m, MonadMVar m, MonadSTM m)-  => RAWLock m st-  -> m st+  (MonadThrow (STM m), MonadCatch m, MonadMVar m, MonadSTM m) =>+  RAWLock m st ->+  m st unsafeAcquireAppendAccess (RAWLock var apm qs) = do   atomically $ do     -- wait until there are no writers     readTVar qs >>= check . noWriters     -- queue myself     modifyTVar qs pushAppender-  (do+  ( do       -- lock the append access       takeMVar apm       -- acquire the state       atomically (readTMVar var >>= throwPoisoned) `onException` putMVar apm ()-    ) `onException` atomically (modifyTVar qs popAppender)+    )+    `onException` atomically (modifyTVar qs popAppender)  unsafeReleaseAppendAccess ::-     (MonadThrow (STM m), MonadMVar m, MonadSTM m)-  => RAWLock m st-  -> st-  -> m ()+  (MonadThrow (STM m), MonadMVar m, MonadSTM m) =>+  RAWLock m st ->+  st ->+  m () unsafeReleaseAppendAccess (RAWLock var apm qs) !st = do   atomically $ do     -- write the new state
test/Main.hs view
@@ -26,44 +26,51 @@ import Test.Tasty.QuickCheck  main :: IO ()-main = defaultMain $ testGroup "RAWLock" [-    testProperty "Exception safe" $ conjoin (map prop_exception_safe allCommandCombinations)-  , testProperty "correctness" prop_correctness-  , testProperty "unsafe functions do not deadlock" $ conjoin (map prop_unsafe_actions allCommandCombinations)-  ]+main =+  defaultMain $+    testGroup+      "RAWLock"+      [ testProperty "Exception safe" $ conjoin (map prop_exception_safe allCommandCombinations)+      , testProperty "correctness" prop_correctness+      , testProperty "unsafe functions do not deadlock" $+          conjoin (map prop_unsafe_actions allCommandCombinations)+      ]  {-------------------------------------------------------------------------------   Exception safe -------------------------------------------------------------------------------}  data Action = Read | Incr | Append-  deriving (Show)+  deriving Show  allCommandCombinations :: [(Action, Action, Action)] allCommandCombinations =-  [ (a, b, c) | let cmds = [Read, Incr, Append], a <- cmds, b <- cmds, c <- cmds ]+  [(a, b, c) | let cmds = [Read, Incr, Append], a <- cmds, b <- cmds, c <- cmds]  prop_exception_safe :: (Action, Action, Action) -> Property-prop_exception_safe actions@(a1, a2, a3) = counterexample (show actions) $-    exploreSimTrace id action+prop_exception_safe actions@(a1, a2, a3) =+  counterexample (show actions) $+    exploreSimTrace+      id+      action       (\_ tr -> counterexample (ppTrace tr) . property . isRight . traceResult False $ tr)-  where-    action :: IOSim s ()-    action = do-      exploreRaces-      l <- new (0 :: Int)-      let c = \case-            Read -> withReadAccess l $ \s -> say (show s)-            Incr -> withWriteAccess l $ \s -> say (show s) >> pure ((), s + 1)-            Append -> withAppendAccess l $ \s -> say (show s) >> pure ((), s + 1)-      t1 <- async $ c a1-      t2 <- async $ c a2-      t3 <- async $ c a3-      async (cancel t1) >>= wait-      (_ :: Either SomeException ()) <- waitCatch t1-      (_ :: Either SomeException ()) <- waitCatch t2-      (_ :: Either SomeException ()) <- waitCatch t3-      pure ()+ where+  action :: IOSim s ()+  action = do+    exploreRaces+    l <- new (0 :: Int)+    let c = \case+          Read -> withReadAccess l $ \s -> say (show s)+          Incr -> withWriteAccess l $ \s -> say (show s) >> pure ((), s + 1)+          Append -> withAppendAccess l $ \s -> say (show s) >> pure ((), s + 1)+    t1 <- async $ c a1+    t2 <- async $ c a2+    t3 <- async $ c a3+    async (cancel t1) >>= wait+    (_ :: Either SomeException ()) <- waitCatch t1+    (_ :: Either SomeException ()) <- waitCatch t2+    (_ :: Either SomeException ()) <- waitCatch t3+    pure ()  {-------------------------------------------------------------------------------   Correctness@@ -84,94 +91,98 @@ -- 'isConsistent'), e.g., not more than one concurrent appender. prop_correctness :: TestSetup -> Property prop_correctness (TestSetup rawDelays) =-    monadicSimWithTrace tabulateBlockeds test-  where-    RAW readerDelays appenderDelays writerDelays = rawDelays+  monadicSimWithTrace tabulateBlockeds test+ where+  RAW readerDelays appenderDelays writerDelays = rawDelays -    test :: forall s. PropertyM (IOSim s) ()-    test = do-      rawVars@(RAW varReaders varAppenders varWriters) <- run newRAWVars+  test :: forall s. PropertyM (IOSim s) ()+  test = do+    rawVars@(RAW varReaders varAppenders varWriters) <- run newRAWVars -      trace <- run $ do-        rawLock  <- new ()-        varTrace <- newTVarIO []+    trace <- run $ do+      rawLock <- new ()+      varTrace <- newTVarIO [] -        let traceState :: STM (IOSim s) ()-            traceState = do-              rawState <- readRAWState rawVars-              modifyTVar varTrace (rawState:)+      let traceState :: STM (IOSim s) ()+          traceState = do+            rawState <- readRAWState rawVars+            modifyTVar varTrace (rawState :) -        threads <- mapM (async . (labelThisThread "testThread" >>)) $-          map (runReader   rawLock traceState varReaders)   readerDelays   <>-          map (runAppender rawLock traceState varAppenders) appenderDelays <>-          map (runWriter   rawLock traceState varWriters)   writerDelays+      threads <-+        mapM (async . (labelThisThread "testThread" >>)) $+          map (runReader rawLock traceState varReaders) readerDelays+            <> map (runAppender rawLock traceState varAppenders) appenderDelays+            <> map (runWriter rawLock traceState varWriters) writerDelays -        mapM_ wait threads-        reverse <$> atomically (readTVar varTrace)+      mapM_ wait threads+      reverse <$> atomically (readTVar varTrace) -      checkRAWTrace trace+    checkRAWTrace trace -    runReader-      :: RAWLock (IOSim s) ()-      -> STM (IOSim s) ()  -- ^ Trace the 'RAWState'-      -> StrictTVar (IOSim s) Int-      -> [ThreadDelays]-      -> IOSim s ()-    runReader rawLock traceState varReaders =-      mapM_ $ \(ThreadDelays before with) -> do-        threadDelay before-        withReadAccess rawLock $ const $ do-          atomically $ modifyTVar varReaders succ *> traceState-          threadDelay with-          atomically $ modifyTVar varReaders pred *> traceState+  runReader ::+    RAWLock (IOSim s) () ->+    STM (IOSim s) () ->+    -- \^ Trace the 'RAWState'+    StrictTVar (IOSim s) Int ->+    [ThreadDelays] ->+    IOSim s ()+  runReader rawLock traceState varReaders =+    mapM_ $ \(ThreadDelays before with) -> do+      threadDelay before+      withReadAccess rawLock $ const $ do+        atomically $ modifyTVar varReaders succ *> traceState+        threadDelay with+        atomically $ modifyTVar varReaders pred *> traceState -    runAppender-      :: RAWLock (IOSim s) ()-      -> STM (IOSim s) ()  -- ^ Trace the 'RAWState'-      -> StrictTVar (IOSim s) Int-      -> [ThreadDelays]-      -> IOSim s ()-    runAppender rawLock traceState varAppenders =-      mapM_ $ \(ThreadDelays before with) -> do-        threadDelay before-        withAppendAccess rawLock $ const $ do-          atomically $ modifyTVar varAppenders succ *> traceState-          threadDelay with-          atomically $ modifyTVar varAppenders pred *> traceState-          return ((), ())+  runAppender ::+    RAWLock (IOSim s) () ->+    STM (IOSim s) () ->+    -- \^ Trace the 'RAWState'+    StrictTVar (IOSim s) Int ->+    [ThreadDelays] ->+    IOSim s ()+  runAppender rawLock traceState varAppenders =+    mapM_ $ \(ThreadDelays before with) -> do+      threadDelay before+      withAppendAccess rawLock $ const $ do+        atomically $ modifyTVar varAppenders succ *> traceState+        threadDelay with+        atomically $ modifyTVar varAppenders pred *> traceState+        return ((), ()) -    runWriter-      :: RAWLock (IOSim s) ()-      -> STM (IOSim s) ()  -- ^ Trace the 'RAWState'-      -> StrictTVar (IOSim s) Int-      -> [ThreadDelays]-      -> IOSim s ()-    runWriter rawLock traceState varWriters =-      mapM_ $ \(ThreadDelays before with) -> do-        threadDelay before-        withWriteAccess rawLock $ const $ do-          atomically $ modifyTVar varWriters succ *> traceState-          threadDelay with-          atomically $ modifyTVar varWriters pred *> traceState-          return ((), ())+  runWriter ::+    RAWLock (IOSim s) () ->+    STM (IOSim s) () ->+    -- \^ Trace the 'RAWState'+    StrictTVar (IOSim s) Int ->+    [ThreadDelays] ->+    IOSim s ()+  runWriter rawLock traceState varWriters =+    mapM_ $ \(ThreadDelays before with) -> do+      threadDelay before+      withWriteAccess rawLock $ const $ do+        atomically $ modifyTVar varWriters succ *> traceState+        threadDelay with+        atomically $ modifyTVar varWriters pred *> traceState+        return ((), ())  -- | Like 'monadicSim' (which is like 'monadicIO' for the IO simulator), but -- allows inspecting the trace for labelling purposes. monadicSimWithTrace ::-     Testable a-  => (forall x. SimTrace x -> Property -> Property)-  -> (forall s. PropertyM (IOSim s) a)-  -> Property+  Testable a =>+  (forall x. SimTrace x -> Property -> Property) ->+  (forall s. PropertyM (IOSim s) a) ->+  Property monadicSimWithTrace attachTrace m = property $ do-    tr <- runSimGenWithTrace (monadic' m)-    case traceResult False tr of-      Left failure -> throw failure-      Right prop   -> return $ attachTrace tr prop-  where-    runSimGenWithTrace :: (forall s. Gen (IOSim s a)) -> Gen (SimTrace a)-    runSimGenWithTrace f = do-      Capture eval <- capture-      return $ runSimTrace (eval f)+  tr <- runSimGenWithTrace (monadic' m)+  case traceResult False tr of+    Left failure -> throw failure+    Right prop -> return $ attachTrace tr prop+ where+  runSimGenWithTrace :: (forall s. Gen (IOSim s a)) -> Gen (SimTrace a)+  runSimGenWithTrace f = do+    Capture eval <- capture+    return $ runSimTrace (eval f)  -- | Tabulate the number of times a thread is blocked. --@@ -179,30 +190,30 @@ -- contention, we're not testing the lock properly. tabulateBlockeds :: SimTrace a -> Property -> Property tabulateBlockeds tr =-    tabulate "number of times blocked" [classifyBand (count isBlocked tr)]-  where-    isBlocked (EventTxBlocked {}) = Just ()-    isBlocked _                   = Nothing+  tabulate "number of times blocked" [classifyBand (count isBlocked tr)]+ where+  isBlocked (EventTxBlocked{}) = Just ()+  isBlocked _ = Nothing -    count :: (SimEventType -> Maybe x) -> SimTrace a -> Int-    count p = length . selectTraceEvents (const p)+  count :: (SimEventType -> Maybe x) -> SimTrace a -> Int+  count p = length . selectTraceEvents (const p) -    classifyBand :: Int -> String-    classifyBand n-      | n < 10-      = "n < 10"-      | n < 100-      = "n < 100"-      | n < 1000-      = "n < 1,000"-      | n < 10_000-      = "1,000 < n < 10,000"-      | n < 100_000-      = "10,000 < n < 100,000"-      | n < 1000_000-      = "100,000 < n < 1,000,000"-      | otherwise-      = "1,000,000 < n"+  classifyBand :: Int -> String+  classifyBand n+    | n < 10 =+        "n < 10"+    | n < 100 =+        "n < 100"+    | n < 1000 =+        "n < 1,000"+    | n < 10_000 =+        "1,000 < n < 10,000"+    | n < 100_000 =+        "10,000 < n < 100,000"+    | n < 1000_000 =+        "100,000 < n < 1,000,000"+    | otherwise =+        "1,000,000 < n"  {-------------------------------------------------------------------------------   State checking@@ -210,10 +221,10 @@  -- | Data type reused whenever we need something for all three of them. data RAW a = RAW-    { readers   :: a-    , appenders :: a-    , writers   :: a-    }+  { readers :: a+  , appenders :: a+  , writers :: a+  }   deriving (Show, Eq, Functor)  type RAWVars m = RAW (StrictTVar m Int)@@ -224,114 +235,124 @@ type RAWState' = RAW Int  readRAWState :: RAWVars (IOSim s) -> STM (IOSim s) RAWState'-readRAWState RAW { readers, appenders, writers } =-    RAW-      <$> readTVar readers-      <*> readTVar appenders-      <*> readTVar writers+readRAWState RAW{readers, appenders, writers} =+  RAW+    <$> readTVar readers+    <*> readTVar appenders+    <*> readTVar writers  isConsistent :: RAWState' -> Except String ()-isConsistent RAW { readers, appenders, writers }-    | appenders > 1-    = throwError $ show appenders <> " appenders while at most 1 is allowed"-    | writers > 1-    = throwError $ show writers <> " writers while at most 1 is allowed"-    | writers == 1, readers > 0-    = throwError $ "writer concurrent with " <> show readers <> "reader(s)"-    | writers == 1, appenders > 0-    = throwError $ "writer concurrent with an appender"-    | otherwise-    = return ()+isConsistent RAW{readers, appenders, writers}+  | appenders > 1 =+      throwError $ show appenders <> " appenders while at most 1 is allowed"+  | writers > 1 =+      throwError $ show writers <> " writers while at most 1 is allowed"+  | writers == 1+  , readers > 0 =+      throwError $ "writer concurrent with " <> show readers <> "reader(s)"+  | writers == 1+  , appenders > 0 =+      throwError $ "writer concurrent with an appender"+  | otherwise =+      return ()  type RAWTrace = [RAWState']  checkRAWTrace :: Monad m => RAWTrace -> PropertyM m () checkRAWTrace = mapM_ $ \rawState ->-    case runExcept $ isConsistent rawState of-      Left msg -> do-        monitor (counterexample msg)-        Test.QuickCheck.Monadic.assert False-      Right () ->-        return ()+  case runExcept $ isConsistent rawState of+    Left msg -> do+      monitor (counterexample msg)+      Test.QuickCheck.Monadic.assert False+    Right () ->+      return ()  {-------------------------------------------------------------------------------   Generators -------------------------------------------------------------------------------}  newtype TestSetup = TestSetup (RAW [[ThreadDelays]])-  deriving (Show)+  deriving Show  instance Arbitrary TestSetup where   arbitrary = do-    nbReaders   <- choose (0, 3)+    nbReaders <- choose (0, 3)     nbAppenders <- choose (0, 3)-    nbWriters   <- choose (0, 3)-    readers   <- vectorOf nbReaders   arbitrary+    nbWriters <- choose (0, 3)+    readers <- vectorOf nbReaders arbitrary     appenders <- vectorOf nbAppenders arbitrary-    writers   <- vectorOf nbWriters   arbitrary-    return $ TestSetup RAW { readers, appenders, writers }-  shrink (TestSetup raw@RAW { readers, appenders, writers }) =-    [TestSetup raw { readers   = readers'   } | readers'   <- shrink readers  ] <>-    [TestSetup raw { appenders = appenders' } | appenders' <- shrink appenders] <>-    [TestSetup raw { writers   = writers'   } | writers'   <- shrink writers  ]+    writers <- vectorOf nbWriters arbitrary+    return $ TestSetup RAW{readers, appenders, writers}+  shrink (TestSetup raw@RAW{readers, appenders, writers}) =+    [TestSetup raw{readers = readers'} | readers' <- shrink readers]+      <> [TestSetup raw{appenders = appenders'} | appenders' <- shrink appenders]+      <> [TestSetup raw{writers = writers'} | writers' <- shrink writers]  data ThreadDelays = ThreadDelays-    { beforeLockTime :: Int-      -- ^ How long the thread should wait before it starts to take the lock-    , withLockTime   :: Int-      -- ^ How long the thread should wait while holding the lock-    }+  { beforeLockTime :: Int+  -- ^ How long the thread should wait before it starts to take the lock+  , withLockTime :: Int+  -- ^ How long the thread should wait while holding the lock+  }   deriving (Eq, Show)  instance Arbitrary ThreadDelays where   arbitrary = do     beforeLockTime <- choose (0, 1000)-    withLockTime   <- choose (0, 2000)-    return ThreadDelays { beforeLockTime, withLockTime }+    withLockTime <- choose (0, 2000)+    return ThreadDelays{beforeLockTime, withLockTime}  {-------------------------------------------------------------------------------   unsafe functions -------------------------------------------------------------------------------}  prop_unsafe_actions :: (Action, Action, Action) -> Property-prop_unsafe_actions actions@(a1, a2, a3) = counterexample (show actions) $-  exploreSimTrace id action+prop_unsafe_actions actions@(a1, a2, a3) =+  counterexample (show actions) $+    exploreSimTrace+      id+      action       (\_ tr -> counterexample (ppTrace tr) . property . isRight . traceResult False $ tr)-  where-    action :: IOSim s ()-    action = do-      exploreRaces-      l <- new (0 :: Int)-      let c = \case-            Read -> bracket-                      (atomically (unsafeAcquireReadAccess l))-                      (const $ atomically (unsafeReleaseReadAccess l))-                      (say . ("Read: " <>) . show)-            Incr -> generalBracket-                      (unsafeAcquireWriteAccess l)-                      (\orig -> \case-                          ExitCaseSuccess s' -> unsafeReleaseWriteAccess l s'-                          _ -> unsafeReleaseWriteAccess l orig-                      )-                      (\s -> do-                          say ("Incr: " <> show s)-                          pure (s + 1)-                      ) >> pure ()-            Append -> generalBracket-                        (unsafeAcquireAppendAccess l)-                        (\orig -> \case-                            ExitCaseSuccess s' -> unsafeReleaseAppendAccess l s'-                            _ -> unsafeReleaseAppendAccess l orig-                        )-                        (\s -> do-                            say ("Append: " <> show s)-                            pure (s + 1)-                        ) >> pure ()-      t1 <- async $ c a1-      t2 <- async $ c a2-      t3 <- async $ c a3-      async (cancel t1) >>= wait-      (_ :: Either SomeException ()) <- waitCatch t1-      (_ :: Either SomeException ()) <- waitCatch t2-      (_ :: Either SomeException ()) <- waitCatch t3-      pure ()+ where+  action :: IOSim s ()+  action = do+    exploreRaces+    l <- new (0 :: Int)+    let c = \case+          Read ->+            bracket+              (atomically (unsafeAcquireReadAccess l))+              (const $ atomically (unsafeReleaseReadAccess l))+              (say . ("Read: " <>) . show)+          Incr ->+            generalBracket+              (unsafeAcquireWriteAccess l)+              ( \orig -> \case+                  ExitCaseSuccess s' -> unsafeReleaseWriteAccess l s'+                  _ -> unsafeReleaseWriteAccess l orig+              )+              ( \s -> do+                  say ("Incr: " <> show s)+                  pure (s + 1)+              )+              >> pure ()+          Append ->+            generalBracket+              (unsafeAcquireAppendAccess l)+              ( \orig -> \case+                  ExitCaseSuccess s' -> unsafeReleaseAppendAccess l s'+                  _ -> unsafeReleaseAppendAccess l orig+              )+              ( \s -> do+                  say ("Append: " <> show s)+                  pure (s + 1)+              )+              >> pure ()+    t1 <- async $ c a1+    t2 <- async $ c a2+    t3 <- async $ c a3+    async (cancel t1) >>= wait+    (_ :: Either SomeException ()) <- waitCatch t1+    (_ :: Either SomeException ()) <- waitCatch t2+    (_ :: Either SomeException ()) <- waitCatch t3+    pure ()