dejafu 0.9.1.2 → 1.0.0.0
raw patch · 24 files changed
+3554/−3305 lines, 24 filesdep +profunctorsdep −transformers-basedep ~concurrencyPVP ok
version bump matches the API change (PVP)
Dependencies added: profunctors
Dependencies removed: transformers-base
Dependency ranges changed: concurrency
API changes (from Hackage documentation)
- Test.DejaFu: [_casesChecked] :: Result a -> Int
- Test.DejaFu: alwaysTrue2 :: (Either Failure a -> Either Failure a -> Bool) -> Predicate a
- Test.DejaFu: autocheckIO :: (Eq a, Show a) => ConcIO a -> IO Bool
- Test.DejaFu: autocheckWayIO :: (Eq a, Show a) => Way -> MemType -> ConcIO a -> IO Bool
- Test.DejaFu: dejafuDiscardIO :: Show a => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcIO a -> (String, Predicate a) -> IO Bool
- Test.DejaFu: dejafuIO :: Show a => ConcIO a -> (String, Predicate a) -> IO Bool
- Test.DejaFu: dejafuWayIO :: Show a => Way -> MemType -> ConcIO a -> (String, Predicate a) -> IO Bool
- Test.DejaFu: dejafusIO :: Show a => ConcIO a -> [(String, Predicate a)] -> IO Bool
- Test.DejaFu: dejafusWayIO :: Show a => Way -> MemType -> ConcIO a -> [(String, Predicate a)] -> IO Bool
- Test.DejaFu: runTestM :: MonadRef r n => Predicate a -> ConcT r n a -> n (Result a)
- Test.DejaFu: runTestWayM :: MonadRef r n => Way -> MemType -> Predicate a -> ConcT r n a -> n (Result a)
- Test.DejaFu.Common: Abort :: Failure
- Test.DejaFu.Common: BlockedPutMVar :: MVarId -> ThreadAction
- Test.DejaFu.Common: BlockedReadMVar :: MVarId -> ThreadAction
- Test.DejaFu.Common: BlockedSTM :: TTrace -> ThreadAction
- Test.DejaFu.Common: BlockedTakeMVar :: MVarId -> ThreadAction
- Test.DejaFu.Common: BlockedThrowTo :: ThreadId -> ThreadAction
- Test.DejaFu.Common: CRefId :: (Maybe String) -> {-# UNPACK #-} !Int -> CRefId
- Test.DejaFu.Common: CasCRef :: CRefId -> Bool -> ThreadAction
- Test.DejaFu.Common: Catching :: ThreadAction
- Test.DejaFu.Common: CommitCRef :: ThreadId -> CRefId -> ThreadAction
- Test.DejaFu.Common: Continue :: Decision
- Test.DejaFu.Common: Deadlock :: Failure
- Test.DejaFu.Common: Fork :: ThreadId -> ThreadAction
- Test.DejaFu.Common: GetNumCapabilities :: Int -> ThreadAction
- Test.DejaFu.Common: Id :: Int -> Int -> Int -> Int -> [String] -> [String] -> [String] -> [String] -> IdSource
- Test.DejaFu.Common: IllegalSubconcurrency :: Failure
- Test.DejaFu.Common: InternalError :: Failure
- Test.DejaFu.Common: Killed :: ThreadAction
- Test.DejaFu.Common: LiftIO :: ThreadAction
- Test.DejaFu.Common: MVarId :: (Maybe String) -> {-# UNPACK #-} !Int -> MVarId
- Test.DejaFu.Common: ModCRef :: CRefId -> ThreadAction
- Test.DejaFu.Common: ModCRefCas :: CRefId -> ThreadAction
- Test.DejaFu.Common: MonadFailException :: String -> MonadFailException
- Test.DejaFu.Common: MyThreadId :: ThreadAction
- Test.DejaFu.Common: NewCRef :: CRefId -> ThreadAction
- Test.DejaFu.Common: NewMVar :: MVarId -> ThreadAction
- Test.DejaFu.Common: PartialStoreOrder :: MemType
- Test.DejaFu.Common: PartiallySynchronisedCommit :: CRefId -> ActionType
- Test.DejaFu.Common: PartiallySynchronisedModify :: CRefId -> ActionType
- Test.DejaFu.Common: PartiallySynchronisedWrite :: CRefId -> ActionType
- Test.DejaFu.Common: PopCatching :: ThreadAction
- Test.DejaFu.Common: PutMVar :: MVarId -> [ThreadId] -> ThreadAction
- Test.DejaFu.Common: ReadCRef :: CRefId -> ThreadAction
- Test.DejaFu.Common: ReadCRefCas :: CRefId -> ThreadAction
- Test.DejaFu.Common: ReadMVar :: MVarId -> ThreadAction
- Test.DejaFu.Common: ResetMasking :: Bool -> MaskingState -> ThreadAction
- Test.DejaFu.Common: Return :: ThreadAction
- Test.DejaFu.Common: STM :: TTrace -> [ThreadId] -> ThreadAction
- Test.DejaFu.Common: STMDeadlock :: Failure
- Test.DejaFu.Common: SequentialConsistency :: MemType
- Test.DejaFu.Common: SetMasking :: Bool -> MaskingState -> ThreadAction
- Test.DejaFu.Common: SetNumCapabilities :: Int -> ThreadAction
- Test.DejaFu.Common: Start :: ThreadId -> Decision
- Test.DejaFu.Common: Stop :: ThreadAction
- Test.DejaFu.Common: StopSubconcurrency :: ThreadAction
- Test.DejaFu.Common: Subconcurrency :: ThreadAction
- Test.DejaFu.Common: SwitchTo :: ThreadId -> Decision
- Test.DejaFu.Common: SynchronisedModify :: CRefId -> ActionType
- Test.DejaFu.Common: SynchronisedOther :: ActionType
- Test.DejaFu.Common: SynchronisedRead :: MVarId -> ActionType
- Test.DejaFu.Common: SynchronisedWrite :: MVarId -> ActionType
- Test.DejaFu.Common: TCatch :: TTrace -> (Maybe TTrace) -> TAction
- Test.DejaFu.Common: TNew :: TVarId -> TAction
- Test.DejaFu.Common: TOrElse :: TTrace -> (Maybe TTrace) -> TAction
- Test.DejaFu.Common: TRead :: TVarId -> TAction
- Test.DejaFu.Common: TRetry :: TAction
- Test.DejaFu.Common: TStop :: TAction
- Test.DejaFu.Common: TThrow :: TAction
- Test.DejaFu.Common: TVarId :: (Maybe String) -> {-# UNPACK #-} !Int -> TVarId
- Test.DejaFu.Common: TWrite :: TVarId -> TAction
- Test.DejaFu.Common: TakeMVar :: MVarId -> [ThreadId] -> ThreadAction
- Test.DejaFu.Common: ThreadDelay :: Int -> ThreadAction
- Test.DejaFu.Common: ThreadId :: (Maybe String) -> {-# UNPACK #-} !Int -> ThreadId
- Test.DejaFu.Common: Throw :: ThreadAction
- Test.DejaFu.Common: ThrowTo :: ThreadId -> ThreadAction
- Test.DejaFu.Common: TotalStoreOrder :: MemType
- Test.DejaFu.Common: TryPutMVar :: MVarId -> Bool -> [ThreadId] -> ThreadAction
- Test.DejaFu.Common: TryReadMVar :: MVarId -> Bool -> ThreadAction
- Test.DejaFu.Common: TryTakeMVar :: MVarId -> Bool -> [ThreadId] -> ThreadAction
- Test.DejaFu.Common: UncaughtException :: SomeException -> Failure
- Test.DejaFu.Common: UnsynchronisedOther :: ActionType
- Test.DejaFu.Common: UnsynchronisedRead :: CRefId -> ActionType
- Test.DejaFu.Common: UnsynchronisedWrite :: CRefId -> ActionType
- Test.DejaFu.Common: WillCasCRef :: CRefId -> Lookahead
- Test.DejaFu.Common: WillCatching :: Lookahead
- Test.DejaFu.Common: WillCommitCRef :: ThreadId -> CRefId -> Lookahead
- Test.DejaFu.Common: WillFork :: Lookahead
- Test.DejaFu.Common: WillGetNumCapabilities :: Lookahead
- Test.DejaFu.Common: WillLiftIO :: Lookahead
- Test.DejaFu.Common: WillModCRef :: CRefId -> Lookahead
- Test.DejaFu.Common: WillModCRefCas :: CRefId -> Lookahead
- Test.DejaFu.Common: WillMyThreadId :: Lookahead
- Test.DejaFu.Common: WillNewCRef :: Lookahead
- Test.DejaFu.Common: WillNewMVar :: Lookahead
- Test.DejaFu.Common: WillPopCatching :: Lookahead
- Test.DejaFu.Common: WillPutMVar :: MVarId -> Lookahead
- Test.DejaFu.Common: WillReadCRef :: CRefId -> Lookahead
- Test.DejaFu.Common: WillReadCRefCas :: CRefId -> Lookahead
- Test.DejaFu.Common: WillReadMVar :: MVarId -> Lookahead
- Test.DejaFu.Common: WillResetMasking :: Bool -> MaskingState -> Lookahead
- Test.DejaFu.Common: WillReturn :: Lookahead
- Test.DejaFu.Common: WillSTM :: Lookahead
- Test.DejaFu.Common: WillSetMasking :: Bool -> MaskingState -> Lookahead
- Test.DejaFu.Common: WillSetNumCapabilities :: Int -> Lookahead
- Test.DejaFu.Common: WillStop :: Lookahead
- Test.DejaFu.Common: WillStopSubconcurrency :: Lookahead
- Test.DejaFu.Common: WillSubconcurrency :: Lookahead
- Test.DejaFu.Common: WillTakeMVar :: MVarId -> Lookahead
- Test.DejaFu.Common: WillThreadDelay :: Int -> Lookahead
- Test.DejaFu.Common: WillThrow :: Lookahead
- Test.DejaFu.Common: WillThrowTo :: ThreadId -> Lookahead
- Test.DejaFu.Common: WillTryPutMVar :: MVarId -> Lookahead
- Test.DejaFu.Common: WillTryReadMVar :: MVarId -> Lookahead
- Test.DejaFu.Common: WillTryTakeMVar :: MVarId -> Lookahead
- Test.DejaFu.Common: WillWriteCRef :: CRefId -> Lookahead
- Test.DejaFu.Common: WillYield :: Lookahead
- Test.DejaFu.Common: WriteCRef :: CRefId -> ThreadAction
- Test.DejaFu.Common: Yield :: ThreadAction
- Test.DejaFu.Common: [_nextCRId] :: IdSource -> Int
- Test.DejaFu.Common: [_nextMVId] :: IdSource -> Int
- Test.DejaFu.Common: [_nextTId] :: IdSource -> Int
- Test.DejaFu.Common: [_nextTVId] :: IdSource -> Int
- Test.DejaFu.Common: [_usedCRNames] :: IdSource -> [String]
- Test.DejaFu.Common: [_usedMVNames] :: IdSource -> [String]
- Test.DejaFu.Common: [_usedTNames] :: IdSource -> [String]
- Test.DejaFu.Common: [_usedTVNames] :: IdSource -> [String]
- Test.DejaFu.Common: crefOf :: ActionType -> Maybe CRefId
- Test.DejaFu.Common: data ActionType
- Test.DejaFu.Common: data CRefId
- Test.DejaFu.Common: data Decision
- Test.DejaFu.Common: data Failure
- Test.DejaFu.Common: data IdSource
- Test.DejaFu.Common: data Lookahead
- Test.DejaFu.Common: data MVarId
- Test.DejaFu.Common: data MemType
- Test.DejaFu.Common: data TAction
- Test.DejaFu.Common: data TVarId
- Test.DejaFu.Common: data ThreadAction
- Test.DejaFu.Common: data ThreadId
- Test.DejaFu.Common: efromJust :: String -> Maybe a -> a
- Test.DejaFu.Common: efromList :: String -> [a] -> NonEmpty a
- Test.DejaFu.Common: ehead :: String -> [a] -> a
- Test.DejaFu.Common: eidx :: String -> [a] -> Int -> a
- Test.DejaFu.Common: etail :: String -> [a] -> [a]
- Test.DejaFu.Common: fatal :: String -> String -> a
- Test.DejaFu.Common: initialIdSource :: IdSource
- Test.DejaFu.Common: initialThread :: ThreadId
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.ActionType
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.CRefId
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.Decision
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.Failure
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.IdSource
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.Lookahead
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.MVarId
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.MemType
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.TAction
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.TVarId
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.ThreadAction
- Test.DejaFu.Common: instance Control.DeepSeq.NFData Test.DejaFu.Common.ThreadId
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.ActionType
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.CRefId
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.Decision
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.Failure
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.IdSource
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.Lookahead
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.MVarId
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.MemType
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.TAction
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.TVarId
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.ThreadAction
- Test.DejaFu.Common: instance GHC.Classes.Eq Test.DejaFu.Common.ThreadId
- Test.DejaFu.Common: instance GHC.Classes.Ord Test.DejaFu.Common.CRefId
- Test.DejaFu.Common: instance GHC.Classes.Ord Test.DejaFu.Common.Failure
- Test.DejaFu.Common: instance GHC.Classes.Ord Test.DejaFu.Common.IdSource
- Test.DejaFu.Common: instance GHC.Classes.Ord Test.DejaFu.Common.MVarId
- Test.DejaFu.Common: instance GHC.Classes.Ord Test.DejaFu.Common.MemType
- Test.DejaFu.Common: instance GHC.Classes.Ord Test.DejaFu.Common.TVarId
- Test.DejaFu.Common: instance GHC.Classes.Ord Test.DejaFu.Common.ThreadId
- Test.DejaFu.Common: instance GHC.Enum.Bounded Test.DejaFu.Common.MemType
- Test.DejaFu.Common: instance GHC.Enum.Enum Test.DejaFu.Common.MemType
- Test.DejaFu.Common: instance GHC.Exception.Exception Test.DejaFu.Common.MonadFailException
- Test.DejaFu.Common: instance GHC.Read.Read Test.DejaFu.Common.MemType
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.ActionType
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.CRefId
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.Decision
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.Failure
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.IdSource
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.Lookahead
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.MVarId
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.MemType
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.MonadFailException
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.TAction
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.TVarId
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.ThreadAction
- Test.DejaFu.Common: instance GHC.Show.Show Test.DejaFu.Common.ThreadId
- Test.DejaFu.Common: isAbort :: Failure -> Bool
- Test.DejaFu.Common: isBarrier :: ActionType -> Bool
- Test.DejaFu.Common: isBlock :: ThreadAction -> Bool
- Test.DejaFu.Common: isCommit :: ActionType -> CRefId -> Bool
- Test.DejaFu.Common: isDeadlock :: Failure -> Bool
- Test.DejaFu.Common: isIllegalSubconcurrency :: Failure -> Bool
- Test.DejaFu.Common: isInternalError :: Failure -> Bool
- Test.DejaFu.Common: isUncaughtException :: Failure -> Bool
- Test.DejaFu.Common: mvarOf :: ActionType -> Maybe MVarId
- Test.DejaFu.Common: newtype MonadFailException
- Test.DejaFu.Common: nextCRId :: String -> IdSource -> (IdSource, CRefId)
- Test.DejaFu.Common: nextMVId :: String -> IdSource -> (IdSource, MVarId)
- Test.DejaFu.Common: nextTId :: String -> IdSource -> (IdSource, ThreadId)
- Test.DejaFu.Common: nextTVId :: String -> IdSource -> (IdSource, TVarId)
- Test.DejaFu.Common: preEmpCount :: [(Decision, ThreadAction)] -> (Decision, Lookahead) -> Int
- Test.DejaFu.Common: rewind :: ThreadAction -> Maybe Lookahead
- Test.DejaFu.Common: runRefCont :: MonadRef r n => (n () -> x) -> (a -> Maybe b) -> ((a -> x) -> x) -> n (x, r (Maybe b))
- Test.DejaFu.Common: showFail :: Failure -> String
- Test.DejaFu.Common: showTrace :: Trace -> String
- Test.DejaFu.Common: simplifyAction :: ThreadAction -> ActionType
- Test.DejaFu.Common: simplifyLookahead :: Lookahead -> ActionType
- Test.DejaFu.Common: synchronises :: ActionType -> CRefId -> Bool
- Test.DejaFu.Common: threadNames :: Trace -> [(Int, String)]
- Test.DejaFu.Common: tvarsOf :: ThreadAction -> Set TVarId
- Test.DejaFu.Common: tvarsRead :: ThreadAction -> Set TVarId
- Test.DejaFu.Common: tvarsWritten :: ThreadAction -> Set TVarId
- Test.DejaFu.Common: type TTrace = [TAction]
- Test.DejaFu.Common: type Trace = [(Decision, [(ThreadId, Lookahead)], ThreadAction)]
- Test.DejaFu.Common: willRelease :: Lookahead -> Bool
- Test.DejaFu.Conc: data ThreadId
- Test.DejaFu.Conc: instance Control.Monad.Base.MonadBase GHC.Types.IO Test.DejaFu.Conc.ConcIO
- Test.DejaFu.Conc: instance Control.Monad.IO.Class.MonadIO Test.DejaFu.Conc.ConcIO
- Test.DejaFu.Conc: type ConcST t = ConcT (STRef t) (ST t)
- Test.DejaFu.SCT: instance Control.DeepSeq.NFData Test.DejaFu.SCT.Discard
- Test.DejaFu.SCT: instance GHC.Classes.Eq Test.DejaFu.SCT.Discard
- Test.DejaFu.SCT: instance GHC.Classes.Ord Test.DejaFu.SCT.Discard
- Test.DejaFu.SCT: instance GHC.Enum.Bounded Test.DejaFu.SCT.Discard
- Test.DejaFu.SCT: instance GHC.Enum.Enum Test.DejaFu.SCT.Discard
- Test.DejaFu.SCT: instance GHC.Read.Read Test.DejaFu.SCT.Discard
- Test.DejaFu.SCT: instance GHC.Show.Show Test.DejaFu.SCT.Discard
- Test.DejaFu.SCT.Internal: BacktrackStep :: ThreadId -> Decision -> ThreadAction -> Map ThreadId Lookahead -> Map ThreadId Bool -> DepState -> BacktrackStep
- Test.DejaFu.SCT.Internal: DPOR :: Set ThreadId -> Map ThreadId Bool -> Maybe (ThreadId, DPOR) -> Set ThreadId -> Map ThreadId ThreadAction -> Map ThreadId ThreadAction -> DPOR
- Test.DejaFu.SCT.Internal: DPORSchedState :: Map ThreadId ThreadAction -> [ThreadId] -> Seq ([(ThreadId, Lookahead)], [ThreadId]) -> Bool -> Bool -> DepState -> Maybe k -> DPORSchedState k
- Test.DejaFu.SCT.Internal: DepState :: Map CRefId Bool -> Map ThreadId MaskingState -> DepState
- Test.DejaFu.SCT.Internal: RandSchedState :: Map ThreadId Int -> g -> RandSchedState g
- Test.DejaFu.SCT.Internal: [bcktAction] :: BacktrackStep -> ThreadAction
- Test.DejaFu.SCT.Internal: [bcktBacktracks] :: BacktrackStep -> Map ThreadId Bool
- Test.DejaFu.SCT.Internal: [bcktDecision] :: BacktrackStep -> Decision
- Test.DejaFu.SCT.Internal: [bcktRunnable] :: BacktrackStep -> Map ThreadId Lookahead
- Test.DejaFu.SCT.Internal: [bcktState] :: BacktrackStep -> DepState
- Test.DejaFu.SCT.Internal: [bcktThreadid] :: BacktrackStep -> ThreadId
- Test.DejaFu.SCT.Internal: [depCRState] :: DepState -> Map CRefId Bool
- Test.DejaFu.SCT.Internal: [depMaskState] :: DepState -> Map ThreadId MaskingState
- Test.DejaFu.SCT.Internal: [dporDone] :: DPOR -> Set ThreadId
- Test.DejaFu.SCT.Internal: [dporNext] :: DPOR -> Maybe (ThreadId, DPOR)
- Test.DejaFu.SCT.Internal: [dporRunnable] :: DPOR -> Set ThreadId
- Test.DejaFu.SCT.Internal: [dporSleep] :: DPOR -> Map ThreadId ThreadAction
- Test.DejaFu.SCT.Internal: [dporTaken] :: DPOR -> Map ThreadId ThreadAction
- Test.DejaFu.SCT.Internal: [dporTodo] :: DPOR -> Map ThreadId Bool
- Test.DejaFu.SCT.Internal: [schedBPoints] :: DPORSchedState k -> Seq ([(ThreadId, Lookahead)], [ThreadId])
- Test.DejaFu.SCT.Internal: [schedBState] :: DPORSchedState k -> Maybe k
- Test.DejaFu.SCT.Internal: [schedBoundKill] :: DPORSchedState k -> Bool
- Test.DejaFu.SCT.Internal: [schedDepState] :: DPORSchedState k -> DepState
- Test.DejaFu.SCT.Internal: [schedGen] :: RandSchedState g -> g
- Test.DejaFu.SCT.Internal: [schedIgnore] :: DPORSchedState k -> Bool
- Test.DejaFu.SCT.Internal: [schedPrefix] :: DPORSchedState k -> [ThreadId]
- Test.DejaFu.SCT.Internal: [schedSleep] :: DPORSchedState k -> Map ThreadId ThreadAction
- Test.DejaFu.SCT.Internal: [schedWeights] :: RandSchedState g -> Map ThreadId Int
- Test.DejaFu.SCT.Internal: backtrackAt :: (ThreadId -> BacktrackStep -> Bool) -> BacktrackFunc
- Test.DejaFu.SCT.Internal: canInterrupt :: DepState -> ThreadId -> ThreadAction -> Bool
- Test.DejaFu.SCT.Internal: canInterruptL :: DepState -> ThreadId -> Lookahead -> Bool
- Test.DejaFu.SCT.Internal: data BacktrackStep
- Test.DejaFu.SCT.Internal: data DPOR
- Test.DejaFu.SCT.Internal: data DPORSchedState k
- Test.DejaFu.SCT.Internal: data DepState
- Test.DejaFu.SCT.Internal: data RandSchedState g
- Test.DejaFu.SCT.Internal: dependent :: DepState -> ThreadId -> ThreadAction -> ThreadId -> ThreadAction -> Bool
- Test.DejaFu.SCT.Internal: dependent' :: DepState -> ThreadId -> ThreadAction -> ThreadId -> Lookahead -> Bool
- Test.DejaFu.SCT.Internal: dependentActions :: DepState -> ActionType -> ActionType -> Bool
- Test.DejaFu.SCT.Internal: didYield :: ThreadAction -> Bool
- Test.DejaFu.SCT.Internal: dporSched :: IncrementalBoundFunc k -> Scheduler (DPORSchedState k)
- Test.DejaFu.SCT.Internal: findBacktrackSteps :: BacktrackFunc -> Bool -> Seq ([(ThreadId, Lookahead)], [ThreadId]) -> Trace -> [BacktrackStep]
- Test.DejaFu.SCT.Internal: findSchedulePrefix :: DPOR -> Maybe ([ThreadId], Bool, Map ThreadId ThreadAction)
- Test.DejaFu.SCT.Internal: incorporateBacktrackSteps :: [BacktrackStep] -> DPOR -> DPOR
- Test.DejaFu.SCT.Internal: incorporateTrace :: Bool -> Trace -> DPOR -> DPOR
- Test.DejaFu.SCT.Internal: initialDPORSchedState :: Map ThreadId ThreadAction -> [ThreadId] -> DPORSchedState k
- Test.DejaFu.SCT.Internal: initialDPORThread :: DPOR -> ThreadId
- Test.DejaFu.SCT.Internal: initialDepState :: DepState
- Test.DejaFu.SCT.Internal: initialRandSchedState :: Maybe (Map ThreadId Int) -> g -> RandSchedState g
- Test.DejaFu.SCT.Internal: initialState :: DPOR
- Test.DejaFu.SCT.Internal: instance Control.DeepSeq.NFData Test.DejaFu.SCT.Internal.BacktrackStep
- Test.DejaFu.SCT.Internal: instance Control.DeepSeq.NFData Test.DejaFu.SCT.Internal.DPOR
- Test.DejaFu.SCT.Internal: instance Control.DeepSeq.NFData Test.DejaFu.SCT.Internal.DepState
- Test.DejaFu.SCT.Internal: instance Control.DeepSeq.NFData g => Control.DeepSeq.NFData (Test.DejaFu.SCT.Internal.RandSchedState g)
- Test.DejaFu.SCT.Internal: instance Control.DeepSeq.NFData k => Control.DeepSeq.NFData (Test.DejaFu.SCT.Internal.DPORSchedState k)
- Test.DejaFu.SCT.Internal: instance GHC.Classes.Eq Test.DejaFu.SCT.Internal.BacktrackStep
- Test.DejaFu.SCT.Internal: instance GHC.Classes.Eq Test.DejaFu.SCT.Internal.DPOR
- Test.DejaFu.SCT.Internal: instance GHC.Classes.Eq Test.DejaFu.SCT.Internal.DepState
- Test.DejaFu.SCT.Internal: instance GHC.Classes.Eq g => GHC.Classes.Eq (Test.DejaFu.SCT.Internal.RandSchedState g)
- Test.DejaFu.SCT.Internal: instance GHC.Classes.Eq k => GHC.Classes.Eq (Test.DejaFu.SCT.Internal.DPORSchedState k)
- Test.DejaFu.SCT.Internal: instance GHC.Show.Show Test.DejaFu.SCT.Internal.BacktrackStep
- Test.DejaFu.SCT.Internal: instance GHC.Show.Show Test.DejaFu.SCT.Internal.DPOR
- Test.DejaFu.SCT.Internal: instance GHC.Show.Show Test.DejaFu.SCT.Internal.DepState
- Test.DejaFu.SCT.Internal: instance GHC.Show.Show g => GHC.Show.Show (Test.DejaFu.SCT.Internal.RandSchedState g)
- Test.DejaFu.SCT.Internal: instance GHC.Show.Show k => GHC.Show.Show (Test.DejaFu.SCT.Internal.DPORSchedState k)
- Test.DejaFu.SCT.Internal: isBuffered :: DepState -> CRefId -> Bool
- Test.DejaFu.SCT.Internal: isMaskedInterruptible :: DepState -> ThreadId -> Bool
- Test.DejaFu.SCT.Internal: isMaskedUninterruptible :: DepState -> ThreadId -> Bool
- Test.DejaFu.SCT.Internal: killsDaemons :: ThreadId -> Lookahead -> Bool
- Test.DejaFu.SCT.Internal: randSched :: RandomGen g => (g -> (Int, g)) -> Scheduler (RandSchedState g)
- Test.DejaFu.SCT.Internal: type BacktrackFunc = [BacktrackStep] -> [(Int, Bool, ThreadId)] -> [BacktrackStep]
- Test.DejaFu.SCT.Internal: type IncrementalBoundFunc k = Maybe k -> Maybe (ThreadId, ThreadAction) -> (Decision, Lookahead) -> Maybe k
- Test.DejaFu.SCT.Internal: updateCRState :: ThreadAction -> Map CRefId Bool -> Map CRefId Bool
- Test.DejaFu.SCT.Internal: updateDepState :: DepState -> ThreadId -> ThreadAction -> DepState
- Test.DejaFu.SCT.Internal: updateMaskState :: ThreadId -> ThreadAction -> Map ThreadId MaskingState -> Map ThreadId MaskingState
- Test.DejaFu.SCT.Internal: willYield :: Lookahead -> Bool
- Test.DejaFu.STM: Exception :: SomeException -> Result a
- Test.DejaFu.STM: Retry :: [TVarId] -> Result a
- Test.DejaFu.STM: Success :: [TVarId] -> [TVarId] -> a -> Result a
- Test.DejaFu.STM: TCatch :: TTrace -> (Maybe TTrace) -> TAction
- Test.DejaFu.STM: TNew :: TVarId -> TAction
- Test.DejaFu.STM: TOrElse :: TTrace -> (Maybe TTrace) -> TAction
- Test.DejaFu.STM: TRead :: TVarId -> TAction
- Test.DejaFu.STM: TRetry :: TAction
- Test.DejaFu.STM: TStop :: TAction
- Test.DejaFu.STM: TThrow :: TAction
- Test.DejaFu.STM: TWrite :: TVarId -> TAction
- Test.DejaFu.STM: data Result a
- Test.DejaFu.STM: data STMLike n r a
- Test.DejaFu.STM: data TAction
- Test.DejaFu.STM: data TVarId
- Test.DejaFu.STM: instance Control.Monad.Catch.MonadCatch (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: instance Control.Monad.Catch.MonadThrow (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: instance Control.Monad.Fail.MonadFail (Test.DejaFu.STM.STMLike r n)
- Test.DejaFu.STM: instance Control.Monad.STM.Class.MonadSTM (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: instance GHC.Base.Alternative (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: instance GHC.Base.Applicative (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: instance GHC.Base.Functor (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: instance GHC.Base.Monad (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: instance GHC.Base.MonadPlus (Test.DejaFu.STM.STMLike n r)
- Test.DejaFu.STM: runTransaction :: MonadRef r n => STMLike n r a -> IdSource -> n (Result a, IdSource, TTrace)
- Test.DejaFu.STM: type STMIO = STMLike IO IORef
- Test.DejaFu.STM: type STMST t = STMLike (ST t) (STRef t)
- Test.DejaFu.STM: type TTrace = [TAction]
- Test.DejaFu.STM.Internal: Exception :: SomeException -> Result a
- Test.DejaFu.STM.Internal: M :: ((a -> STMAction n r) -> STMAction n r) -> M n r a
- Test.DejaFu.STM.Internal: Retry :: [TVarId] -> Result a
- Test.DejaFu.STM.Internal: SCatch :: (e -> M n r a) -> (M n r a) -> (a -> STMAction n r) -> STMAction n r
- Test.DejaFu.STM.Internal: SNew :: String -> a -> (TVar r a -> STMAction n r) -> STMAction n r
- Test.DejaFu.STM.Internal: SOrElse :: (M n r a) -> (M n r a) -> (a -> STMAction n r) -> STMAction n r
- Test.DejaFu.STM.Internal: SRead :: (TVar r a) -> (a -> STMAction n r) -> STMAction n r
- Test.DejaFu.STM.Internal: SRetry :: STMAction n r
- Test.DejaFu.STM.Internal: SStop :: (n ()) -> STMAction n r
- Test.DejaFu.STM.Internal: SThrow :: e -> STMAction n r
- Test.DejaFu.STM.Internal: SWrite :: (TVar r a) -> a -> (STMAction n r) -> STMAction n r
- Test.DejaFu.STM.Internal: Success :: [TVarId] -> [TVarId] -> a -> Result a
- Test.DejaFu.STM.Internal: TVar :: (TVarId, r a) -> TVar r a
- Test.DejaFu.STM.Internal: [runM] :: M n r a -> (a -> STMAction n r) -> STMAction n r
- Test.DejaFu.STM.Internal: cont :: ((a -> STMAction n r) -> STMAction n r) -> M n r a
- Test.DejaFu.STM.Internal: data Result a
- Test.DejaFu.STM.Internal: data STMAction n r
- Test.DejaFu.STM.Internal: doTransaction :: MonadRef r n => M n r a -> IdSource -> n (Result a, n (), IdSource, TTrace)
- Test.DejaFu.STM.Internal: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Test.DejaFu.STM.Internal.Result a)
- Test.DejaFu.STM.Internal: instance Control.Monad.Fail.MonadFail (Test.DejaFu.STM.Internal.M n r)
- Test.DejaFu.STM.Internal: instance Data.Foldable.Foldable Test.DejaFu.STM.Internal.Result
- Test.DejaFu.STM.Internal: instance GHC.Base.Applicative (Test.DejaFu.STM.Internal.M n r)
- Test.DejaFu.STM.Internal: instance GHC.Base.Functor (Test.DejaFu.STM.Internal.M n r)
- Test.DejaFu.STM.Internal: instance GHC.Base.Functor Test.DejaFu.STM.Internal.Result
- Test.DejaFu.STM.Internal: instance GHC.Base.Monad (Test.DejaFu.STM.Internal.M n r)
- Test.DejaFu.STM.Internal: instance GHC.Show.Show a => GHC.Show.Show (Test.DejaFu.STM.Internal.Result a)
- Test.DejaFu.STM.Internal: isSTMSuccess :: Result a -> Bool
- Test.DejaFu.STM.Internal: newtype M n r a
- Test.DejaFu.STM.Internal: newtype TVar r a
- Test.DejaFu.STM.Internal: runCont :: M n r a -> (a -> STMAction n r) -> STMAction n r
- Test.DejaFu.STM.Internal: stepTrans :: MonadRef r n => STMAction n r -> IdSource -> n (STMAction n r, n (), IdSource, [TVarId], [TVarId], TAction)
- Test.DejaFu.Schedule: (:|) :: a -> [a] -> NonEmpty a
- Test.DejaFu.Schedule: Continue :: Decision
- Test.DejaFu.Schedule: Start :: ThreadId -> Decision
- Test.DejaFu.Schedule: SwitchTo :: ThreadId -> Decision
- Test.DejaFu.Schedule: data Decision
- Test.DejaFu.Schedule: data NonEmpty a :: * -> *
- Test.DejaFu.Schedule: decisionOf :: Foldable f => Maybe ThreadId -> f ThreadId -> ThreadId -> Decision
- Test.DejaFu.Schedule: tidOf :: ThreadId -> Decision -> ThreadId
+ Test.DejaFu: ProPredicate :: (Either Failure a -> Maybe Discard) -> ([(Either Failure a, Trace)] -> Result b) -> ProPredicate a b
+ Test.DejaFu: [pdiscard] :: ProPredicate a b -> Either Failure a -> Maybe Discard
+ Test.DejaFu: [peval] :: ProPredicate a b -> [(Either Failure a, Trace)] -> Result b
+ Test.DejaFu: alwaysNothing :: (Either Failure a -> Maybe (Either Failure b)) -> ProPredicate a b
+ Test.DejaFu: alwaysSameBy :: (Either Failure a -> Either Failure a -> Bool) -> Predicate a
+ Test.DejaFu: alwaysSameOn :: Eq b => (Either Failure a -> b) -> Predicate a
+ Test.DejaFu: data ProPredicate a b
+ Test.DejaFu: instance Data.Profunctor.Unsafe.Profunctor Test.DejaFu.ProPredicate
+ Test.DejaFu: instance GHC.Base.Functor (Test.DejaFu.ProPredicate x)
+ Test.DejaFu: somewhereNothing :: (Either Failure a -> Maybe (Either Failure b)) -> ProPredicate a b
+ Test.DejaFu.Conc: ForkOS :: ThreadId -> ThreadAction
+ Test.DejaFu.Conc: IsCurrentThreadBound :: Bool -> ThreadAction
+ Test.DejaFu.Conc: WillForkOS :: Lookahead
+ Test.DejaFu.Conc: WillIsCurrentThreadBound :: Lookahead
+ Test.DejaFu.Conc: instance Control.Monad.IO.Class.MonadIO n => Control.Monad.IO.Class.MonadIO (Test.DejaFu.Conc.ConcT r n)
+ Test.DejaFu.Conc: newtype ThreadId
+ Test.DejaFu.Conc.Internal.Common: AForkOS :: String -> ((forall b. M n r b -> M n r b) -> Action n r) -> (ThreadId -> Action n r) -> Action n r
+ Test.DejaFu.Conc.Internal.Common: AIsBound :: (Bool -> Action n r) -> Action n r
+ Test.DejaFu.Conc.Internal.STM: Exception :: SomeException -> Result a
+ Test.DejaFu.Conc.Internal.STM: Retry :: [TVarId] -> Result a
+ Test.DejaFu.Conc.Internal.STM: S :: ((a -> STMAction n r) -> STMAction n r) -> S n r a
+ Test.DejaFu.Conc.Internal.STM: SCatch :: (e -> S n r a) -> (S n r a) -> (a -> STMAction n r) -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: SNew :: String -> a -> (TVar r a -> STMAction n r) -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: SOrElse :: (S n r a) -> (S n r a) -> (a -> STMAction n r) -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: SRead :: (TVar r a) -> (a -> STMAction n r) -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: SRetry :: STMAction n r
+ Test.DejaFu.Conc.Internal.STM: SStop :: (n ()) -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: SThrow :: e -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: SWrite :: (TVar r a) -> a -> (STMAction n r) -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: Success :: [TVarId] -> [TVarId] -> a -> Result a
+ Test.DejaFu.Conc.Internal.STM: TVar :: (TVarId, r a) -> TVar r a
+ Test.DejaFu.Conc.Internal.STM: [runSTM] :: S n r a -> (a -> STMAction n r) -> STMAction n r
+ Test.DejaFu.Conc.Internal.STM: data Result a
+ Test.DejaFu.Conc.Internal.STM: data STMAction n r
+ Test.DejaFu.Conc.Internal.STM: doTransaction :: MonadRef r n => S n r a -> IdSource -> n (Result a, n (), IdSource, [TAction])
+ Test.DejaFu.Conc.Internal.STM: instance Control.Monad.Catch.MonadCatch (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance Control.Monad.Catch.MonadThrow (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance Control.Monad.Fail.MonadFail (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance Control.Monad.STM.Class.MonadSTM (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance GHC.Base.Alternative (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance GHC.Base.Applicative (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance GHC.Base.Functor (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance GHC.Base.Monad (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance GHC.Base.MonadPlus (Test.DejaFu.Conc.Internal.STM.S n r)
+ Test.DejaFu.Conc.Internal.STM: instance GHC.Show.Show a => GHC.Show.Show (Test.DejaFu.Conc.Internal.STM.Result a)
+ Test.DejaFu.Conc.Internal.STM: newtype S n r a
+ Test.DejaFu.Conc.Internal.STM: newtype TVar r a
+ Test.DejaFu.Conc.Internal.STM: runTransaction :: MonadRef r n => S n r a -> IdSource -> n (Result a, IdSource, [TAction])
+ Test.DejaFu.Conc.Internal.STM: stepTrans :: MonadRef r n => STMAction n r -> IdSource -> n (STMAction n r, n (), IdSource, [TVarId], [TVarId], TAction)
+ Test.DejaFu.Conc.Internal.Threading: BoundThread :: MVar n (n (Action n r)) -> MVar n (Action n r) -> ThreadId n -> BoundThread n r
+ Test.DejaFu.Conc.Internal.Threading: [_boundTId] :: BoundThread n r -> ThreadId n
+ Test.DejaFu.Conc.Internal.Threading: [_bound] :: Thread n r -> Maybe (BoundThread n r)
+ Test.DejaFu.Conc.Internal.Threading: [_getboundIO] :: BoundThread n r -> MVar n (Action n r)
+ Test.DejaFu.Conc.Internal.Threading: [_runboundIO] :: BoundThread n r -> MVar n (n (Action n r))
+ Test.DejaFu.Conc.Internal.Threading: data BoundThread n r
+ Test.DejaFu.Conc.Internal.Threading: makeBound :: MonadConc n => ThreadId -> Threads n r -> n (Threads n r)
+ Test.DejaFu.Conc.Internal.Threading: runLiftedAct :: MonadConc n => ThreadId -> Threads n r -> n (Action n r) -> n (Action n r)
+ Test.DejaFu.Internal: IdSource :: (Int, [String]) -> (Int, [String]) -> (Int, [String]) -> (Int, [String]) -> IdSource
+ Test.DejaFu.Internal: PartiallySynchronisedCommit :: CRefId -> ActionType
+ Test.DejaFu.Internal: PartiallySynchronisedModify :: CRefId -> ActionType
+ Test.DejaFu.Internal: PartiallySynchronisedWrite :: CRefId -> ActionType
+ Test.DejaFu.Internal: SynchronisedModify :: CRefId -> ActionType
+ Test.DejaFu.Internal: SynchronisedOther :: ActionType
+ Test.DejaFu.Internal: SynchronisedRead :: MVarId -> ActionType
+ Test.DejaFu.Internal: SynchronisedWrite :: MVarId -> ActionType
+ Test.DejaFu.Internal: UnsynchronisedOther :: ActionType
+ Test.DejaFu.Internal: UnsynchronisedRead :: CRefId -> ActionType
+ Test.DejaFu.Internal: UnsynchronisedWrite :: CRefId -> ActionType
+ Test.DejaFu.Internal: [_crids] :: IdSource -> (Int, [String])
+ Test.DejaFu.Internal: [_mvids] :: IdSource -> (Int, [String])
+ Test.DejaFu.Internal: [_tids] :: IdSource -> (Int, [String])
+ Test.DejaFu.Internal: [_tvids] :: IdSource -> (Int, [String])
+ Test.DejaFu.Internal: crefOf :: ActionType -> Maybe CRefId
+ Test.DejaFu.Internal: data ActionType
+ Test.DejaFu.Internal: data IdSource
+ Test.DejaFu.Internal: efromJust :: String -> Maybe a -> a
+ Test.DejaFu.Internal: efromList :: String -> [a] -> NonEmpty a
+ Test.DejaFu.Internal: ehead :: String -> [a] -> a
+ Test.DejaFu.Internal: eidx :: String -> [a] -> Int -> a
+ Test.DejaFu.Internal: etail :: String -> [a] -> [a]
+ Test.DejaFu.Internal: fatal :: String -> String -> a
+ Test.DejaFu.Internal: initialIdSource :: IdSource
+ Test.DejaFu.Internal: instance Control.DeepSeq.NFData Test.DejaFu.Internal.ActionType
+ Test.DejaFu.Internal: instance Control.DeepSeq.NFData Test.DejaFu.Internal.IdSource
+ Test.DejaFu.Internal: instance GHC.Classes.Eq Test.DejaFu.Internal.ActionType
+ Test.DejaFu.Internal: instance GHC.Classes.Eq Test.DejaFu.Internal.IdSource
+ Test.DejaFu.Internal: instance GHC.Classes.Ord Test.DejaFu.Internal.IdSource
+ Test.DejaFu.Internal: instance GHC.Show.Show Test.DejaFu.Internal.ActionType
+ Test.DejaFu.Internal: instance GHC.Show.Show Test.DejaFu.Internal.IdSource
+ Test.DejaFu.Internal: isBarrier :: ActionType -> Bool
+ Test.DejaFu.Internal: isBlock :: ThreadAction -> Bool
+ Test.DejaFu.Internal: isCommit :: ActionType -> CRefId -> Bool
+ Test.DejaFu.Internal: mvarOf :: ActionType -> Maybe MVarId
+ Test.DejaFu.Internal: nextCRId :: String -> IdSource -> (IdSource, CRefId)
+ Test.DejaFu.Internal: nextId :: String -> (Int, [String]) -> (Id, (Int, [String]))
+ Test.DejaFu.Internal: nextMVId :: String -> IdSource -> (IdSource, MVarId)
+ Test.DejaFu.Internal: nextTId :: String -> IdSource -> (IdSource, ThreadId)
+ Test.DejaFu.Internal: nextTVId :: String -> IdSource -> (IdSource, TVarId)
+ Test.DejaFu.Internal: rewind :: ThreadAction -> Maybe Lookahead
+ Test.DejaFu.Internal: runRefCont :: MonadRef r n => (n () -> x) -> (a -> Maybe b) -> ((a -> x) -> x) -> n (x, r (Maybe b))
+ Test.DejaFu.Internal: simplifyAction :: ThreadAction -> ActionType
+ Test.DejaFu.Internal: simplifyLookahead :: Lookahead -> ActionType
+ Test.DejaFu.Internal: synchronises :: ActionType -> CRefId -> Bool
+ Test.DejaFu.Internal: tvarsOf :: ThreadAction -> Set TVarId
+ Test.DejaFu.Internal: tvarsRead :: ThreadAction -> Set TVarId
+ Test.DejaFu.Internal: tvarsWritten :: ThreadAction -> Set TVarId
+ Test.DejaFu.Internal: willRelease :: Lookahead -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: BacktrackStep :: ThreadId -> Decision -> ThreadAction -> Map ThreadId Lookahead -> Map ThreadId Bool -> DepState -> BacktrackStep
+ Test.DejaFu.SCT.Internal.DPOR: DPOR :: Set ThreadId -> Map ThreadId Bool -> Maybe (ThreadId, DPOR) -> Set ThreadId -> Map ThreadId ThreadAction -> Map ThreadId ThreadAction -> DPOR
+ Test.DejaFu.SCT.Internal.DPOR: DPORSchedState :: Map ThreadId ThreadAction -> [ThreadId] -> Seq ([(ThreadId, Lookahead)], [ThreadId]) -> Bool -> Bool -> DepState -> Maybe k -> DPORSchedState k
+ Test.DejaFu.SCT.Internal.DPOR: DepState :: Map CRefId Bool -> Set MVarId -> Map ThreadId MaskingState -> DepState
+ Test.DejaFu.SCT.Internal.DPOR: [bcktAction] :: BacktrackStep -> ThreadAction
+ Test.DejaFu.SCT.Internal.DPOR: [bcktBacktracks] :: BacktrackStep -> Map ThreadId Bool
+ Test.DejaFu.SCT.Internal.DPOR: [bcktDecision] :: BacktrackStep -> Decision
+ Test.DejaFu.SCT.Internal.DPOR: [bcktRunnable] :: BacktrackStep -> Map ThreadId Lookahead
+ Test.DejaFu.SCT.Internal.DPOR: [bcktState] :: BacktrackStep -> DepState
+ Test.DejaFu.SCT.Internal.DPOR: [bcktThreadid] :: BacktrackStep -> ThreadId
+ Test.DejaFu.SCT.Internal.DPOR: [depCRState] :: DepState -> Map CRefId Bool
+ Test.DejaFu.SCT.Internal.DPOR: [depMVState] :: DepState -> Set MVarId
+ Test.DejaFu.SCT.Internal.DPOR: [depMaskState] :: DepState -> Map ThreadId MaskingState
+ Test.DejaFu.SCT.Internal.DPOR: [dporDone] :: DPOR -> Set ThreadId
+ Test.DejaFu.SCT.Internal.DPOR: [dporNext] :: DPOR -> Maybe (ThreadId, DPOR)
+ Test.DejaFu.SCT.Internal.DPOR: [dporRunnable] :: DPOR -> Set ThreadId
+ Test.DejaFu.SCT.Internal.DPOR: [dporSleep] :: DPOR -> Map ThreadId ThreadAction
+ Test.DejaFu.SCT.Internal.DPOR: [dporTaken] :: DPOR -> Map ThreadId ThreadAction
+ Test.DejaFu.SCT.Internal.DPOR: [dporTodo] :: DPOR -> Map ThreadId Bool
+ Test.DejaFu.SCT.Internal.DPOR: [schedBPoints] :: DPORSchedState k -> Seq ([(ThreadId, Lookahead)], [ThreadId])
+ Test.DejaFu.SCT.Internal.DPOR: [schedBState] :: DPORSchedState k -> Maybe k
+ Test.DejaFu.SCT.Internal.DPOR: [schedBoundKill] :: DPORSchedState k -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: [schedDepState] :: DPORSchedState k -> DepState
+ Test.DejaFu.SCT.Internal.DPOR: [schedIgnore] :: DPORSchedState k -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: [schedPrefix] :: DPORSchedState k -> [ThreadId]
+ Test.DejaFu.SCT.Internal.DPOR: [schedSleep] :: DPORSchedState k -> Map ThreadId ThreadAction
+ Test.DejaFu.SCT.Internal.DPOR: backtrackAt :: (ThreadId -> BacktrackStep -> Bool) -> BacktrackFunc
+ Test.DejaFu.SCT.Internal.DPOR: canInterrupt :: DepState -> ThreadId -> ThreadAction -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: canInterruptL :: DepState -> ThreadId -> Lookahead -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: data BacktrackStep
+ Test.DejaFu.SCT.Internal.DPOR: data DPOR
+ Test.DejaFu.SCT.Internal.DPOR: data DPORSchedState k
+ Test.DejaFu.SCT.Internal.DPOR: data DepState
+ Test.DejaFu.SCT.Internal.DPOR: dependent :: DepState -> ThreadId -> ThreadAction -> ThreadId -> ThreadAction -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: dependent' :: DepState -> ThreadId -> ThreadAction -> ThreadId -> Lookahead -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: dependentActions :: DepState -> ActionType -> ActionType -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: didYield :: ThreadAction -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: dporSched :: IncrementalBoundFunc k -> Scheduler (DPORSchedState k)
+ Test.DejaFu.SCT.Internal.DPOR: findBacktrackSteps :: BacktrackFunc -> Bool -> Seq ([(ThreadId, Lookahead)], [ThreadId]) -> Trace -> [BacktrackStep]
+ Test.DejaFu.SCT.Internal.DPOR: findSchedulePrefix :: DPOR -> Maybe ([ThreadId], Bool, Map ThreadId ThreadAction)
+ Test.DejaFu.SCT.Internal.DPOR: incorporateBacktrackSteps :: [BacktrackStep] -> DPOR -> DPOR
+ Test.DejaFu.SCT.Internal.DPOR: incorporateTrace :: Bool -> Trace -> DPOR -> DPOR
+ Test.DejaFu.SCT.Internal.DPOR: initialDPORSchedState :: Map ThreadId ThreadAction -> [ThreadId] -> DPORSchedState k
+ Test.DejaFu.SCT.Internal.DPOR: initialDPORThread :: DPOR -> ThreadId
+ Test.DejaFu.SCT.Internal.DPOR: initialDepState :: DepState
+ Test.DejaFu.SCT.Internal.DPOR: initialState :: DPOR
+ Test.DejaFu.SCT.Internal.DPOR: instance Control.DeepSeq.NFData Test.DejaFu.SCT.Internal.DPOR.BacktrackStep
+ Test.DejaFu.SCT.Internal.DPOR: instance Control.DeepSeq.NFData Test.DejaFu.SCT.Internal.DPOR.DPOR
+ Test.DejaFu.SCT.Internal.DPOR: instance Control.DeepSeq.NFData Test.DejaFu.SCT.Internal.DPOR.DepState
+ Test.DejaFu.SCT.Internal.DPOR: instance Control.DeepSeq.NFData k => Control.DeepSeq.NFData (Test.DejaFu.SCT.Internal.DPOR.DPORSchedState k)
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Classes.Eq Test.DejaFu.SCT.Internal.DPOR.BacktrackStep
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Classes.Eq Test.DejaFu.SCT.Internal.DPOR.DPOR
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Classes.Eq Test.DejaFu.SCT.Internal.DPOR.DepState
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Classes.Eq k => GHC.Classes.Eq (Test.DejaFu.SCT.Internal.DPOR.DPORSchedState k)
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Show.Show Test.DejaFu.SCT.Internal.DPOR.BacktrackStep
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Show.Show Test.DejaFu.SCT.Internal.DPOR.DPOR
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Show.Show Test.DejaFu.SCT.Internal.DPOR.DepState
+ Test.DejaFu.SCT.Internal.DPOR: instance GHC.Show.Show k => GHC.Show.Show (Test.DejaFu.SCT.Internal.DPOR.DPORSchedState k)
+ Test.DejaFu.SCT.Internal.DPOR: isBuffered :: DepState -> CRefId -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: isFull :: DepState -> MVarId -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: isMaskedInterruptible :: DepState -> ThreadId -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: isMaskedUninterruptible :: DepState -> ThreadId -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: killsDaemons :: ThreadId -> Lookahead -> Bool
+ Test.DejaFu.SCT.Internal.DPOR: type BacktrackFunc = [BacktrackStep] -> [(Int, Bool, ThreadId)] -> [BacktrackStep]
+ Test.DejaFu.SCT.Internal.DPOR: type IncrementalBoundFunc k = Maybe k -> Maybe (ThreadId, ThreadAction) -> (Decision, Lookahead) -> Maybe k
+ Test.DejaFu.SCT.Internal.DPOR: updateCRState :: ThreadAction -> Map CRefId Bool -> Map CRefId Bool
+ Test.DejaFu.SCT.Internal.DPOR: updateDepState :: DepState -> ThreadId -> ThreadAction -> DepState
+ Test.DejaFu.SCT.Internal.DPOR: updateMVState :: ThreadAction -> Set MVarId -> Set MVarId
+ Test.DejaFu.SCT.Internal.DPOR: updateMaskState :: ThreadId -> ThreadAction -> Map ThreadId MaskingState -> Map ThreadId MaskingState
+ Test.DejaFu.SCT.Internal.DPOR: willYield :: Lookahead -> Bool
+ Test.DejaFu.SCT.Internal.Weighted: RandSchedState :: Map ThreadId Int -> g -> RandSchedState g
+ Test.DejaFu.SCT.Internal.Weighted: [schedGen] :: RandSchedState g -> g
+ Test.DejaFu.SCT.Internal.Weighted: [schedWeights] :: RandSchedState g -> Map ThreadId Int
+ Test.DejaFu.SCT.Internal.Weighted: data RandSchedState g
+ Test.DejaFu.SCT.Internal.Weighted: initialRandSchedState :: Maybe (Map ThreadId Int) -> g -> RandSchedState g
+ Test.DejaFu.SCT.Internal.Weighted: instance Control.DeepSeq.NFData g => Control.DeepSeq.NFData (Test.DejaFu.SCT.Internal.Weighted.RandSchedState g)
+ Test.DejaFu.SCT.Internal.Weighted: instance GHC.Classes.Eq g => GHC.Classes.Eq (Test.DejaFu.SCT.Internal.Weighted.RandSchedState g)
+ Test.DejaFu.SCT.Internal.Weighted: instance GHC.Show.Show g => GHC.Show.Show (Test.DejaFu.SCT.Internal.Weighted.RandSchedState g)
+ Test.DejaFu.SCT.Internal.Weighted: randSched :: RandomGen g => (g -> (Int, g)) -> Scheduler (RandSchedState g)
+ Test.DejaFu.Types: Abort :: Failure
+ Test.DejaFu.Types: BlockedPutMVar :: MVarId -> ThreadAction
+ Test.DejaFu.Types: BlockedReadMVar :: MVarId -> ThreadAction
+ Test.DejaFu.Types: BlockedSTM :: [TAction] -> ThreadAction
+ Test.DejaFu.Types: BlockedTakeMVar :: MVarId -> ThreadAction
+ Test.DejaFu.Types: BlockedThrowTo :: ThreadId -> ThreadAction
+ Test.DejaFu.Types: CRefId :: Id -> CRefId
+ Test.DejaFu.Types: CasCRef :: CRefId -> Bool -> ThreadAction
+ Test.DejaFu.Types: Catching :: ThreadAction
+ Test.DejaFu.Types: CommitCRef :: ThreadId -> CRefId -> ThreadAction
+ Test.DejaFu.Types: Continue :: Decision
+ Test.DejaFu.Types: Deadlock :: Failure
+ Test.DejaFu.Types: DiscardResultAndTrace :: Discard
+ Test.DejaFu.Types: DiscardTrace :: Discard
+ Test.DejaFu.Types: Fork :: ThreadId -> ThreadAction
+ Test.DejaFu.Types: ForkOS :: ThreadId -> ThreadAction
+ Test.DejaFu.Types: GetNumCapabilities :: Int -> ThreadAction
+ Test.DejaFu.Types: Id :: (Maybe String) -> {-# UNPACK #-} !Int -> Id
+ Test.DejaFu.Types: IllegalSubconcurrency :: Failure
+ Test.DejaFu.Types: InternalError :: Failure
+ Test.DejaFu.Types: IsCurrentThreadBound :: Bool -> ThreadAction
+ Test.DejaFu.Types: Killed :: ThreadAction
+ Test.DejaFu.Types: LiftIO :: ThreadAction
+ Test.DejaFu.Types: MVarId :: Id -> MVarId
+ Test.DejaFu.Types: ModCRef :: CRefId -> ThreadAction
+ Test.DejaFu.Types: ModCRefCas :: CRefId -> ThreadAction
+ Test.DejaFu.Types: MonadFailException :: String -> MonadFailException
+ Test.DejaFu.Types: MyThreadId :: ThreadAction
+ Test.DejaFu.Types: NewCRef :: CRefId -> ThreadAction
+ Test.DejaFu.Types: NewMVar :: MVarId -> ThreadAction
+ Test.DejaFu.Types: PartialStoreOrder :: MemType
+ Test.DejaFu.Types: PopCatching :: ThreadAction
+ Test.DejaFu.Types: PutMVar :: MVarId -> [ThreadId] -> ThreadAction
+ Test.DejaFu.Types: ReadCRef :: CRefId -> ThreadAction
+ Test.DejaFu.Types: ReadCRefCas :: CRefId -> ThreadAction
+ Test.DejaFu.Types: ReadMVar :: MVarId -> ThreadAction
+ Test.DejaFu.Types: ResetMasking :: Bool -> MaskingState -> ThreadAction
+ Test.DejaFu.Types: Return :: ThreadAction
+ Test.DejaFu.Types: STM :: [TAction] -> [ThreadId] -> ThreadAction
+ Test.DejaFu.Types: STMDeadlock :: Failure
+ Test.DejaFu.Types: SequentialConsistency :: MemType
+ Test.DejaFu.Types: SetMasking :: Bool -> MaskingState -> ThreadAction
+ Test.DejaFu.Types: SetNumCapabilities :: Int -> ThreadAction
+ Test.DejaFu.Types: Start :: ThreadId -> Decision
+ Test.DejaFu.Types: Stop :: ThreadAction
+ Test.DejaFu.Types: StopSubconcurrency :: ThreadAction
+ Test.DejaFu.Types: Subconcurrency :: ThreadAction
+ Test.DejaFu.Types: SwitchTo :: ThreadId -> Decision
+ Test.DejaFu.Types: TCatch :: [TAction] -> (Maybe [TAction]) -> TAction
+ Test.DejaFu.Types: TNew :: TVarId -> TAction
+ Test.DejaFu.Types: TOrElse :: [TAction] -> (Maybe [TAction]) -> TAction
+ Test.DejaFu.Types: TRead :: TVarId -> TAction
+ Test.DejaFu.Types: TRetry :: TAction
+ Test.DejaFu.Types: TStop :: TAction
+ Test.DejaFu.Types: TThrow :: TAction
+ Test.DejaFu.Types: TVarId :: Id -> TVarId
+ Test.DejaFu.Types: TWrite :: TVarId -> TAction
+ Test.DejaFu.Types: TakeMVar :: MVarId -> [ThreadId] -> ThreadAction
+ Test.DejaFu.Types: ThreadDelay :: Int -> ThreadAction
+ Test.DejaFu.Types: ThreadId :: Id -> ThreadId
+ Test.DejaFu.Types: Throw :: ThreadAction
+ Test.DejaFu.Types: ThrowTo :: ThreadId -> ThreadAction
+ Test.DejaFu.Types: TotalStoreOrder :: MemType
+ Test.DejaFu.Types: TryPutMVar :: MVarId -> Bool -> [ThreadId] -> ThreadAction
+ Test.DejaFu.Types: TryReadMVar :: MVarId -> Bool -> ThreadAction
+ Test.DejaFu.Types: TryTakeMVar :: MVarId -> Bool -> [ThreadId] -> ThreadAction
+ Test.DejaFu.Types: UncaughtException :: SomeException -> Failure
+ Test.DejaFu.Types: WillCasCRef :: CRefId -> Lookahead
+ Test.DejaFu.Types: WillCatching :: Lookahead
+ Test.DejaFu.Types: WillCommitCRef :: ThreadId -> CRefId -> Lookahead
+ Test.DejaFu.Types: WillFork :: Lookahead
+ Test.DejaFu.Types: WillForkOS :: Lookahead
+ Test.DejaFu.Types: WillGetNumCapabilities :: Lookahead
+ Test.DejaFu.Types: WillIsCurrentThreadBound :: Lookahead
+ Test.DejaFu.Types: WillLiftIO :: Lookahead
+ Test.DejaFu.Types: WillModCRef :: CRefId -> Lookahead
+ Test.DejaFu.Types: WillModCRefCas :: CRefId -> Lookahead
+ Test.DejaFu.Types: WillMyThreadId :: Lookahead
+ Test.DejaFu.Types: WillNewCRef :: Lookahead
+ Test.DejaFu.Types: WillNewMVar :: Lookahead
+ Test.DejaFu.Types: WillPopCatching :: Lookahead
+ Test.DejaFu.Types: WillPutMVar :: MVarId -> Lookahead
+ Test.DejaFu.Types: WillReadCRef :: CRefId -> Lookahead
+ Test.DejaFu.Types: WillReadCRefCas :: CRefId -> Lookahead
+ Test.DejaFu.Types: WillReadMVar :: MVarId -> Lookahead
+ Test.DejaFu.Types: WillResetMasking :: Bool -> MaskingState -> Lookahead
+ Test.DejaFu.Types: WillReturn :: Lookahead
+ Test.DejaFu.Types: WillSTM :: Lookahead
+ Test.DejaFu.Types: WillSetMasking :: Bool -> MaskingState -> Lookahead
+ Test.DejaFu.Types: WillSetNumCapabilities :: Int -> Lookahead
+ Test.DejaFu.Types: WillStop :: Lookahead
+ Test.DejaFu.Types: WillStopSubconcurrency :: Lookahead
+ Test.DejaFu.Types: WillSubconcurrency :: Lookahead
+ Test.DejaFu.Types: WillTakeMVar :: MVarId -> Lookahead
+ Test.DejaFu.Types: WillThreadDelay :: Int -> Lookahead
+ Test.DejaFu.Types: WillThrow :: Lookahead
+ Test.DejaFu.Types: WillThrowTo :: ThreadId -> Lookahead
+ Test.DejaFu.Types: WillTryPutMVar :: MVarId -> Lookahead
+ Test.DejaFu.Types: WillTryReadMVar :: MVarId -> Lookahead
+ Test.DejaFu.Types: WillTryTakeMVar :: MVarId -> Lookahead
+ Test.DejaFu.Types: WillWriteCRef :: CRefId -> Lookahead
+ Test.DejaFu.Types: WillYield :: Lookahead
+ Test.DejaFu.Types: WriteCRef :: CRefId -> ThreadAction
+ Test.DejaFu.Types: Yield :: ThreadAction
+ Test.DejaFu.Types: data Decision
+ Test.DejaFu.Types: data Discard
+ Test.DejaFu.Types: data Failure
+ Test.DejaFu.Types: data Id
+ Test.DejaFu.Types: data Lookahead
+ Test.DejaFu.Types: data MemType
+ Test.DejaFu.Types: data TAction
+ Test.DejaFu.Types: data ThreadAction
+ Test.DejaFu.Types: initialThread :: ThreadId
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.CRefId
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.Decision
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.Discard
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.Failure
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.Id
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.Lookahead
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.MVarId
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.MemType
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.TAction
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.TVarId
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.ThreadAction
+ Test.DejaFu.Types: instance Control.DeepSeq.NFData Test.DejaFu.Types.ThreadId
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.CRefId
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.Decision
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.Discard
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.Failure
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.Id
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.Lookahead
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.MVarId
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.MemType
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.TAction
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.TVarId
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.ThreadAction
+ Test.DejaFu.Types: instance GHC.Classes.Eq Test.DejaFu.Types.ThreadId
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.CRefId
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.Discard
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.Failure
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.Id
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.MVarId
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.MemType
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.TVarId
+ Test.DejaFu.Types: instance GHC.Classes.Ord Test.DejaFu.Types.ThreadId
+ Test.DejaFu.Types: instance GHC.Enum.Bounded Test.DejaFu.Types.Discard
+ Test.DejaFu.Types: instance GHC.Enum.Bounded Test.DejaFu.Types.MemType
+ Test.DejaFu.Types: instance GHC.Enum.Enum Test.DejaFu.Types.Discard
+ Test.DejaFu.Types: instance GHC.Enum.Enum Test.DejaFu.Types.MemType
+ Test.DejaFu.Types: instance GHC.Exception.Exception Test.DejaFu.Types.MonadFailException
+ Test.DejaFu.Types: instance GHC.Read.Read Test.DejaFu.Types.Discard
+ Test.DejaFu.Types: instance GHC.Read.Read Test.DejaFu.Types.MemType
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.CRefId
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.Decision
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.Discard
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.Failure
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.Id
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.Lookahead
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.MVarId
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.MemType
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.MonadFailException
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.TAction
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.TVarId
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.ThreadAction
+ Test.DejaFu.Types: instance GHC.Show.Show Test.DejaFu.Types.ThreadId
+ Test.DejaFu.Types: isAbort :: Failure -> Bool
+ Test.DejaFu.Types: isDeadlock :: Failure -> Bool
+ Test.DejaFu.Types: isIllegalSubconcurrency :: Failure -> Bool
+ Test.DejaFu.Types: isInternalError :: Failure -> Bool
+ Test.DejaFu.Types: isUncaughtException :: Failure -> Bool
+ Test.DejaFu.Types: newtype CRefId
+ Test.DejaFu.Types: newtype MVarId
+ Test.DejaFu.Types: newtype MonadFailException
+ Test.DejaFu.Types: newtype TVarId
+ Test.DejaFu.Types: newtype ThreadId
+ Test.DejaFu.Types: strengthenDiscard :: (Either Failure a -> Maybe Discard) -> (Either Failure a -> Maybe Discard) -> Either Failure a -> Maybe Discard
+ Test.DejaFu.Types: type Trace = [(Decision, [(ThreadId, Lookahead)], ThreadAction)]
+ Test.DejaFu.Types: weakenDiscard :: (Either Failure a -> Maybe Discard) -> (Either Failure a -> Maybe Discard) -> Either Failure a -> Maybe Discard
+ Test.DejaFu.Utils: decisionOf :: Foldable f => Maybe ThreadId -> f ThreadId -> ThreadId -> Decision
+ Test.DejaFu.Utils: showFail :: Failure -> String
+ Test.DejaFu.Utils: showTrace :: Trace -> String
+ Test.DejaFu.Utils: simplestsBy :: (x -> x -> Bool) -> [(x, Trace)] -> [(x, Trace)]
+ Test.DejaFu.Utils: threadNames :: Trace -> [(Int, String)]
+ Test.DejaFu.Utils: tidOf :: ThreadId -> Decision -> ThreadId
- Test.DejaFu: Result :: Bool -> Int -> [(Either Failure a, Trace)] -> String -> Result a
+ Test.DejaFu: Result :: Bool -> [(Either Failure a, Trace)] -> String -> Result a
- Test.DejaFu: autocheck :: (Eq a, Show a) => (forall t. ConcST t a) -> IO Bool
+ Test.DejaFu: autocheck :: (MonadConc n, MonadIO n, MonadRef r n, Eq a, Show a) => ConcT r n a -> n Bool
- Test.DejaFu: autocheckWay :: (Eq a, Show a) => Way -> MemType -> (forall t. ConcST t a) -> IO Bool
+ Test.DejaFu: autocheckWay :: (MonadConc n, MonadIO n, MonadRef r n, Eq a, Show a) => Way -> MemType -> ConcT r n a -> n Bool
- Test.DejaFu: dejafu :: Show a => (forall t. ConcST t a) -> (String, Predicate a) -> IO Bool
+ Test.DejaFu: dejafu :: (MonadConc n, MonadIO n, MonadRef r n, Show b) => String -> ProPredicate a b -> ConcT r n a -> n Bool
- Test.DejaFu: dejafuDiscard :: Show a => (Either Failure a -> Maybe Discard) -> Way -> MemType -> (forall t. ConcST t a) -> (String, Predicate a) -> IO Bool
+ Test.DejaFu: dejafuDiscard :: (MonadConc n, MonadIO n, MonadRef r n, Show b) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> String -> ProPredicate a b -> ConcT r n a -> n Bool
- Test.DejaFu: dejafuWay :: Show a => Way -> MemType -> (forall t. ConcST t a) -> (String, Predicate a) -> IO Bool
+ Test.DejaFu: dejafuWay :: (MonadConc n, MonadIO n, MonadRef r n, Show b) => Way -> MemType -> String -> ProPredicate a b -> ConcT r n a -> n Bool
- Test.DejaFu: dejafus :: Show a => (forall t. ConcST t a) -> [(String, Predicate a)] -> IO Bool
+ Test.DejaFu: dejafus :: (MonadConc n, MonadIO n, MonadRef r n, Show b) => [(String, ProPredicate a b)] -> ConcT r n a -> n Bool
- Test.DejaFu: dejafusWay :: Show a => Way -> MemType -> (forall t. ConcST t a) -> [(String, Predicate a)] -> IO Bool
+ Test.DejaFu: dejafusWay :: (MonadConc n, MonadIO n, MonadRef r n, Show b) => Way -> MemType -> [(String, ProPredicate a b)] -> ConcT r n a -> n Bool
- Test.DejaFu: representative :: Eq a => Predicate a -> Predicate a
+ Test.DejaFu: representative :: Eq b => ProPredicate a b -> ProPredicate a b
- Test.DejaFu: runTest :: Predicate a -> (forall t. ConcST t a) -> Result a
+ Test.DejaFu: runTest :: (MonadConc n, MonadRef r n) => ProPredicate a b -> ConcT r n a -> n (Result b)
- Test.DejaFu: runTestWay :: Way -> MemType -> Predicate a -> (forall t. ConcST t a) -> Result a
+ Test.DejaFu: runTestWay :: (MonadConc n, MonadRef r n) => Way -> MemType -> ProPredicate a b -> ConcT r n a -> n (Result b)
- Test.DejaFu: type Predicate a = [(Either Failure a, Trace)] -> Result a
+ Test.DejaFu: type Predicate a = ProPredicate a a
- Test.DejaFu.Conc: BlockedSTM :: TTrace -> ThreadAction
+ Test.DejaFu.Conc: BlockedSTM :: [TAction] -> ThreadAction
- Test.DejaFu.Conc: STM :: TTrace -> [ThreadId] -> ThreadAction
+ Test.DejaFu.Conc: STM :: [TAction] -> [ThreadId] -> ThreadAction
- Test.DejaFu.Conc: ThreadId :: (Maybe String) -> {-# UNPACK #-} !Int -> ThreadId
+ Test.DejaFu.Conc: ThreadId :: Id -> ThreadId
- Test.DejaFu.Conc: runConcurrent :: MonadRef r n => Scheduler s -> MemType -> s -> ConcT r n a -> n (Either Failure a, s, Trace)
+ Test.DejaFu.Conc: runConcurrent :: (MonadConc n, MonadRef r n) => Scheduler s -> MemType -> s -> ConcT r n a -> n (Either Failure a, s, Trace)
- Test.DejaFu.Conc.Internal: runConcurrency :: MonadRef r n => Scheduler g -> MemType -> g -> IdSource -> Int -> M n r a -> n (Either Failure a, Context n r g, SeqTrace, Maybe (ThreadId, ThreadAction))
+ Test.DejaFu.Conc.Internal: runConcurrency :: (MonadConc n, MonadRef r n) => Scheduler g -> MemType -> g -> IdSource -> Int -> M n r a -> n (Either Failure a, Context n r g, SeqTrace, Maybe (ThreadId, ThreadAction))
- Test.DejaFu.Conc.Internal: runThreads :: MonadRef r n => Scheduler g -> MemType -> r (Maybe (Either Failure a)) -> Context n r g -> n (Context n r g, SeqTrace, Maybe (ThreadId, ThreadAction))
+ Test.DejaFu.Conc.Internal: runThreads :: (MonadConc n, MonadRef r n) => Scheduler g -> MemType -> r (Maybe (Either Failure a)) -> Context n r g -> n (Context n r g, SeqTrace, Maybe (ThreadId, ThreadAction))
- Test.DejaFu.Conc.Internal: stepThread :: forall n r g. MonadRef r n => Scheduler g -> MemType -> ThreadId -> Action n r -> Context n r g -> n (Either Failure (Context n r g), Act)
+ Test.DejaFu.Conc.Internal: stepThread :: forall n r g. (MonadConc n, MonadRef r n) => Scheduler g -> MemType -> ThreadId -> Action n r -> Context n r g -> n (Either Failure (Context n r g), Act)
- Test.DejaFu.Conc.Internal.Common: AAtom :: (STMLike n r a) -> (a -> Action n r) -> Action n r
+ Test.DejaFu.Conc.Internal.Common: AAtom :: (S n r a) -> (a -> Action n r) -> Action n r
- Test.DejaFu.Conc.Internal.Threading: Thread :: Action n r -> Maybe BlockedOn -> [Handler n r] -> MaskingState -> Thread n r
+ Test.DejaFu.Conc.Internal.Threading: Thread :: Action n r -> Maybe BlockedOn -> [Handler n r] -> MaskingState -> Maybe (BoundThread n r) -> Thread n r
- Test.DejaFu.Conc.Internal.Threading: kill :: ThreadId -> Threads n r -> Threads n r
+ Test.DejaFu.Conc.Internal.Threading: kill :: MonadConc n => ThreadId -> Threads n r -> n (Threads n r)
- Test.DejaFu.Refinement: class Testable a where type O a :: * type X a :: * where {
+ Test.DejaFu.Refinement: class Testable a where {
- Test.DejaFu.SCT: resultsSet :: (MonadRef r n, Ord a) => Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
+ Test.DejaFu.SCT: resultsSet :: (MonadConc n, MonadRef r n, Ord a) => Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
- Test.DejaFu.SCT: resultsSet' :: (MonadRef r n, Ord a, NFData a) => Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
+ Test.DejaFu.SCT: resultsSet' :: (MonadConc n, MonadRef r n, Ord a, NFData a) => Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
- Test.DejaFu.SCT: resultsSetDiscard :: (MonadRef r n, Ord a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
+ Test.DejaFu.SCT: resultsSetDiscard :: (MonadConc n, MonadRef r n, Ord a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
- Test.DejaFu.SCT: resultsSetDiscard' :: (MonadRef r n, Ord a, NFData a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
+ Test.DejaFu.SCT: resultsSetDiscard' :: (MonadConc n, MonadRef r n, Ord a, NFData a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n (Set (Either Failure a))
- Test.DejaFu.SCT: runSCT :: MonadRef r n => Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: runSCT :: (MonadConc n, MonadRef r n) => Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: runSCT' :: (MonadRef r n, NFData a) => Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: runSCT' :: (MonadConc n, MonadRef r n, NFData a) => Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: runSCTDiscard :: MonadRef r n => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: runSCTDiscard :: (MonadConc n, MonadRef r n) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: runSCTDiscard' :: (MonadRef r n, NFData a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: runSCTDiscard' :: (MonadConc n, MonadRef r n, NFData a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: sctBound :: MonadRef r n => MemType -> Bounds -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: sctBound :: (MonadConc n, MonadRef r n) => MemType -> Bounds -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: sctBoundDiscard :: MonadRef r n => (Either Failure a -> Maybe Discard) -> MemType -> Bounds -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: sctBoundDiscard :: (MonadConc n, MonadRef r n) => (Either Failure a -> Maybe Discard) -> MemType -> Bounds -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: sctUniformRandom :: (MonadRef r n, RandomGen g) => MemType -> g -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: sctUniformRandom :: (MonadConc n, MonadRef r n, RandomGen g) => MemType -> g -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: sctUniformRandomDiscard :: (MonadRef r n, RandomGen g) => (Either Failure a -> Maybe Discard) -> MemType -> g -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: sctUniformRandomDiscard :: (MonadConc n, MonadRef r n, RandomGen g) => (Either Failure a -> Maybe Discard) -> MemType -> g -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: sctWeightedRandom :: (MonadRef r n, RandomGen g) => MemType -> g -> Int -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: sctWeightedRandom :: (MonadConc n, MonadRef r n, RandomGen g) => MemType -> g -> Int -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
- Test.DejaFu.SCT: sctWeightedRandomDiscard :: (MonadRef r n, RandomGen g) => (Either Failure a -> Maybe Discard) -> MemType -> g -> Int -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT: sctWeightedRandomDiscard :: (MonadConc n, MonadRef r n, RandomGen g) => (Either Failure a -> Maybe Discard) -> MemType -> g -> Int -> Int -> ConcT r n a -> n [(Either Failure a, Trace)]
Files
- CHANGELOG.markdown +112/−0
- LICENSE +1/−1
- README.markdown +105/−42
- Test/DejaFu.hs +939/−798
- Test/DejaFu/Common.hs +0/−1070
- Test/DejaFu/Conc.hs +28/−24
- Test/DejaFu/Conc/Internal.hs +48/−23
- Test/DejaFu/Conc/Internal/Common.hs +13/−8
- Test/DejaFu/Conc/Internal/Memory.hs +5/−5
- Test/DejaFu/Conc/Internal/STM.hs +239/−0
- Test/DejaFu/Conc/Internal/Threading.hs +59/−8
- Test/DejaFu/Defaults.hs +1/−1
- Test/DejaFu/Internal.hs +338/−0
- Test/DejaFu/Refinement.hs +4/−5
- Test/DejaFu/SCT.hs +92/−91
- Test/DejaFu/SCT/Internal.hs +0/−816
- Test/DejaFu/SCT/Internal/DPOR.hs +793/−0
- Test/DejaFu/SCT/Internal/Weighted.hs +66/−0
- Test/DejaFu/STM.hs +0/−122
- Test/DejaFu/STM/Internal.hs +0/−225
- Test/DejaFu/Schedule.hs +12/−45
- Test/DejaFu/Types.hs +566/−0
- Test/DejaFu/Utils.hs +114/−0
- dejafu.cabal +19/−21
CHANGELOG.markdown view
@@ -7,6 +7,118 @@ *de facto* standard Haskell versioning scheme. +1.0.0.0+-------++- **Date** 2017-12-23+- **Git tag** [dejafu-1.0.0.0][]+- **Hackage** https://hackage.haskell.org/package/dejafu-1.0.0.0++### Test.DejaFu++- All testing functions now require a `MonadConc`, `MonadRef`, and `MonadIO` constraint:++ It is no longer possible to test things in `ST`.++- All testing functions now take the action to test as the last parameter.++- The `autocheckIO`, `dejafuIO`, `dejafusIO`, `autocheckWayIO`, `dejafuWayIO`, `dejafusWayIO`,+ `dejafuDiscardIO`, `runTestM`, and `runTestWayM` functions are now gone.++- The `Predicate` type has been replaced with a more general `ProPredicate` type which is a+ profunctor and (b) can discard results not needed to determine if the predicate passes. (#124)++ All testing functions have been generalised to take a `ProPredicate` instead. The `Predicate a`+ type remains as an alias for `ProPredicate a a`. Passing tests have their resident memory usage+ significantly decreased.++- The `Result` type no longer includes a number of cases checked, as this is not meaningful with+ predicates including discard functions.++- New `alwaysNothing` and `somewhereNothing` functions, like `alwaysTrue` and `somewhereTrue`, to+ lift functions to `ProPredicate`s.++- The `alwaysTrue2` function is gone, as its behaviour was unintuitive and easy to get wrong, and+ has been replaced with new `alwaysSameOn` and `alwaysSameBy` predicates, which generalise+ `alwaysSame`.++- The `alwaysSame`, `alwaysSameOn`, and `alwaysSameBy` predicates now gives the simplest execution+ trace leading to each distinct result.++### Test.DejaFu.Common++- This module has been split up into new Test.DejaFu.Internal, Types, and Utils modules. (#155)++- New `ForkOS` and `IsCurrentThreadBound` thread actions. (#126)++- New `WillForkOS` and `WillIsCurrentThreadBound` lookaheads. (#126)++- The `TTrace` type synonym for `[TAction]` has been removed.++- The `preEmpCount` function has been removed.++- New functions `strengthenDiscard` and `weakenDiscard` to combine discard functions.++- The `Discard` type is now defined here and re-exported from Test.DejaFu.SCT.++- The `ThreadId`, `CRefId`, `MVarId`, and `TVarId` types are now newtypes over a common `Id`+ type. (#137)++### Test.DejaFu.Conc++- The `ConcST` type alias is gone.++- The `MonadBase IO ConcIO` instance is gone.++- The `MonadIO ConcIO` instance is replaces with a more general `MonadIO n => MonadIO (ConcT r n)`+ instance.++- The `runConcurrent` function now has a `MonadConc` constraint.++- If bound threads are supported, the main thread when testing is bound. (#126)++- Each entry in an execution trace is now in the form `(decision, alternatives, action)`. The+ chosen thread is no longer in the list of alternatives, which makes raw traces easier to+ read. (#121)++- Due to changes in Test.DejaFu.Schedule, no longer re-exports `Decision`, `NonEmpty`, `tidOf`, or+ `decisionOf`.++### Test.DejaFu.Refinement++- A blocking interference function is no longer reported as a deadlocking execution.++### Test.DejaFu.Schedule++- No longer re-exports `Decision` or `NonEmpty`.++- The `tidOf` and `decisionOf` functions have moved to Test.DejaFu.Utils.++### Test.DejaFu.SCT++- All testing functions now require a `MonadConc` constraint:++ It is no longer possible to test things in `ST`.++### Test.DejaFu.STM++- This is now an internal module. (#155)++### Performance++- Significant resident memory reduction for most passing tests.+- Improved dependency detection for `MVar` actions, leading to fewer executions.++### Miscellaneous++- The minimum supported version of concurrency is now 1.3.0.0.++[dejafu-1.0.0.0]: https://github.com/barrucadu/dejafu/releases/tag/dejafu-1.0.0.0+++---------------------------------------------------------------------------------------------------++ 0.9.1.2 -------
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2015, Michael Walker <mike@barrucadu.co.uk>+Copyright (c) 2015--2017, Michael Walker <mike@barrucadu.co.uk> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
README.markdown view
@@ -7,58 +7,125 @@ > > -- Terry Pratchett, Thief of Time -Concurrency is nice, deadlocks and race conditions not so much. The-`Par` monad family, as defined in [abstract-par][] provides-deterministic parallelism, but sometimes we can tolerate a bit of-nondeterminism.+- [Installation](#installation)+- [Quick start guide](#quick-start-guide)+- [Why Déjà Fu?](#why-déjà-fu)+- [Contributing](#contributing)+- [Release notes](#release-notes)+- [Questions, feedback, discussion](#questions-feedback-discussion)+- [Bibliography](#bibliography)+- **[The website!](http://dejafu.readthedocs.io/)** -This package builds on the concurrency package (also in this-repository) by enabling you to systematically and deterministically-test your concurrent programs.+Déjà Fu is a unit-testing library for concurrent Haskell programs.+Tests are deterministic and expressive, making it easy and convenient+to test your threaded code. Available on [GitHub][], [Hackage][], and+[Stackage][]. -The documentation of the latest developmental version is-[available online][docs]. Examples can be found in the test suite.+[GitHub]: https://github.com/barrucadu/dejafu+[Hackage]: https://hackage.haskell.org/package/dejafu+[Stackage]: https://www.stackage.org/package/dejafu -**Note on the test suite:** This is in a separate project-(dejafu-tests) because Cabal-the-library is a bit naff. See this-[issue][]. -Déjà Fu and `IO`-----------------+Installation+------------ -The core assumption underlying Déjà Fu is that any apparent-nondeterminism arises purely from the scheduling behaviour. To put it-another way, a given computation, parametrised with a fixed set of-scheduling decisions, is deterministic.+Install from Hackage globally: -Whilst this assumption may not hold in general when `IO` is involved,-you should strive to produce test cases where it does.+```+$ cabal install dejafu+``` -Memory Model+Or add it to your cabal file:++```+build-depends: ...+ , dejafu+```++Or to your package.yaml:++```+dependencies:+ ...+ - dejafu+```+++Quick start guide+-----------------++Déjà Fu supports unit testing, and comes with a helper function called+`autocheck` to look for some common issues. Let's see it in action:++```haskell+import Control.Concurrent.Classy++myFunction :: MonadConc m => m String+myFunction = do+ var <- newEmptyMVar+ fork (putMVar var "hello")+ fork (putMVar var "world")+ readMVar var+```++That `MonadConc` is a typeclass abstraction over concurrency, but+we'll get onto that shortly. First, the result of testing:++```+> autocheck myFunction+[pass] Never Deadlocks+[pass] No Exceptions+[fail] Consistent Result+ "hello" S0----S1--S0--++ "world" S0----S2--S0--+False+```++There are no deadlocks or uncaught exceptions, which is good; but the+program is (as you probably spotted) nondeterministic!++Along with each result, Déjà Fu gives us a representative execution+trace in an abbreviated form. `Sn` means that thread `n` started+executing, and `Pn` means that thread `n` pre-empted the previously+running thread.+++Why Déjà Fu? ------------ -The testing functionality supports a few different memory models, for-computations which use non-synchronised `CRef` operations. The-supported models are:+Testing concurrent programs is difficult, because in general they are+nondeterministic. This leads to people using work-arounds like+running their testsuite many thousands of times; or running their+testsuite while putting their machine under heavy load. -- **Sequential Consistency:** A program behaves as a simple- interleaving of the actions in different threads. When a CRef is- written to, that write is immediately visible to all threads.+These approaches are inadequate for a few reasons: -- **Total Store Order (TSO):** Each thread has a write buffer. A- thread sees its writes immediately, but other threads will only- see writes when they are committed, which may happen later. Writes- are committed in the same order that they are created.+- **How many runs is enough?** When you are just hopping to spot a bug+ by coincidence, how do you know to stop?+- **How do you know if you've fixed a bug you saw previously?**+ Because the scheduler is a black box, you don't know if the+ previously buggy schedule has been re-run.+- **You won't get that much scheduling variety!** Operating systems+ and language runtimes like to run threads for long periods of time,+ which reduces the variety you get (and so drives up the number of+ runs you need). -- **Partial Store Order (PSO):** Each CRef has a write buffer. A- thread sees its writes immediately, but other threads will only- see writes when they are committed, which may happen later. Writes- to different CRefs are not necessarily committed in the same order- that they are created.+Déjà Fu addresses these points by offering *complete* testing. You+can run a test case and be guaranteed to find all results with some+bounds. These bounds can be configured, or even disabled! The+underlying approach used is smarter than merely trying all possible+executions, and will in general explore the state-space quickly. -If a testing function does not take the memory model as a parameter,-it uses TSO.+If your test case is just too big for complete testing, there is also+a random scheduling mode, which is necessarily *incomplete*. However,+Déjà Fu will tend to produce much more schedule variety than just+running your test case in `IO` the same number of times, and so bugs+will tend to crop up sooner. Furthermore, as you get execution traces+out, you can be certain that a bug has been fixed by simply following+the trace by eye. + Contributing ------------ @@ -66,7 +133,3 @@ Feel free to contact me on GitHub, through IRC (#haskell on freenode), or email (mike@barrucadu.co.uk).--[docs]: https://docs.barrucadu.co.uk/dejafu-[abstract-par]: https://hackage.haskell.org/package/abstract-par/docs/Control-Monad-Par-Class.html-[issue]: https://github.com/commercialhaskell/stack/issues/1122
Test/DejaFu.hs view
@@ -1,801 +1,942 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RankNTypes #-}---- |--- Module : Test.DejaFu--- Copyright : (c) 2016 Michael Walker--- License : MIT--- Maintainer : Michael Walker <mike@barrucadu.co.uk>--- Stability : experimental--- Portability : RankNTypes------ Deterministic testing for concurrent computations.------ As an example, consider this program, which has two locks and a--- shared variable. Two threads are spawned, which claim the locks,--- update the shared variable, and release the locks. The main thread--- waits for them both to terminate, and returns the final result.------ > example1 :: MonadConc m => m Int--- > example1 = do--- > a <- newEmptyMVar--- > b <- newEmptyMVar--- >--- > c <- newMVar 0--- >--- > let lock m = putMVar m ()--- > let unlock = takeMVar--- >--- > j1 <- spawn $ lock a >> lock b >> modifyMVar_ c (return . succ) >> unlock b >> unlock a--- > j2 <- spawn $ lock b >> lock a >> modifyMVar_ c (return . pred) >> unlock a >> unlock b--- >--- > takeMVar j1--- > takeMVar j2--- >--- > takeMVar c------ The correct result is 0, as it starts out as 0 and is incremented--- and decremented by threads 1 and 2, respectively. However, note the--- order of acquisition of the locks in the two threads. If thread 2--- pre-empts thread 1 between the acquisition of the locks (or if--- thread 1 pre-empts thread 2), a deadlock situation will arise, as--- thread 1 will have lock @a@ and be waiting on @b@, and thread 2--- will have @b@ and be waiting on @a@.------ Here is what Deja Fu has to say about it:------ > > autocheck example1--- > [fail] Never Deadlocks (checked: 5)--- > [deadlock] S0------------S1-P2--S1---- > [pass] No Exceptions (checked: 12)--- > [fail] Consistent Result (checked: 11)--- > 0 S0------------S2-----------------S1-----------------S0------- >--- > [deadlock] S0------------S1-P2--S1---- > False------ It identifies the deadlock, and also the possible results the--- computation can produce, and displays a simplified trace leading to--- each failing outcome. The trace contains thread numbers, and the--- names (which can be set by the programmer) are displayed beneath.--- It also returns @False@ as there are test failures. The automatic--- testing functionality is good enough if you only want to check your--- computation is deterministic, but if you have more specific--- requirements (or have some expected and tolerated level of--- nondeterminism), you can write tests yourself using the @dejafu*@--- functions.------ __Warning:__ If your computation under test does @IO@, the @IO@--- will be executed lots of times! Be sure that it is deterministic--- enough not to invalidate your test results. Mocking may be useful--- where possible.-module Test.DejaFu- ( -- * Testing-- -- | Testing in Deja Fu is similar to unit testing, the programmer- -- produces a self-contained monadic action to execute under- -- different schedules, and supplies a list of predicates to apply- -- to the list of results produced.- --- -- If you simply wish to check that something is deterministic, see- -- the 'autocheck' and 'autocheckIO' functions.- --- -- These functions use a Total Store Order (TSO) memory model for- -- unsynchronised actions, see \"Testing under Alternative Memory- -- Models\" for some explanation of this.-- autocheck- , dejafu- , dejafus- , autocheckIO- , dejafuIO- , dejafusIO-- -- * Testing with different settings-- , Way- , defaultWay- , systematically- , randomly- , uniformly- , swarmy-- , autocheckWay- , autocheckWayIO- , dejafuWay- , dejafuWayIO- , dejafusWay- , dejafusWayIO-- , Discard(..)- , defaultDiscarder-- , dejafuDiscard- , dejafuDiscardIO-- -- ** Memory Models-- -- | Threads running under modern multicore processors do not behave- -- as a simple interleaving of the individual thread- -- actions. Processors do all sorts of complex things to increase- -- speed, such as buffering writes. For concurrent programs which- -- make use of non-synchronised functions (such as 'readCRef'- -- coupled with 'writeCRef') different memory models may yield- -- different results.- --- -- As an example, consider this program (modified from the- -- Data.IORef documentation). Two @CRef@s are created, and two- -- threads spawned to write to and read from both. Each thread- -- returns the value it observes.- --- -- > example2 :: MonadConc m => m (Bool, Bool)- -- > example2 = do- -- > r1 <- newCRef False- -- > r2 <- newCRef False- -- >- -- > x <- spawn $ writeCRef r1 True >> readCRef r2- -- > y <- spawn $ writeCRef r2 True >> readCRef r1- -- >- -- > (,) <$> readMVar x <*> readMVar y- --- -- Under a sequentially consistent memory model the possible results- -- are @(True, True)@, @(True, False)@, and @(False, True)@. Under- -- total or partial store order, @(False, False)@ is also a possible- -- result, even though there is no interleaving of the threads which- -- can lead to this.- --- -- We can see this by testing with different memory models:- --- -- > > autocheckWay defaultWay SequentialConsistency example2- -- > [pass] Never Deadlocks (checked: 6)- -- > [pass] No Exceptions (checked: 6)- -- > [fail] Consistent Result (checked: 5)- -- > (False,True) S0-------S1-----S0--S2-----S0---- -- > (True,False) S0-------S1-P2-----S1----S0----- -- > (True,True) S0-------S1--P2-----S1---S0----- -- > (False,True) S0-------S1---P2-----S1--S0----- -- > (True,False) S0-------S2-----S1-----S0----- -- > ...- -- > False- --- -- > > autocheckWay defaultWay TotalStoreOrder example2- -- > [pass] Never Deadlocks (checked: 303)- -- > [pass] No Exceptions (checked: 303)- -- > [fail] Consistent Result (checked: 302)- -- > (False,True) S0-------S1-----C-S0--S2-----C-S0---- -- > (True,False) S0-------S1-P2-----C-S1----S0----- -- > (True,True) S0-------S1-P2--C-S1----C-S0--S2---S0---- -- > (False,True) S0-------S1-P2--P1--C-C-S1--S0--S2---S0---- -- > (False,False) S0-------S1-P2--P1----S2---C-C-S0----- -- > ...- -- > False- --- -- Traces for non-sequentially-consistent memory models show where- -- writes to @CRef@s are /committed/, which makes a write visible to- -- all threads rather than just the one which performed the- -- write. Only 'writeCRef' is broken up into separate write and- -- commit steps, 'atomicModifyCRef' is still atomic and imposes a- -- memory barrier.-- , MemType(..)- , defaultMemType-- -- ** Schedule Bounding-- -- | Schedule bounding is an optimisation which only considers- -- schedules within some /bound/. This sacrifices completeness- -- outside of the bound, but can drastically reduce the number of- -- schedules to test, and is in fact necessary for non-terminating- -- programs.- --- -- The standard testing mechanism uses a combination of pre-emption- -- bounding, fair bounding, and length bounding. Pre-emption + fair- -- bounding is useful for programs which use loop/yield control- -- flows but are otherwise terminating. Length bounding makes it- -- possible to test potentially non-terminating programs.-- , Bounds(..)- , defaultBounds- , noBounds- , PreemptionBound(..)- , defaultPreemptionBound- , FairBound(..)- , defaultFairBound- , LengthBound(..)- , defaultLengthBound-- -- * Results-- -- | The results of a test can be pretty-printed to the console, as- -- with the above functions, or used in their original, much richer,- -- form for debugging purposes. These functions provide full access- -- to this data type which, most usefully, contains a detailed trace- -- of execution, showing what each thread did at each point.-- , Result(..)- , Failure(..)- , runTest- , runTestWay- , runTestM- , runTestWayM-- -- * Predicates-- -- | Predicates evaluate a list of results of execution and decide- -- whether some test case has passed or failed. They can be lazy and- -- make use of short-circuit evaluation to avoid needing to examine- -- the entire list of results, and can check any property which can- -- be defined for the return type of your monadic action.- --- -- A collection of common predicates are provided, along with the- -- helper functions 'alwaysTrue', 'alwaysTrue2' and 'somewhereTrue'- -- to lfit predicates over a single result to over a collection of- -- results.-- , Predicate- , representative- , abortsNever- , abortsAlways- , abortsSometimes- , deadlocksNever- , deadlocksAlways- , deadlocksSometimes- , exceptionsNever- , exceptionsAlways- , exceptionsSometimes- , alwaysSame- , notAlwaysSame- , alwaysTrue- , alwaysTrue2- , somewhereTrue- , gives- , gives'-- -- ** Failures-- , isInternalError- , isAbort- , isDeadlock- , isUncaughtException- , isIllegalSubconcurrency-- -- * Refinement property testing-- -- | Consider this statement about @MVar@s: \"using @readMVar@ is- -- better than @takeMVar@ followed by @putMVar@ because the former- -- is atomic but the latter is not.\"- --- -- Deja Fu can test properties like that:- --- -- @- -- sig e = Sig- -- { initialise = maybe newEmptyMVar newMVar- -- , observe = \\v _ -> tryReadMVar v- -- , interfere = \\v s -> tryTakeMVar v >> maybe (pure ()) (void . tryPutMVar v) s- -- , expression = e- -- }- --- -- > check $ sig (void . readMVar) \`equivalentTo\` sig (\\v -> takeMVar v >>= putMVar v)- -- *** Failure: (seed Just ())- -- left: [(Nothing,Just ())]- -- right: [(Nothing,Just ()),(Just Deadlock,Just ())]- -- @- --- -- The two expressions are not equivalent, and we get given the- -- counterexample!- , module Test.DejaFu.Refinement- ) where--import Control.Arrow (first)-import Control.DeepSeq (NFData(..))-import Control.Monad (unless, when)-import Control.Monad.Ref (MonadRef)-import Control.Monad.ST (runST)-import Data.Function (on)-import Data.List (intercalate, intersperse, minimumBy)-import Data.Ord (comparing)--import Test.DejaFu.Common-import Test.DejaFu.Conc-import Test.DejaFu.Defaults-import Test.DejaFu.Refinement-import Test.DejaFu.SCT------------------------------------------------------------------------------------- DejaFu---- | Automatically test a computation. In particular, look for--- deadlocks, uncaught exceptions, and multiple return values.------ This uses the 'Conc' monad for testing, which is an instance of--- 'MonadConc'. If you need to test something which also uses--- 'MonadIO', use 'autocheckIO'.------ @since 0.1.0.0-autocheck :: (Eq a, Show a)- => (forall t. ConcST t a)- -- ^ The computation to test- -> IO Bool-autocheck = autocheckWay defaultWay defaultMemType---- | Variant of 'autocheck' which takes a way to run the program and a--- memory model.------ Schedule bounding is used to filter the large number of possible--- schedules, and can be iteratively increased for further coverage--- guarantees. Empirical studies (/Concurrency Testing Using Schedule--- Bounding: an Empirical Study/, P. Thompson, A. Donaldson, and--- A. Betts) have found that many concurrency bugs can be exhibited--- with as few as two threads and two pre-emptions, which is part of--- what 'dejafus' uses.------ __Warning:__ Using largers bounds will almost certainly--- significantly increase the time taken to test!------ @since 0.6.0.0-autocheckWay :: (Eq a, Show a)- => Way- -- ^ How to run the concurrent program.- -> MemType- -- ^ The memory model to use for non-synchronised @CRef@ operations.- -> (forall t. ConcST t a)- -- ^ The computation to test- -> IO Bool-autocheckWay way memtype conc =- dejafusWay way memtype conc autocheckCases---- | Variant of 'autocheck' for computations which do 'IO'.------ @since 0.2.0.0-autocheckIO :: (Eq a, Show a) => ConcIO a -> IO Bool-autocheckIO = autocheckWayIO defaultWay defaultMemType---- | Variant of 'autocheckWay' for computations which do 'IO'.------ @since 0.6.0.0-autocheckWayIO :: (Eq a, Show a) => Way -> MemType -> ConcIO a -> IO Bool-autocheckWayIO way memtype concio =- dejafusWayIO way memtype concio autocheckCases---- | Predicates for the various autocheck functions.-autocheckCases :: Eq a => [(String, Predicate a)]-autocheckCases =- [ ("Never Deadlocks", representative deadlocksNever)- , ("No Exceptions", representative exceptionsNever)- , ("Consistent Result", alwaysSame) -- already representative- ]---- | Check a predicate and print the result to stdout, return 'True'--- if it passes.------ @since 0.1.0.0-dejafu :: Show a- => (forall t. ConcST t a)- -- ^ The computation to test- -> (String, Predicate a)- -- ^ The predicate (with a name) to check- -> IO Bool-dejafu = dejafuWay defaultWay defaultMemType---- | Variant of 'dejafu' which takes a way to run the program and a--- memory model.------ @since 0.6.0.0-dejafuWay :: Show a- => Way- -- ^ How to run the concurrent program.- -> MemType- -- ^ The memory model to use for non-synchronised @CRef@ operations.- -> (forall t. ConcST t a)- -- ^ The computation to test- -> (String, Predicate a)- -- ^ The predicate (with a name) to check- -> IO Bool-dejafuWay = dejafuDiscard (const Nothing)---- | Variant of 'dejafuWay' which can selectively discard results.------ @since 0.7.1.0-dejafuDiscard :: Show a- => (Either Failure a -> Maybe Discard)- -- ^ Selectively discard results.- -> Way- -- ^ How to run the concurrent program.- -> MemType- -- ^ The memory model to use for non-synchronised @CRef@ operations.- -> (forall t. ConcST t a)- -- ^ The computation to test- -> (String, Predicate a)- -- ^ The predicate (with a name) to check- -> IO Bool-dejafuDiscard discard way memtype conc (name, test) = do- let traces = runST (runSCTDiscard discard way memtype conc)- doTest name (test traces)---- | Variant of 'dejafu' which takes a collection of predicates to--- test, returning 'True' if all pass.------ @since 0.1.0.0-dejafus :: Show a- => (forall t. ConcST t a)- -- ^ The computation to test- -> [(String, Predicate a)]- -- ^ The list of predicates (with names) to check- -> IO Bool-dejafus = dejafusWay defaultWay defaultMemType---- | Variant of 'dejafus' which takes a way to run the program and a--- memory model.------ @since 0.6.0.0-dejafusWay :: Show a- => Way- -- ^ How to run the concurrent program.- -> MemType- -- ^ The memory model to use for non-synchronised @CRef@ operations.- -> (forall t. ConcST t a)- -- ^ The computation to test- -> [(String, Predicate a)]- -- ^ The list of predicates (with names) to check- -> IO Bool-dejafusWay way memtype conc tests = do- let traces = runST (runSCT way memtype conc)- results <- mapM (\(name, test) -> doTest name $ test traces) tests- pure (and results)---- | Variant of 'dejafu' for computations which do 'IO'.------ @since 0.2.0.0-dejafuIO :: Show a => ConcIO a -> (String, Predicate a) -> IO Bool-dejafuIO = dejafuWayIO defaultWay defaultMemType---- | Variant of 'dejafuWay' for computations which do 'IO'.------ @since 0.6.0.0-dejafuWayIO :: Show a => Way -> MemType -> ConcIO a -> (String, Predicate a) -> IO Bool-dejafuWayIO = dejafuDiscardIO (const Nothing)---- | Variant of 'dejafuDiscard' for computations which do 'IO'.------ @since 0.7.1.0-dejafuDiscardIO :: Show a => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcIO a -> (String, Predicate a) -> IO Bool-dejafuDiscardIO discard way memtype concio (name, test) = do- traces <- runSCTDiscard discard way memtype concio- doTest name (test traces)---- | Variant of 'dejafus' for computations which do 'IO'.------ @since 0.2.0.0-dejafusIO :: Show a => ConcIO a -> [(String, Predicate a)] -> IO Bool-dejafusIO = dejafusWayIO defaultWay defaultMemType---- | Variant of 'dejafusWay' for computations which do 'IO'.------ @since 0.6.0.0-dejafusWayIO :: Show a => Way -> MemType -> ConcIO a -> [(String, Predicate a)] -> IO Bool-dejafusWayIO way memtype concio tests = do- traces <- runSCT way memtype concio- results <- mapM (\(name, test) -> doTest name $ test traces) tests- pure (and results)------------------------------------------------------------------------------------- Test cases---- | The results of a test, including the number of cases checked to--- determine the final boolean outcome.------ @since 0.2.0.0-data Result a = Result- { _pass :: Bool- -- ^ Whether the test passed or not.- , _casesChecked :: Int- -- ^ The number of cases checked.- , _failures :: [(Either Failure a, Trace)]- -- ^ The failing cases, if any.- , _failureMsg :: String- -- ^ A message to display on failure, if nonempty- } deriving (Eq, Show)---- | @since 0.5.1.0-instance NFData a => NFData (Result a) where- rnf r = rnf ( _pass r- , _casesChecked r- , _failures r- , _failureMsg r- )---- | A failed result, taking the given list of failures.-defaultFail :: [(Either Failure a, Trace)] -> Result a-defaultFail failures = Result False 0 failures ""---- | A passed result.-defaultPass :: Result a-defaultPass = Result True 0 [] ""--instance Functor Result where- fmap f r = r { _failures = map (first $ fmap f) $ _failures r }--instance Foldable Result where- foldMap f r = foldMap f [a | (Right a, _) <- _failures r]---- | Run a predicate over all executions within the default schedule--- bounds.------ @since 0.1.0.0-runTest ::- Predicate a- -- ^ The predicate to check- -> (forall t. ConcST t a)- -- ^ The computation to test- -> Result a-runTest test conc =- runST (runTestM test conc)---- | Variant of 'runTest' which takes a way to run the program and a--- memory model.------ @since 0.6.0.0-runTestWay- :: Way- -- ^ How to run the concurrent program.- -> MemType- -- ^ The memory model to use for non-synchronised @CRef@ operations.- -> Predicate a- -- ^ The predicate to check- -> (forall t. ConcST t a)- -- ^ The computation to test- -> Result a-runTestWay way memtype predicate conc =- runST (runTestWayM way memtype predicate conc)---- | Monad-polymorphic variant of 'runTest'.------ @since 0.4.0.0-runTestM :: MonadRef r n- => Predicate a -> ConcT r n a -> n (Result a)-runTestM = runTestWayM defaultWay defaultMemType---- | Monad-polymorphic variant of 'runTest''.------ @since 0.6.0.0-runTestWayM :: MonadRef r n- => Way -> MemType -> Predicate a -> ConcT r n a -> n (Result a)-runTestWayM way memtype predicate conc =- predicate <$> runSCT way memtype conc------------------------------------------------------------------------------------- Predicates---- | A @Predicate@ is a function which collapses a list of results--- into a 'Result'.------ @since 0.1.0.0-type Predicate a = [(Either Failure a, Trace)] -> Result a---- | Reduce the list of failures in a @Predicate@ to one--- representative trace for each unique result.------ This may throw away \"duplicate\" failures which have a unique--- cause but happen to manifest in the same way. However, it is--- convenient for filtering out true duplicates.------ @since 0.2.0.0-representative :: Eq a => Predicate a -> Predicate a-representative p xs = result { _failures = choose . collect $ _failures result } where- result = p xs- collect = groupBy' [] ((==) `on` fst)- choose = map $ minimumBy (comparing $ \(_, trc) -> (preEmps trc, length trc))-- preEmps trc = preEmpCount (map (\(d,_,a) -> (d, a)) trc) (Continue, WillStop)-- groupBy' res _ [] = res- groupBy' res eq (y:ys) = groupBy' (insert' eq y res) eq ys-- insert' _ x [] = [[x]]- insert' eq x (ys@(y:_):yss)- | x `eq` y = (x:ys) : yss- | otherwise = ys : insert' eq x yss- insert' _ _ ([]:_) = undefined---- | Check that a computation never aborts.------ @since 0.2.0.0-abortsNever :: Predicate a-abortsNever = alwaysTrue (not . either (==Abort) (const False))---- | Check that a computation always aborts.------ @since 0.2.0.0-abortsAlways :: Predicate a-abortsAlways = alwaysTrue $ either (==Abort) (const False)---- | Check that a computation aborts at least once.------ @since 0.2.0.0-abortsSometimes :: Predicate a-abortsSometimes = somewhereTrue $ either (==Abort) (const False)---- | Check that a computation never deadlocks.------ @since 0.1.0.0-deadlocksNever :: Predicate a-deadlocksNever = alwaysTrue (not . either isDeadlock (const False))---- | Check that a computation always deadlocks.------ @since 0.1.0.0-deadlocksAlways :: Predicate a-deadlocksAlways = alwaysTrue $ either isDeadlock (const False)---- | Check that a computation deadlocks at least once.------ @since 0.1.0.0-deadlocksSometimes :: Predicate a-deadlocksSometimes = somewhereTrue $ either isDeadlock (const False)---- | Check that a computation never fails with an uncaught exception.------ @since 0.1.0.0-exceptionsNever :: Predicate a-exceptionsNever = alwaysTrue (not . either isUncaughtException (const False))---- | Check that a computation always fails with an uncaught exception.------ @since 0.1.0.0-exceptionsAlways :: Predicate a-exceptionsAlways = alwaysTrue $ either isUncaughtException (const False)---- | Check that a computation fails with an uncaught exception at least once.------ @since 0.1.0.0-exceptionsSometimes :: Predicate a-exceptionsSometimes = somewhereTrue $ either isUncaughtException (const False)---- | Check that the result of a computation is always the same. In--- particular this means either: (a) it always fails in the same way,--- or (b) it never fails and the values returned are all equal.------ @since 0.1.0.0-alwaysSame :: Eq a => Predicate a-alwaysSame = representative $ alwaysTrue2 (==)---- | Check that the result of a computation is not always the same.------ @since 0.1.0.0-notAlwaysSame :: Eq a => Predicate a-notAlwaysSame [x] = (defaultFail [x]) { _casesChecked = 1 }-notAlwaysSame xs = go xs $ defaultFail [] where- go [y1,y2] res- | fst y1 /= fst y2 = incCC res { _pass = True }- | otherwise = incCC res { _failures = y1 : y2 : _failures res }- go (y1:y2:ys) res- | fst y1 /= fst y2 = go (y2:ys) . incCC $ res { _pass = True }- | otherwise = go (y2:ys) . incCC $ res { _failures = y1 : y2 : _failures res }- go _ res = res---- | Check that the result of a unary boolean predicate is always--- true.------ @since 0.1.0.0-alwaysTrue :: (Either Failure a -> Bool) -> Predicate a-alwaysTrue p xs = go xs $ (defaultFail failures) { _pass = True } where- go (y:ys) res- | p (fst y) = go ys . incCC $ res- | otherwise = incCC $ res { _pass = False }- go [] res = res-- failures = filter (not . p . fst) xs---- | Check that the result of a binary boolean predicate is true--- between all pairs of results. Only properties which are transitive--- and symmetric should be used here.------ If the predicate fails, /both/ (result,trace) tuples will be added--- to the failures list.------ @since 0.1.0.0-alwaysTrue2 :: (Either Failure a -> Either Failure a -> Bool) -> Predicate a-alwaysTrue2 _ [_] = defaultPass { _casesChecked = 1 }-alwaysTrue2 p xs = go xs $ defaultPass { _failures = failures } where- go [y1,y2] res- | p (fst y1) (fst y2) = incCC res- | otherwise = incCC res { _pass = False }- go (y1:y2:ys) res- | p (fst y1) (fst y2) = go (y2:ys) . incCC $ res- | otherwise = go (y2:ys) . incCC $ res { _pass = False }- go _ res = res-- failures = fgo xs where- fgo (y1:y2:ys)- | p (fst y1) (fst y2) = fgo (y2:ys)- | otherwise = y1 : y2 : fgo2 y2 ys- fgo _ = []-- fgo2 y1 (y2:ys)- | p (fst y1) (fst y2) = fgo (y2:ys)- | otherwise = y2 : fgo2 y2 ys- fgo2 _ _ = []---- | Check that the result of a unary boolean predicate is true at--- least once.------ @since 0.1.0.0-somewhereTrue :: (Either Failure a -> Bool) -> Predicate a-somewhereTrue p xs = go xs $ defaultFail failures where- go (y:ys) res- | p (fst y) = incCC $ res { _pass = True }- | otherwise = go ys . incCC $ res { _failures = y : _failures res }- go [] res = res-- failures = filter (not . p . fst) xs---- | Predicate for when there is a known set of results where every--- result must be exhibited at least once.------ @since 0.2.0.0-gives :: (Eq a, Show a) => [Either Failure a] -> Predicate a-gives expected results = go expected [] results $ defaultFail failures where- go waitingFor alreadySeen ((x, _):xs) res- -- If it's a result we're waiting for, move it to the- -- @alreadySeen@ list and continue.- | x `elem` waitingFor = go (filter (/=x) waitingFor) (x:alreadySeen) xs res { _casesChecked = _casesChecked res + 1 }-- -- If it's a result we've already seen, continue.- | x `elem` alreadySeen = go waitingFor alreadySeen xs res { _casesChecked = _casesChecked res + 1 }-- -- If it's not a result we expected, fail.- | otherwise = res { _casesChecked = _casesChecked res + 1 }-- go [] _ [] res = res { _pass = True }- go es _ [] res = res { _failureMsg = unlines $ map (\e -> "Expected: " ++ show e) es }-- failures = filter (\(r, _) -> r `notElem` expected) results---- | Variant of 'gives' that doesn't allow for expected failures.------ @since 0.2.0.0-gives' :: (Eq a, Show a) => [a] -> Predicate a-gives' = gives . map Right------------------------------------------------------------------------------------- Utils---- | Run a test and print to stdout-doTest :: Show a => String -> Result a -> IO Bool-doTest name result = do- if _pass result- then- -- Display a pass message.- putStrLn $ "\27[32m[pass]\27[0m " ++ name ++ " (checked: " ++ show (_casesChecked result) ++ ")"- else do- -- Display a failure message, and the first 5 (simplified) failed traces- putStrLn ("\27[31m[fail]\27[0m " ++ name ++ " (checked: " ++ show (_casesChecked result) ++ ")")-- unless (null $ _failureMsg result) $- putStrLn $ _failureMsg result-- let failures = _failures result- let output = map (\(r, t) -> putStrLn . indent $ either showFail show r ++ " " ++ showTrace t) $ take 5 failures- sequence_ $ intersperse (putStrLn "") output- when (moreThan 5 failures) $- putStrLn (indent "...")-- pure (_pass result)---- | Check if a list is longer than some value, without needing to--- compute the entire length.-moreThan :: Int -> [a] -> Bool-moreThan n [] = n < 0-moreThan 0 _ = True-moreThan n (_:rest) = moreThan (n-1) rest---- | Increment the cases-incCC :: Result a -> Result a-incCC r = r { _casesChecked = _casesChecked r + 1 }+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TupleSections #-}++{- |+Module : Test.DejaFu+Copyright : (c) 2015--2017 Michael Walker+License : MIT+Maintainer : Michael Walker <mike@barrucadu.co.uk>+Stability : experimental+Portability : LambdaCase, MultiParamTypeClasses, TupleSections++dejafu is a library for unit-testing concurrent Haskell programs,+written using the [concurrency](https://hackage.haskell.org/package/concurrency)+package's 'MonadConc' typeclass.++__A first test:__ This is a simple concurrent program which forks two+threads and each races to write to the same @MVar@:++> example = do+> var <- newEmptyMVar+> fork (putMVar var "hello")+> fork (putMVar var "world")+> readMVar var++We can test it with dejafu like so:++> > autocheck example+> [pass] Never Deadlocks+> [pass] No Exceptions+> [fail] Consistent Result+> "hello" S0----S1--S0--+>+> "world" S0----S2--S0--++The 'autocheck' function takes a concurrent program to test and looks+for some common unwanted behaviours: deadlocks, uncaught exceptions in+the main thread, and nondeterminism. Here we see the program is+nondeterministic, dejafu gives us all the distinct results it found+and, for each, a summarised execution trace leading to that result:++ * \"Sn\" means that thread \"n\" started executing after the previous+ thread terminated or blocked.++ * \"Pn\" means that thread \"n\" started executing, even though the+ previous thread could have continued running.++ * Each \"-\" represents one \"step\" of the computation.++__Failures:__ If a program doesn't terminate successfully, we say it+has /failed/. dejafu can detect a few different types of failure:++ * 'Deadlock', if every thread is blocked.++ * 'STMDeadlock', if every thread is blocked /and/ the main thread is+ blocked in an STM transaction.++ * 'UncaughtException', if the main thread is killed by an exception.++There are two types of failure which dejafu itself may raise:++ * 'Abort', used in systematic testing (the default) if there are no+ allowed decisions remaining. For example, by default any test case+ which takes more than 250 scheduling points to finish will be+ aborted. You can use the 'systematically' function to supply (or+ disable) your own bounds.++ * 'InternalError', used if something goes wrong. If you get this and+ aren't using a scheduler you wrote yourself, please [file a+ bug](https://github.com/barrucadu/dejafu/issues).++Finally, there is one failure which can arise through improper use of+dejafu:++ * 'IllegalSubconcurrency', the "Test.DejaFu.Conc.subconcurrency"+ function is used when multiple threads exist, or is used inside+ another @subconcurrency@ call.++__Beware of 'liftIO':__ dejafu works by running your test case lots of+times with different schedules. If you use 'liftIO' at all, make sure+that any @IO@ you perform is deterministic when executed in the same+order.++If you need to test things with /nondeterministc/ @IO@, see the+'autocheckWay', 'dejafuWay', and 'dejafusWay' functions: the+'randomly', 'uniformly', and 'swarmy' testing modes can cope with+nondeterminism.+-}+module Test.DejaFu+ ( -- * Unit testing++ autocheck+ , dejafu+ , dejafus++ -- ** Configuration++ {- |++There are a few knobs to tweak to control the behaviour of dejafu.+The defaults should generally be good enough, but if not you have a+few tricks available.++ * The 'Way', which controls how schedules are explored.++ * The 'MemType', which controls how reads and writes to @CRef@s (or+ @IORef@s) behave.++ * The 'Discard' function, which saves memory by throwing away+ uninteresting results during exploration.++-}++ , autocheckWay+ , dejafuWay+ , dejafusWay+ , dejafuDiscard++ -- *** Defaults++ , defaultWay+ , defaultMemType+ , defaultDiscarder++ -- *** Exploration++ , Way+ , systematically+ , randomly+ , uniformly+ , swarmy++ -- **** Schedule bounding++ {- |++Schedule bounding is used by the 'systematically' approach to limit+the search-space, which in general will be huge.++There are three types of bounds used:++ * The 'PreemptionBound', which bounds the number of pre-emptive+ context switches. Empirical evidence suggests @2@ is a good value+ for this, if you have a small test case.++ * The 'FairBound', which bounds the difference between how many times+ threads can yield. This is necessary to test certain kinds of+ potentially non-terminating behaviour, such as spinlocks.++ * The 'LengthBound', which bounds how long a test case can run, in+ terms of scheduling decisions. This is necessary to test certain+ kinds of potentially non-terminating behaviour, such as livelocks.++Schedule bounding is not used by the non-systematic exploration+behaviours.++-}++ , Bounds(..)+ , defaultBounds+ , noBounds+ , PreemptionBound(..)+ , defaultPreemptionBound+ , FairBound(..)+ , defaultFairBound+ , LengthBound(..)+ , defaultLengthBound++ -- *** Memory model++ {- |++When executed on a multi-core processor some @CRef@ / @IORef@ programs+can exhibit \"relaxed memory\" behaviours, where the apparent+behaviour of the program is not a simple interleaving of the actions+of each thread.++__Example:__ This is a simple program which creates two @CRef@s+containing @False@, and forks two threads. Each thread writes @True@+to one of the @CRef@s and reads the other. The value that each thread+reads is communicated back through an @MVar@:++> relaxed = do+> r1 <- newCRef False+> r2 <- newCRef False+> x <- spawn $ writeCRef r1 True >> readCRef r2+> y <- spawn $ writeCRef r2 True >> readCRef r1+> (,) <$> readMVar x <*> readMVar y++We see something surprising if we ask for the results:++> > autocheck relaxed+> [pass] Never Deadlocks+> [pass] No Exceptions+> [fail] Consistent Result+> (False,True) S0---------S1----S0--S2----S0--+>+> (False,False) S0---------S1--P2----S1--S0---+>+> (True,False) S0---------S2----S1----S0---+>+> (True,True) S0---------S1-C-S2----S1---S0---++It's possible for both threads to read the value @False@, even though+each writes @True@ to the other @CRef@ before reading. This is+because processors are free to re-order reads and writes to+independent memory addresses in the name of performance.++Execution traces for relaxed memory computations can include \"C\"+actions, as above, which show where @CRef@ writes were explicitly+/committed/, and made visible to other threads.++However, modelling this behaviour can require more executions. If you+do not care about the relaxed-memory behaviour of your program, use+the 'SequentialConsistency' model.++-}++ , MemType(..)++ -- *** Reducing memory usage++ {- |++Sometimes we know that a result is uninteresting and cannot affect the+result of a test, in which case there is no point in keeping it+around. Execution traces can be large, so any opportunity to get rid+of them early is possibly a great saving of memory.++A discard function, which has type @Either Failure a -> Maybe+Discard@, can selectively discard results or execution traces before+the schedule exploration finishes, allowing them to be garbage+collected sooner.++__Note:__ This is only relevant if you are producing your own+predicates. The predicates and helper functions provided by this+module do discard results and traces wherever possible.++-}++ , Discard(..)++ -- ** Manual testing++ {- |++The standard testing functions print their result to stdout, and throw+away some information. The traces are pretty-printed, and if there+are many failures, only the first few are shown.++If you need more information, use these functions.++-}++ , Result(..)+ , Failure(..)+ , runTest+ , runTestWay++ -- ** Predicates++ {- |++A dejafu test has two parts: the concurrent program to test, and a+predicate to determine if the test passes, based on the results of the+schedule exploration.++All of these predicates discard results and traces as eagerly as+possible, to reduce memory usage.++-}++ , Predicate+ , ProPredicate(..)+ , abortsNever+ , abortsAlways+ , abortsSometimes+ , deadlocksNever+ , deadlocksAlways+ , deadlocksSometimes+ , exceptionsNever+ , exceptionsAlways+ , exceptionsSometimes++ -- *** Helpers++ {- |++Helper functions to produce your own predicates. Such predicates+discard results and traces as eagerly as possible, to reduce memory+usage.++-}++ , representative+ , alwaysSame+ , alwaysSameOn+ , alwaysSameBy+ , notAlwaysSame+ , alwaysTrue+ , somewhereTrue+ , alwaysNothing+ , somewhereNothing+ , gives+ , gives'++ -- *** Failures++ {- |++Helper functions to identify failures.++-}++ , isInternalError+ , isAbort+ , isDeadlock+ , isUncaughtException+ , isIllegalSubconcurrency++ -- * Property testing++ {- |++dejafu can also use a property-testing style to test stateful+operations for a variety of inputs. Inputs are generated using the+[leancheck](https://hackage.haskell.org/package/leancheck) library for+enumerative testing.++__Testing @MVar@ operations with multiple producers__:++These are a little different to the property tests you may be familiar+with from libraries like QuickCheck (and leancheck). As we're testing+properties of /stateful/ and /concurrent/ things, we need to provide+some extra information.++A property consists of two /signatures/ and a relation between them.+A signature contains:++ * An initialisation function, to construct the initial state.++ * An observation function, to take a snapshot of the state at the+ end.++ * An interference function, to mess with the state in some way.++ * The expression to evaluate, as a function over the state.++> sig e = Sig+> { initialise = maybe newEmptyMVar newMVar+> , observe = \v _ -> tryReadMVar v+> , interfere = \v _ -> putMVar v 42+> , expression = void . e+> }++This is a signature for operations over @Num n => MVar n@ values where+there are multiple producers. The initialisation function takes a+@Maybe n@ and constructs an @MVar n@, empty if it gets @Nothing@; the+observation function reads the @MVar@; and the interference function+puts a new value in.++Given this signature, we can check if @readMVar@ is the same as a+@takeMVar@ followed by a @putMVar@:++> > check $ sig readMVar === sig (\v -> takeMVar v >>= putMVar v)+> *** Failure: (seed Just 0)+> left: [(Nothing,Just 0)]+> right: [(Nothing,Just 0),(Just Deadlock,Just 42)]++The two expressions are not equivalent, and we get a counterexample:+if the @MVar@ is nonempty, then the left expression (@readMVar@) will+preserve the value, but the right expression (@\v -> takeMVar v >>=+putMVar v@) may cause it to change. This is because of the concurrent+interference we have provided: the left term never empties a full+@MVar@, but the Right term does.++-}++ , module Test.DejaFu.Refinement+ ) where++import Control.Arrow (first)+import Control.DeepSeq (NFData(..))+import Control.Monad (unless, when)+import Control.Monad.Conc.Class (MonadConc)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Ref (MonadRef)+import Data.Function (on)+import Data.List (intercalate, intersperse)+import Data.Maybe (catMaybes, isNothing, mapMaybe)+import Data.Profunctor (Profunctor(..))++import Test.DejaFu.Conc+import Test.DejaFu.Defaults+import Test.DejaFu.Refinement+import Test.DejaFu.SCT+import Test.DejaFu.Types+import Test.DejaFu.Utils+++-------------------------------------------------------------------------------+-- DejaFu++-- | Automatically test a computation.+--+-- In particular, look for deadlocks, uncaught exceptions, and+-- multiple return values. Returns @True@ if all tests pass+--+-- > > autocheck example+-- > [pass] Never Deadlocks+-- > [pass] No Exceptions+-- > [fail] Consistent Result+-- > "hello" S0----S1--S0--+-- >+-- > "world" S0----S2--S0--+--+-- @since 1.0.0.0+autocheck :: (MonadConc n, MonadIO n, MonadRef r n, Eq a, Show a)+ => ConcT r n a+ -- ^ The computation to test.+ -> n Bool+autocheck = autocheckWay defaultWay defaultMemType++-- | Variant of 'autocheck' which takes a way to run the program and a+-- memory model.+--+-- > > autocheckWay defaultWay defaultMemType relaxed+-- > [pass] Never Deadlocks+-- > [pass] No Exceptions+-- > [fail] Consistent Result+-- > (False,True) S0---------S1----S0--S2----S0--+-- >+-- > (False,False) S0---------S1--P2----S1--S0---+-- >+-- > (True,False) S0---------S2----S1----S0---+-- >+-- > (True,True) S0---------S1-C-S2----S1---S0---+-- >+-- > > autocheckWay defaultWay SequentialConsistency relaxed+-- > [pass] Never Deadlocks+-- > [pass] No Exceptions+-- > [fail] Consistent Result+-- > (False,True) S0---------S1----S0--S2----S0--+-- >+-- > (True,True) S0---------S1-P2----S1---S0---+-- >+-- > (True,False) S0---------S2----S1----S0---+--+-- @since 1.0.0.0+autocheckWay :: (MonadConc n, MonadIO n, MonadRef r n, Eq a, Show a)+ => Way+ -- ^ How to run the concurrent program.+ -> MemType+ -- ^ The memory model to use for non-synchronised @CRef@ operations.+ -> ConcT r n a+ -- ^ The computation to test.+ -> n Bool+autocheckWay way memtype = dejafusWay way memtype autocheckCases++-- | Predicates for the various autocheck functions.+autocheckCases :: Eq a => [(String, Predicate a)]+autocheckCases =+ [ ("Never Deadlocks", representative deadlocksNever)+ , ("No Exceptions", representative exceptionsNever)+ , ("Consistent Result", alwaysSame) -- already representative+ ]++-- | Check a predicate and print the result to stdout, return 'True'+-- if it passes.+--+-- A dejafu test has two parts: the program you are testing, and a+-- predicate to determine if the test passes. Predicates can look for+-- anything, including checking for some expected nondeterminism.+--+-- > > dejafu "Test Name" alwaysSame example+-- > [fail] Test Name+-- > "hello" S0----S1--S0--+-- >+-- > "world" S0----S2--S0--+--+-- @since 1.0.0.0+dejafu :: (MonadConc n, MonadIO n, MonadRef r n, Show b)+ => String+ -- ^ The name of the test.+ -> ProPredicate a b+ -- ^ The predicate to check.+ -> ConcT r n a+ -- ^ The computation to test.+ -> n Bool+dejafu = dejafuWay defaultWay defaultMemType++-- | Variant of 'dejafu' which takes a way to run the program and a+-- memory model.+--+-- > > import System.Random+-- >+-- > > dejafuWay (randomly (mkStdGen 0) 100) defaultMemType "Randomly!" alwaysSame example+-- > [fail] Randomly!+-- > "hello" S0----S1--S0--+-- >+-- > "world" S0----S2--S0--+-- >+-- > > dejafuWay (randomly (mkStdGen 1) 100) defaultMemType "Randomly!" alwaysSame example+-- > [fail] Randomly!+-- > "hello" S0----S1--S0--+-- >+-- > "world" S0----S2--S1-S0--+--+-- @since 1.0.0.0+dejafuWay :: (MonadConc n, MonadIO n, MonadRef r n, Show b)+ => Way+ -- ^ How to run the concurrent program.+ -> MemType+ -- ^ The memory model to use for non-synchronised @CRef@ operations.+ -> String+ -- ^ The name of the test.+ -> ProPredicate a b+ -- ^ The predicate to check.+ -> ConcT r n a+ -- ^ The computation to test.+ -> n Bool+dejafuWay = dejafuDiscard (const Nothing)++-- | Variant of 'dejafuWay' which can selectively discard results.+--+-- > > dejafuDiscard (\_ -> Just DiscardTrace) defaultWay defaultMemType "Discarding" alwaysSame example+-- > [fail] Discarding+-- > "hello" <trace discarded>+-- >+-- > "world" <trace discarded>+--+-- @since 1.0.0.0+dejafuDiscard :: (MonadConc n, MonadIO n, MonadRef r n, Show b)+ => (Either Failure a -> Maybe Discard)+ -- ^ Selectively discard results.+ -> Way+ -- ^ How to run the concurrent program.+ -> MemType+ -- ^ The memory model to use for non-synchronised @CRef@ operations.+ -> String+ -- ^ The name of the test.+ -> ProPredicate a b+ -- ^ The predicate to check.+ -> ConcT r n a+ -- ^ The computation to test.+ -> n Bool+dejafuDiscard discard way memtype name test conc = do+ let discarder = strengthenDiscard discard (pdiscard test)+ traces <- runSCTDiscard discarder way memtype conc+ liftIO $ doTest name (peval test traces)++-- | Variant of 'dejafu' which takes a collection of predicates to+-- test, returning 'True' if all pass.+--+-- > > dejafus [("A", alwaysSame), ("B", deadlocksNever)] example+-- > [fail] A+-- > "hello" S0----S1--S0--+-- >+-- > "world" S0----S2--S0--+-- > [pass] B+--+-- @since 1.0.0.0+dejafus :: (MonadConc n, MonadIO n, MonadRef r n, Show b)+ => [(String, ProPredicate a b)]+ -- ^ The list of predicates (with names) to check.+ -> ConcT r n a+ -- ^ The computation to test.+ -> n Bool+dejafus = dejafusWay defaultWay defaultMemType++-- | Variant of 'dejafus' which takes a way to run the program and a+-- memory model.+--+-- > > dejafusWay defaultWay SequentialConsistency [("A", alwaysSame), ("B", exceptionsNever)] relaxed+-- > [fail] A+-- > (False,True) S0---------S1----S0--S2----S0--+-- >+-- > (True,True) S0---------S1-P2----S1---S0---+-- >+-- > (True,False) S0---------S2----S1----S0---+-- > [pass] B+--+-- @since 1.0.0.0+dejafusWay :: (MonadConc n, MonadIO n, MonadRef r n, Show b)+ => Way+ -- ^ How to run the concurrent program.+ -> MemType+ -- ^ The memory model to use for non-synchronised @CRef@ operations.+ -> [(String, ProPredicate a b)]+ -- ^ The list of predicates (with names) to check.+ -> ConcT r n a+ -- ^ The computation to test.+ -> n Bool+dejafusWay way memtype tests conc = do+ traces <- runSCTDiscard discarder way memtype conc+ results <- mapM (\(name, test) -> liftIO . doTest name $ chk test traces) tests+ pure (and results)+ where+ discarder = foldr+ (weakenDiscard . pdiscard . snd)+ (const (Just DiscardResultAndTrace))+ tests++ -- for evaluating each individual predicate, we only want the+ -- results/traces it would not discard, but the traces set may+ -- include more than this if the different predicates have+ -- different discard functions, so we do another pass of+ -- discarding.+ chk p = peval p . mapMaybe go where+ go r@(efa, _) = case pdiscard p efa of+ Just DiscardResultAndTrace -> Nothing+ Just DiscardTrace -> Just (efa, [])+ Nothing -> Just r++-------------------------------------------------------------------------------+-- Test cases++-- | The results of a test, including the number of cases checked to+-- determine the final boolean outcome.+--+-- @since 1.0.0.0+data Result a = Result+ { _pass :: Bool+ -- ^ Whether the test passed or not.+ , _failures :: [(Either Failure a, Trace)]+ -- ^ The failing cases, if any.+ , _failureMsg :: String+ -- ^ A message to display on failure, if nonempty+ } deriving (Eq, Show)++instance NFData a => NFData (Result a) where+ rnf r = rnf ( _pass r+ , _failures r+ , _failureMsg r+ )++-- | A failed result, taking the given list of failures.+defaultFail :: [(Either Failure a, Trace)] -> Result a+defaultFail failures = Result False failures ""++-- | A passed result.+defaultPass :: Result a+defaultPass = Result True [] ""++instance Functor Result where+ fmap f r = r { _failures = map (first $ fmap f) $ _failures r }++instance Foldable Result where+ foldMap f r = foldMap f [a | (Right a, _) <- _failures r]++-- | Run a predicate over all executions within the default schedule+-- bounds.+--+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases. This may+-- affect which failing traces are reported, when there is a failure.+--+-- @since 1.0.0.0+runTest :: (MonadConc n, MonadRef r n)+ => ProPredicate a b+ -- ^ The predicate to check+ -> ConcT r n a+ -- ^ The computation to test+ -> n (Result b)+runTest = runTestWay defaultWay defaultMemType++-- | Variant of 'runTest' which takes a way to run the program and a+-- memory model.+--+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases. This may+-- affect which failing traces are reported, when there is a failure.+--+-- @since 1.0.0.0+runTestWay :: (MonadConc n, MonadRef r n)+ => Way+ -- ^ How to run the concurrent program.+ -> MemType+ -- ^ The memory model to use for non-synchronised @CRef@ operations.+ -> ProPredicate a b+ -- ^ The predicate to check+ -> ConcT r n a+ -- ^ The computation to test+ -> n (Result b)+runTestWay way memtype p conc =+ peval p <$> runSCTDiscard (pdiscard p) way memtype conc+++-------------------------------------------------------------------------------+-- Predicates++-- | A @Predicate@ is a function which collapses a list of results+-- into a 'Result', possibly discarding some on the way.+--+-- @Predicate@ cannot be a functor as the type parameter is used both+-- co- and contravariantly.+--+-- @since 1.0.0.0+type Predicate a = ProPredicate a a++-- | A @ProPredicate@ is a function which collapses a list of results+-- into a 'Result', possibly discarding some on the way.+--+-- @since 1.0.0.0+data ProPredicate a b = ProPredicate+ { pdiscard :: Either Failure a -> Maybe Discard+ -- ^ Selectively discard results before computing the result.+ , peval :: [(Either Failure a, Trace)] -> Result b+ -- ^ Compute the result with the un-discarded results.+ }++instance Profunctor ProPredicate where+ dimap f g p = ProPredicate+ { pdiscard = pdiscard p . fmap f+ , peval = fmap g . peval p . map (first (fmap f))+ }++instance Functor (ProPredicate x) where+ fmap = dimap id++-- | Reduce the list of failures in a @ProPredicate@ to one+-- representative trace for each unique result.+--+-- This may throw away \"duplicate\" failures which have a unique+-- cause but happen to manifest in the same way. However, it is+-- convenient for filtering out true duplicates.+--+-- @since 1.0.0.0+representative :: Eq b => ProPredicate a b -> ProPredicate a b+representative p = p+ { peval = \xs ->+ let result = peval p xs+ in result { _failures = simplestsBy (==) (_failures result) }+ }++-- | Check that a computation never aborts.+--+-- @since 1.0.0.0+abortsNever :: Predicate a+abortsNever = alwaysTrue (not . either (==Abort) (const False))++-- | Check that a computation always aborts.+--+-- @since 1.0.0.0+abortsAlways :: Predicate a+abortsAlways = alwaysTrue $ either (==Abort) (const False)++-- | Check that a computation aborts at least once.+--+-- @since 1.0.0.0+abortsSometimes :: Predicate a+abortsSometimes = somewhereTrue $ either (==Abort) (const False)++-- | Check that a computation never deadlocks.+--+-- @since 1.0.0.0+deadlocksNever :: Predicate a+deadlocksNever = alwaysTrue (not . either isDeadlock (const False))++-- | Check that a computation always deadlocks.+--+-- @since 1.0.0.0+deadlocksAlways :: Predicate a+deadlocksAlways = alwaysTrue $ either isDeadlock (const False)++-- | Check that a computation deadlocks at least once.+--+-- @since 1.0.0.0+deadlocksSometimes :: Predicate a+deadlocksSometimes = somewhereTrue $ either isDeadlock (const False)++-- | Check that a computation never fails with an uncaught exception.+--+-- @since 1.0.0.0+exceptionsNever :: Predicate a+exceptionsNever = alwaysTrue (not . either isUncaughtException (const False))++-- | Check that a computation always fails with an uncaught exception.+--+-- @since 1.0.0.0+exceptionsAlways :: Predicate a+exceptionsAlways = alwaysTrue $ either isUncaughtException (const False)++-- | Check that a computation fails with an uncaught exception at least once.+--+-- @since 1.0.0.0+exceptionsSometimes :: Predicate a+exceptionsSometimes = somewhereTrue $ either isUncaughtException (const False)++-- | Check that the result of a computation is always the same. In+-- particular this means either: (a) it always fails in the same way,+-- or (b) it never fails and the values returned are all equal.+--+-- > alwaysSame = alwaysSameBy (==)+--+-- @since 1.0.0.0+alwaysSame :: Eq a => Predicate a+alwaysSame = alwaysSameBy (==)++-- | Check that the result of a computation is always the same by+-- comparing the result of a function on every result.+--+-- > alwaysSameOn = alwaysSameBy ((==) `on` f)+--+-- @since 1.0.0.0+alwaysSameOn :: Eq b => (Either Failure a -> b) -> Predicate a+alwaysSameOn f = alwaysSameBy ((==) `on` f)++-- | Check that the result of a computation is always the same, using+-- some transformation on results.+--+-- @since 1.0.0.0+alwaysSameBy :: (Either Failure a -> Either Failure a -> Bool) -> Predicate a+alwaysSameBy f = ProPredicate+ { pdiscard = const Nothing+ , peval = \xs -> case simplestsBy f xs of+ [] -> defaultPass+ [_] -> defaultPass+ xs' -> defaultFail xs'+ }++-- | Check that the result of a computation is not always the same.+--+-- @since 1.0.0.0+notAlwaysSame :: Eq a => Predicate a+notAlwaysSame = ProPredicate+ { pdiscard = const Nothing+ , peval = \case+ [x] -> defaultFail [x]+ xs -> go xs (defaultFail [])+ }+ where+ go [y1,y2] res+ | fst y1 /= fst y2 = res { _pass = True }+ | otherwise = res { _failures = y1 : y2 : _failures res }+ go (y1:y2:ys) res+ | fst y1 /= fst y2 = go (y2:ys) res { _pass = True }+ | otherwise = go (y2:ys) res { _failures = y1 : y2 : _failures res }+ go _ res = res++-- | Check that a @Maybe@-producing function always returns 'Nothing'.+--+-- @since 1.0.0.0+alwaysNothing :: (Either Failure a -> Maybe (Either Failure b)) -> ProPredicate a b+alwaysNothing f = ProPredicate+ { pdiscard = maybe (Just DiscardResultAndTrace) (const Nothing) . f+ , peval = \xs ->+ let failures = mapMaybe (\(efa,trc) -> (,trc) <$> f efa) xs+ in Result (null failures) failures ""+ }++-- | Check that the result of a unary boolean predicate is always+-- true.+--+-- @since 1.0.0.0+alwaysTrue :: (Either Failure a -> Bool) -> Predicate a+alwaysTrue p = alwaysNothing (\efa -> if p efa then Nothing else Just efa)++-- | Check that a @Maybe@-producing function returns 'Nothing' at+-- least once.+--+-- @since 1.0.0.0+somewhereNothing :: (Either Failure a -> Maybe (Either Failure b)) -> ProPredicate a b+somewhereNothing f = ProPredicate+ { pdiscard = maybe (Just DiscardTrace) (const Nothing) . f+ , peval = \xs ->+ let failures = map (\(efa,trc) -> (,trc) <$> f efa) xs+ in Result (any isNothing failures) (catMaybes failures) ""+ }++-- | Check that the result of a unary boolean predicate is true at+-- least once.+--+-- @since 1.0.0.0+somewhereTrue :: (Either Failure a -> Bool) -> Predicate a+somewhereTrue p = somewhereNothing (\efa -> if p efa then Nothing else Just efa)++-- | Predicate for when there is a known set of results where every+-- result must be exhibited at least once.+--+-- @since 1.0.0.0+gives :: (Eq a, Show a) => [Either Failure a] -> Predicate a+gives expected = ProPredicate+ { pdiscard = \efa -> if efa `elem` expected then Just DiscardTrace else Nothing+ , peval = \xs -> go expected [] xs $ defaultFail (failures xs)+ }+ where+ go waitingFor alreadySeen ((x, _):xs) res+ -- If it's a result we're waiting for, move it to the+ -- @alreadySeen@ list and continue.+ | x `elem` waitingFor = go (filter (/=x) waitingFor) (x:alreadySeen) xs res+ -- If it's a result we've already seen, continue.+ | x `elem` alreadySeen = go waitingFor alreadySeen xs res+ -- If it's not a result we expected, fail.+ | otherwise = res++ go [] _ [] res = res { _pass = True }+ go es _ [] res = res { _failureMsg = unlines $ map (\e -> "Expected: " ++ show e) es }++ failures = filter (\(r, _) -> r `notElem` expected)++-- | Variant of 'gives' that doesn't allow for expected failures.+--+-- @since 1.0.0.0+gives' :: (Eq a, Show a) => [a] -> Predicate a+gives' = gives . map Right+++-------------------------------------------------------------------------------+-- Utils++-- | Run a test and print to stdout+doTest :: Show a => String -> Result a -> IO Bool+doTest name result = do+ if _pass result+ then+ -- Display a pass message.+ putStrLn ("\27[32m[pass]\27[0m " ++ name)+ else do+ -- Display a failure message, and the first 5 (simplified) failed traces+ putStrLn ("\27[31m[fail]\27[0m " ++ name)++ unless (null $ _failureMsg result) $+ putStrLn $ _failureMsg result++ let failures = _failures result+ let output = map (\(r, t) -> putStrLn . indent $ either showFail show r ++ " " ++ showTrace t) $ take 5 failures+ sequence_ $ intersperse (putStrLn "") output+ when (moreThan 5 failures) $+ putStrLn (indent "...")++ pure (_pass result)++-- | Check if a list is longer than some value, without needing to+-- compute the entire length.+moreThan :: Int -> [a] -> Bool+moreThan n [] = n < 0+moreThan 0 _ = True+moreThan n (_:rest) = moreThan (n-1) rest -- | Indent every line of a string. indent :: String -> String
− Test/DejaFu/Common.hs
@@ -1,1070 +0,0 @@--- |--- Module : Test.DejaFu.Common--- Copyright : (c) 2016 Michael Walker--- License : MIT--- Maintainer : Michael Walker <mike@barrucadu.co.uk>--- Stability : experimental--- Portability : portable------ Common types and functions used throughout DejaFu.-module Test.DejaFu.Common- ( -- * Identifiers- ThreadId(..)- , CRefId(..)- , MVarId(..)- , TVarId(..)- , initialThread- -- ** Identifier source- , IdSource(..)- , nextCRId- , nextMVId- , nextTVId- , nextTId- , initialIdSource-- -- * Actions- -- ** Thread actions- , ThreadAction(..)- , isBlock- , tvarsOf- , tvarsWritten- , tvarsRead- -- ** Lookahead- , Lookahead(..)- , rewind- , willRelease- -- ** Simplified actions- , ActionType(..)- , isBarrier- , isCommit- , synchronises- , crefOf- , mvarOf- , simplifyAction- , simplifyLookahead- -- ** STM actions- , TTrace- , TAction(..)-- -- * Traces- , Trace- , Decision(..)- , showTrace- , threadNames- , preEmpCount-- -- * Failures- , Failure(..)- , isInternalError- , isAbort- , isDeadlock- , isUncaughtException- , isIllegalSubconcurrency- , showFail-- -- * Memory models- , MemType(..)-- -- * Miscellaneous- , MonadFailException(..)- , runRefCont- , ehead- , etail- , eidx- , efromJust- , efromList- , fatal- ) where--import Control.DeepSeq (NFData(..))-import Control.Exception (Exception(..), MaskingState(..),- SomeException, displayException)-import Control.Monad.Ref (MonadRef(..))-import Data.Function (on)-import Data.List (intercalate)-import Data.List.NonEmpty (NonEmpty(..))-import Data.Maybe (fromMaybe, mapMaybe)-import Data.Set (Set)-import qualified Data.Set as S------------------------------------------------------------------------------------ Identifiers---- | Every live thread has a unique identitifer.------ The @Eq@ and @Ord@ instances only consider the int, not the name.------ @since 0.4.0.0-data ThreadId = ThreadId (Maybe String) {-# UNPACK #-} !Int---- | Previously this was a derived instance.------ @since 0.7.2.0-instance Eq ThreadId where- (ThreadId _ i) == (ThreadId _ j) = i == j--instance Ord ThreadId where- compare (ThreadId _ i) (ThreadId _ j) = compare i j--instance Show ThreadId where- show (ThreadId (Just n) _) = n- show (ThreadId Nothing i) = show i---- | @since 0.5.1.0-instance NFData ThreadId where- rnf (ThreadId n i) = rnf (n, i)---- | Every @CRef@ has a unique identifier.------ The @Eq@ and @Ord@ instances only consider the int, not the name.------ @since 0.4.0.0-data CRefId = CRefId (Maybe String) {-# UNPACK #-} !Int---- | Previously this was a derived instance.------ @since 0.7.2.0-instance Eq CRefId where- (CRefId _ i) == (CRefId _ j) = i == j--instance Ord CRefId where- compare (CRefId _ i) (CRefId _ j) = compare i j--instance Show CRefId where- show (CRefId (Just n) _) = n- show (CRefId Nothing i) = show i---- | @since 0.5.1.0-instance NFData CRefId where- rnf (CRefId n i) = rnf (n, i)---- | Every @MVar@ has a unique identifier.------ The @Eq@ and @Ord@ instances only consider the int, not the name.------ @since 0.4.0.0-data MVarId = MVarId (Maybe String) {-# UNPACK #-} !Int---- | Previously this was a derived instance.------ @since 0.7.2.0-instance Eq MVarId where- (MVarId _ i) == (MVarId _ j) = i == j--instance Ord MVarId where- compare (MVarId _ i) (MVarId _ j) = compare i j--instance Show MVarId where- show (MVarId (Just n) _) = n- show (MVarId Nothing i) = show i---- | @since 0.5.1.0-instance NFData MVarId where- rnf (MVarId n i) = rnf (n, i)---- | Every @TVar@ has a unique identifier.------ The @Eq@ and @Ord@ instances only consider the int, not the name.------ @since 0.4.0.0-data TVarId = TVarId (Maybe String) {-# UNPACK #-} !Int---- | Previously this was a derived instance.------ @since 0.7.2.0-instance Eq TVarId where- (TVarId _ i) == (TVarId _ j) = i == j--instance Ord TVarId where- compare (TVarId _ i) (TVarId _ j) = compare i j--instance Show TVarId where- show (TVarId (Just n) _) = n- show (TVarId Nothing i) = show i---- | @since 0.5.1.0-instance NFData TVarId where- rnf (TVarId n i) = rnf (n, i)---- | The ID of the initial thread.------ @since 0.4.0.0-initialThread :: ThreadId-initialThread = ThreadId (Just "main") 0-------------------------------------------- Identifier source---- | The number of ID parameters was getting a bit unwieldy, so this--- hides them all away.------ @since 0.4.0.0-data IdSource = Id- { _nextCRId :: Int- , _nextMVId :: Int- , _nextTVId :: Int- , _nextTId :: Int- , _usedCRNames :: [String]- , _usedMVNames :: [String]- , _usedTVNames :: [String]- , _usedTNames :: [String]- } deriving (Eq, Ord, Show)---- | @since 0.5.1.0-instance NFData IdSource where- rnf idsource = rnf ( _nextCRId idsource- , _nextMVId idsource- , _nextTVId idsource- , _nextTId idsource- , _usedCRNames idsource- , _usedMVNames idsource- , _usedTVNames idsource- , _usedTNames idsource- )---- | Get the next free 'CRefId'.------ @since 0.4.0.0-nextCRId :: String -> IdSource -> (IdSource, CRefId)-nextCRId name idsource = (newIdSource, newCRId) where- newIdSource = idsource { _nextCRId = newId, _usedCRNames = newUsed }- newCRId = CRefId newName newId- newId = _nextCRId idsource + 1- (newName, newUsed) = nextId name (_usedCRNames idsource)---- | Get the next free 'MVarId'.------ @since 0.4.0.0-nextMVId :: String -> IdSource -> (IdSource, MVarId)-nextMVId name idsource = (newIdSource, newMVId) where- newIdSource = idsource { _nextMVId = newId, _usedMVNames = newUsed }- newMVId = MVarId newName newId- newId = _nextMVId idsource + 1- (newName, newUsed) = nextId name (_usedMVNames idsource)---- | Get the next free 'TVarId'.------ @since 0.4.0.0-nextTVId :: String -> IdSource -> (IdSource, TVarId)-nextTVId name idsource = (newIdSource, newTVId) where- newIdSource = idsource { _nextTVId = newId, _usedTVNames = newUsed }- newTVId = TVarId newName newId- newId = _nextTVId idsource + 1- (newName, newUsed) = nextId name (_usedTVNames idsource)---- | Get the next free 'ThreadId'.------ @since 0.4.0.0-nextTId :: String -> IdSource -> (IdSource, ThreadId)-nextTId name idsource = (newIdSource, newTId) where- newIdSource = idsource { _nextTId = newId, _usedTNames = newUsed }- newTId = ThreadId newName newId- newId = _nextTId idsource + 1- (newName, newUsed) = nextId name (_usedTNames idsource)---- | The initial ID source.------ @since 0.4.0.0-initialIdSource :: IdSource-initialIdSource = Id 0 0 0 0 [] [] [] []------------------------------------------------------------------------------------ Actions-------------------------------------------- Thread actions---- | All the actions that a thread can perform.------ @since 0.9.0.0-data ThreadAction =- Fork ThreadId- -- ^ Start a new thread.- | MyThreadId- -- ^ Get the 'ThreadId' of the current thread.- | GetNumCapabilities Int- -- ^ Get the number of Haskell threads that can run simultaneously.- | SetNumCapabilities Int- -- ^ Set the number of Haskell threads that can run simultaneously.- | Yield- -- ^ Yield the current thread.- | ThreadDelay Int- -- ^ Yield/delay the current thread.- | NewMVar MVarId- -- ^ Create a new 'MVar'.- | PutMVar MVarId [ThreadId]- -- ^ Put into a 'MVar', possibly waking up some threads.- | BlockedPutMVar MVarId- -- ^ Get blocked on a put.- | TryPutMVar MVarId Bool [ThreadId]- -- ^ Try to put into a 'MVar', possibly waking up some threads.- | ReadMVar MVarId- -- ^ Read from a 'MVar'.- | TryReadMVar MVarId Bool- -- ^ Try to read from a 'MVar'.- | BlockedReadMVar MVarId- -- ^ Get blocked on a read.- | TakeMVar MVarId [ThreadId]- -- ^ Take from a 'MVar', possibly waking up some threads.- | BlockedTakeMVar MVarId- -- ^ Get blocked on a take.- | TryTakeMVar MVarId Bool [ThreadId]- -- ^ Try to take from a 'MVar', possibly waking up some threads.- | NewCRef CRefId- -- ^ Create a new 'CRef'.- | ReadCRef CRefId- -- ^ Read from a 'CRef'.- | ReadCRefCas CRefId- -- ^ Read from a 'CRef' for a future compare-and-swap.- | ModCRef CRefId- -- ^ Modify a 'CRef'.- | ModCRefCas CRefId- -- ^ Modify a 'CRef' using a compare-and-swap.- | WriteCRef CRefId- -- ^ Write to a 'CRef' without synchronising.- | CasCRef CRefId Bool- -- ^ Attempt to to a 'CRef' using a compare-and-swap, synchronising- -- it.- | CommitCRef ThreadId CRefId- -- ^ Commit the last write to the given 'CRef' by the given thread,- -- so that all threads can see the updated value.- | STM TTrace [ThreadId]- -- ^ An STM transaction was executed, possibly waking up some- -- threads.- | BlockedSTM TTrace- -- ^ Got blocked in an STM transaction.- | Catching- -- ^ Register a new exception handler- | PopCatching- -- ^ Pop the innermost exception handler from the stack.- | Throw- -- ^ Throw an exception.- | ThrowTo ThreadId- -- ^ Throw an exception to a thread.- | BlockedThrowTo ThreadId- -- ^ Get blocked on a 'throwTo'.- | Killed- -- ^ Killed by an uncaught exception.- | SetMasking Bool MaskingState- -- ^ Set the masking state. If 'True', this is being used to set the- -- masking state to the original state in the argument passed to a- -- 'mask'ed function.- | ResetMasking Bool MaskingState- -- ^ Return to an earlier masking state. If 'True', this is being- -- used to return to the state of the masked block in the argument- -- passed to a 'mask'ed function.- | LiftIO- -- ^ Lift an IO action. Note that this can only happen with- -- 'ConcIO'.- | Return- -- ^ A 'return' or 'pure' action was executed.- | Stop- -- ^ Cease execution and terminate.- | Subconcurrency- -- ^ Start executing an action with @subconcurrency@.- | StopSubconcurrency- -- ^ Stop executing an action with @subconcurrency@.- deriving (Eq, Show)--instance NFData ThreadAction where- rnf (Fork t) = rnf t- rnf (ThreadDelay n) = rnf n- rnf (GetNumCapabilities c) = rnf c- rnf (SetNumCapabilities c) = rnf c- rnf (NewMVar m) = rnf m- rnf (PutMVar m ts) = rnf (m, ts)- rnf (BlockedPutMVar m) = rnf m- rnf (TryPutMVar m b ts) = rnf (m, b, ts)- rnf (ReadMVar m) = rnf m- rnf (TryReadMVar m b) = rnf (m, b)- rnf (BlockedReadMVar m) = rnf m- rnf (TakeMVar m ts) = rnf (m, ts)- rnf (BlockedTakeMVar m) = rnf m- rnf (TryTakeMVar m b ts) = rnf (m, b, ts)- rnf (NewCRef c) = rnf c- rnf (ReadCRef c) = rnf c- rnf (ReadCRefCas c) = rnf c- rnf (ModCRef c) = rnf c- rnf (ModCRefCas c) = rnf c- rnf (WriteCRef c) = rnf c- rnf (CasCRef c b) = rnf (c, b)- rnf (CommitCRef t c) = rnf (t, c)- rnf (STM tr ts) = rnf (tr, ts)- rnf (BlockedSTM tr) = rnf tr- rnf (ThrowTo t) = rnf t- rnf (BlockedThrowTo t) = rnf t- rnf (SetMasking b m) = b `seq` m `seq` ()- rnf (ResetMasking b m) = b `seq` m `seq` ()- rnf a = a `seq` ()---- | Check if a @ThreadAction@ immediately blocks.------ @since 0.4.0.0-isBlock :: ThreadAction -> Bool-isBlock (BlockedThrowTo _) = True-isBlock (BlockedTakeMVar _) = True-isBlock (BlockedReadMVar _) = True-isBlock (BlockedPutMVar _) = True-isBlock (BlockedSTM _) = True-isBlock _ = False---- | Get the @TVar@s affected by a @ThreadAction@.------ @since 0.4.0.0-tvarsOf :: ThreadAction -> Set TVarId-tvarsOf act = tvarsRead act `S.union` tvarsWritten act---- | Get the @TVar@s a transaction wrote to (or would have, if it--- didn't @retry@).------ @since 0.9.0.2-tvarsWritten :: ThreadAction -> Set TVarId-tvarsWritten act = S.fromList $ case act of- STM trc _ -> concatMap tvarsOf' trc- BlockedSTM trc -> concatMap tvarsOf' trc- _ -> []-- where- tvarsOf' (TWrite tv) = [tv]- tvarsOf' (TOrElse ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)- tvarsOf' (TCatch ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)- tvarsOf' _ = []---- | Get the @TVar@s a transaction read from.------ @since 0.9.0.2-tvarsRead :: ThreadAction -> Set TVarId-tvarsRead act = S.fromList $ case act of- STM trc _ -> concatMap tvarsOf' trc- BlockedSTM trc -> concatMap tvarsOf' trc- _ -> []-- where- tvarsOf' (TRead tv) = [tv]- tvarsOf' (TOrElse ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)- tvarsOf' (TCatch ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)- tvarsOf' _ = []-------------------------------------------- Lookahead---- | A one-step look-ahead at what a thread will do next.------ @since 0.9.0.0-data Lookahead =- WillFork- -- ^ Will start a new thread.- | WillMyThreadId- -- ^ Will get the 'ThreadId'.- | WillGetNumCapabilities- -- ^ Will get the number of Haskell threads that can run- -- simultaneously.- | WillSetNumCapabilities Int- -- ^ Will set the number of Haskell threads that can run- -- simultaneously.- | WillYield- -- ^ Will yield the current thread.- | WillThreadDelay Int- -- ^ Will yield/delay the current thread.- | WillNewMVar- -- ^ Will create a new 'MVar'.- | WillPutMVar MVarId- -- ^ Will put into a 'MVar', possibly waking up some threads.- | WillTryPutMVar MVarId- -- ^ Will try to put into a 'MVar', possibly waking up some threads.- | WillReadMVar MVarId- -- ^ Will read from a 'MVar'.- | WillTryReadMVar MVarId- -- ^ Will try to read from a 'MVar'.- | WillTakeMVar MVarId- -- ^ Will take from a 'MVar', possibly waking up some threads.- | WillTryTakeMVar MVarId- -- ^ Will try to take from a 'MVar', possibly waking up some threads.- | WillNewCRef- -- ^ Will create a new 'CRef'.- | WillReadCRef CRefId- -- ^ Will read from a 'CRef'.- | WillReadCRefCas CRefId- -- ^ Will read from a 'CRef' for a future compare-and-swap.- | WillModCRef CRefId- -- ^ Will modify a 'CRef'.- | WillModCRefCas CRefId- -- ^ Will modify a 'CRef' using a compare-and-swap.- | WillWriteCRef CRefId- -- ^ Will write to a 'CRef' without synchronising.- | WillCasCRef CRefId- -- ^ Will attempt to to a 'CRef' using a compare-and-swap,- -- synchronising it.- | WillCommitCRef ThreadId CRefId- -- ^ Will commit the last write by the given thread to the 'CRef'.- | WillSTM- -- ^ Will execute an STM transaction, possibly waking up some- -- threads.- | WillCatching- -- ^ Will register a new exception handler- | WillPopCatching- -- ^ Will pop the innermost exception handler from the stack.- | WillThrow- -- ^ Will throw an exception.- | WillThrowTo ThreadId- -- ^ Will throw an exception to a thread.- | WillSetMasking Bool MaskingState- -- ^ Will set the masking state. If 'True', this is being used to- -- set the masking state to the original state in the argument- -- passed to a 'mask'ed function.- | WillResetMasking Bool MaskingState- -- ^ Will return to an earlier masking state. If 'True', this is- -- being used to return to the state of the masked block in the- -- argument passed to a 'mask'ed function.- | WillLiftIO- -- ^ Will lift an IO action. Note that this can only happen with- -- 'ConcIO'.- | WillReturn- -- ^ Will execute a 'return' or 'pure' action.- | WillStop- -- ^ Will cease execution and terminate.- | WillSubconcurrency- -- ^ Will execute an action with @subconcurrency@.- | WillStopSubconcurrency- -- ^ Will stop executing an extion with @subconcurrency@.- deriving (Eq, Show)--instance NFData Lookahead where- rnf (WillThreadDelay n) = rnf n- rnf (WillSetNumCapabilities c) = rnf c- rnf (WillPutMVar m) = rnf m- rnf (WillTryPutMVar m) = rnf m- rnf (WillReadMVar m) = rnf m- rnf (WillTryReadMVar m) = rnf m- rnf (WillTakeMVar m) = rnf m- rnf (WillTryTakeMVar m) = rnf m- rnf (WillReadCRef c) = rnf c- rnf (WillReadCRefCas c) = rnf c- rnf (WillModCRef c) = rnf c- rnf (WillModCRefCas c) = rnf c- rnf (WillWriteCRef c) = rnf c- rnf (WillCasCRef c) = rnf c- rnf (WillCommitCRef t c) = rnf (t, c)- rnf (WillThrowTo t) = rnf t- rnf (WillSetMasking b m) = b `seq` m `seq` ()- rnf (WillResetMasking b m) = b `seq` m `seq` ()- rnf l = l `seq` ()---- | Convert a 'ThreadAction' into a 'Lookahead': \"rewind\" what has--- happened. 'Killed' has no 'Lookahead' counterpart.------ @since 0.4.0.0-rewind :: ThreadAction -> Maybe Lookahead-rewind (Fork _) = Just WillFork-rewind MyThreadId = Just WillMyThreadId-rewind (GetNumCapabilities _) = Just WillGetNumCapabilities-rewind (SetNumCapabilities i) = Just (WillSetNumCapabilities i)-rewind Yield = Just WillYield-rewind (ThreadDelay n) = Just (WillThreadDelay n)-rewind (NewMVar _) = Just WillNewMVar-rewind (PutMVar c _) = Just (WillPutMVar c)-rewind (BlockedPutMVar c) = Just (WillPutMVar c)-rewind (TryPutMVar c _ _) = Just (WillTryPutMVar c)-rewind (ReadMVar c) = Just (WillReadMVar c)-rewind (BlockedReadMVar c) = Just (WillReadMVar c)-rewind (TryReadMVar c _) = Just (WillTryReadMVar c)-rewind (TakeMVar c _) = Just (WillTakeMVar c)-rewind (BlockedTakeMVar c) = Just (WillTakeMVar c)-rewind (TryTakeMVar c _ _) = Just (WillTryTakeMVar c)-rewind (NewCRef _) = Just WillNewCRef-rewind (ReadCRef c) = Just (WillReadCRef c)-rewind (ReadCRefCas c) = Just (WillReadCRefCas c)-rewind (ModCRef c) = Just (WillModCRef c)-rewind (ModCRefCas c) = Just (WillModCRefCas c)-rewind (WriteCRef c) = Just (WillWriteCRef c)-rewind (CasCRef c _) = Just (WillCasCRef c)-rewind (CommitCRef t c) = Just (WillCommitCRef t c)-rewind (STM _ _) = Just WillSTM-rewind (BlockedSTM _) = Just WillSTM-rewind Catching = Just WillCatching-rewind PopCatching = Just WillPopCatching-rewind Throw = Just WillThrow-rewind (ThrowTo t) = Just (WillThrowTo t)-rewind (BlockedThrowTo t) = Just (WillThrowTo t)-rewind Killed = Nothing-rewind (SetMasking b m) = Just (WillSetMasking b m)-rewind (ResetMasking b m) = Just (WillResetMasking b m)-rewind LiftIO = Just WillLiftIO-rewind Return = Just WillReturn-rewind Stop = Just WillStop-rewind Subconcurrency = Just WillSubconcurrency-rewind StopSubconcurrency = Just WillStopSubconcurrency---- | Check if an operation could enable another thread.------ @since 0.4.0.0-willRelease :: Lookahead -> Bool-willRelease WillFork = True-willRelease WillYield = True-willRelease (WillThreadDelay _) = True-willRelease (WillPutMVar _) = True-willRelease (WillTryPutMVar _) = True-willRelease (WillReadMVar _) = True-willRelease (WillTakeMVar _) = True-willRelease (WillTryTakeMVar _) = True-willRelease WillSTM = True-willRelease WillThrow = True-willRelease (WillSetMasking _ _) = True-willRelease (WillResetMasking _ _) = True-willRelease WillStop = True-willRelease _ = False-------------------------------------------- Simplified actions---- | A simplified view of the possible actions a thread can perform.------ @since 0.4.0.0-data ActionType =- UnsynchronisedRead CRefId- -- ^ A 'readCRef' or a 'readForCAS'.- | UnsynchronisedWrite CRefId- -- ^ A 'writeCRef'.- | UnsynchronisedOther- -- ^ Some other action which doesn't require cross-thread- -- communication.- | PartiallySynchronisedCommit CRefId- -- ^ A commit.- | PartiallySynchronisedWrite CRefId- -- ^ A 'casCRef'- | PartiallySynchronisedModify CRefId- -- ^ A 'modifyCRefCAS'- | SynchronisedModify CRefId- -- ^ An 'atomicModifyCRef'.- | SynchronisedRead MVarId- -- ^ A 'readMVar' or 'takeMVar' (or @try@/@blocked@ variants).- | SynchronisedWrite MVarId- -- ^ A 'putMVar' (or @try@/@blocked@ variant).- | SynchronisedOther- -- ^ Some other action which does require cross-thread- -- communication.- deriving (Eq, Show)---- | @since 0.5.1.0-instance NFData ActionType where- rnf (UnsynchronisedRead c) = rnf c- rnf (UnsynchronisedWrite c) = rnf c- rnf (PartiallySynchronisedCommit c) = rnf c- rnf (PartiallySynchronisedWrite c) = rnf c- rnf (PartiallySynchronisedModify c) = rnf c- rnf (SynchronisedModify c) = rnf c- rnf (SynchronisedRead m) = rnf m- rnf (SynchronisedWrite m) = rnf m- rnf a = a `seq` ()---- | Check if an action imposes a write barrier.------ @since 0.4.0.0-isBarrier :: ActionType -> Bool-isBarrier (SynchronisedModify _) = True-isBarrier (SynchronisedRead _) = True-isBarrier (SynchronisedWrite _) = True-isBarrier SynchronisedOther = True-isBarrier _ = False---- | Check if an action commits a given 'CRef'.------ @since 0.4.0.0-isCommit :: ActionType -> CRefId -> Bool-isCommit (PartiallySynchronisedCommit c) r = c == r-isCommit (PartiallySynchronisedWrite c) r = c == r-isCommit (PartiallySynchronisedModify c) r = c == r-isCommit _ _ = False---- | Check if an action synchronises a given 'CRef'.------ @since 0.4.0.0-synchronises :: ActionType -> CRefId -> Bool-synchronises a r = isCommit a r || isBarrier a---- | Get the 'CRef' affected.------ @since 0.4.0.0-crefOf :: ActionType -> Maybe CRefId-crefOf (UnsynchronisedRead r) = Just r-crefOf (UnsynchronisedWrite r) = Just r-crefOf (SynchronisedModify r) = Just r-crefOf (PartiallySynchronisedCommit r) = Just r-crefOf (PartiallySynchronisedWrite r) = Just r-crefOf (PartiallySynchronisedModify r) = Just r-crefOf _ = Nothing---- | Get the 'MVar' affected.------ @since 0.4.0.0-mvarOf :: ActionType -> Maybe MVarId-mvarOf (SynchronisedRead c) = Just c-mvarOf (SynchronisedWrite c) = Just c-mvarOf _ = Nothing---- | Throw away information from a 'ThreadAction' and give a--- simplified view of what is happening.------ This is used in the SCT code to help determine interesting--- alternative scheduling decisions.------ @since 0.4.0.0-simplifyAction :: ThreadAction -> ActionType-simplifyAction = maybe UnsynchronisedOther simplifyLookahead . rewind---- | Variant of 'simplifyAction' that takes a 'Lookahead'.------ @since 0.4.0.0-simplifyLookahead :: Lookahead -> ActionType-simplifyLookahead (WillPutMVar c) = SynchronisedWrite c-simplifyLookahead (WillTryPutMVar c) = SynchronisedWrite c-simplifyLookahead (WillReadMVar c) = SynchronisedRead c-simplifyLookahead (WillTryReadMVar c) = SynchronisedRead c-simplifyLookahead (WillTakeMVar c) = SynchronisedRead c-simplifyLookahead (WillTryTakeMVar c) = SynchronisedRead c-simplifyLookahead (WillReadCRef r) = UnsynchronisedRead r-simplifyLookahead (WillReadCRefCas r) = UnsynchronisedRead r-simplifyLookahead (WillModCRef r) = SynchronisedModify r-simplifyLookahead (WillModCRefCas r) = PartiallySynchronisedModify r-simplifyLookahead (WillWriteCRef r) = UnsynchronisedWrite r-simplifyLookahead (WillCasCRef r) = PartiallySynchronisedWrite r-simplifyLookahead (WillCommitCRef _ r) = PartiallySynchronisedCommit r-simplifyLookahead WillSTM = SynchronisedOther-simplifyLookahead (WillThrowTo _) = SynchronisedOther-simplifyLookahead _ = UnsynchronisedOther-------------------------------------------- STM actions---- | A trace of an STM transaction is just a list of actions that--- occurred, as there are no scheduling decisions to make.------ @since 0.4.0.0-type TTrace = [TAction]---- | All the actions that an STM transaction can perform.------ @since 0.8.0.0-data TAction =- TNew TVarId- -- ^ Create a new @TVar@- | TRead TVarId- -- ^ Read from a @TVar@.- | TWrite TVarId- -- ^ Write to a @TVar@.- | TRetry- -- ^ Abort and discard effects.- | TOrElse TTrace (Maybe TTrace)- -- ^ Execute a transaction until it succeeds (@STMStop@) or aborts- -- (@STMRetry@) and, if it aborts, execute the other transaction.- | TThrow- -- ^ Throw an exception, abort, and discard effects.- | TCatch TTrace (Maybe TTrace)- -- ^ Execute a transaction until it succeeds (@STMStop@) or aborts- -- (@STMThrow@). If the exception is of the appropriate type, it is- -- handled and execution continues; otherwise aborts, propagating- -- the exception upwards.- | TStop- -- ^ Terminate successfully and commit effects.- deriving (Eq, Show)---- | @since 0.5.1.0-instance NFData TAction where- rnf (TRead t) = rnf t- rnf (TWrite t) = rnf t- rnf (TOrElse tr mtr) = rnf (tr, mtr)- rnf (TCatch tr mtr) = rnf (tr, mtr)- rnf ta = ta `seq` ()------------------------------------------------------------------------------------ Traces---- | One of the outputs of the runner is a @Trace@, which is a log of--- decisions made, all the runnable threads and what they would do,--- and the action a thread took in its step.------ @since 0.8.0.0-type Trace- = [(Decision, [(ThreadId, Lookahead)], ThreadAction)]---- | Scheduling decisions are based on the state of the running--- program, and so we can capture some of that state in recording what--- specific decision we made.------ @since 0.5.0.0-data Decision =- Start ThreadId- -- ^ Start a new thread, because the last was blocked (or it's the- -- start of computation).- | Continue- -- ^ Continue running the last thread for another step.- | SwitchTo ThreadId- -- ^ Pre-empt the running thread, and switch to another.- deriving (Eq, Show)---- | @since 0.5.1.0-instance NFData Decision where- rnf (Start t) = rnf t- rnf (SwitchTo t) = rnf t- rnf d = d `seq` ()---- | Pretty-print a trace, including a key of the thread IDs (not--- including thread 0). Each line of the key is indented by two--- spaces.------ @since 0.5.0.0-showTrace :: Trace -> String-showTrace [] = "<trace discarded>"-showTrace trc = intercalate "\n" $ go False trc : strkey where- go _ ((_,_,CommitCRef _ _):rest) = "C-" ++ go False rest- go _ ((Start (ThreadId _ i),_,a):rest) = "S" ++ show i ++ "-" ++ go (didYield a) rest- go y ((SwitchTo (ThreadId _ i),_,a):rest) = (if y then "p" else "P") ++ show i ++ "-" ++ go (didYield a) rest- go _ ((Continue,_,a):rest) = '-' : go (didYield a) rest- go _ _ = ""-- strkey =- [" " ++ show i ++ ": " ++ name | (i, name) <- threadNames trc]-- didYield Yield = True- didYield (ThreadDelay _) = True- didYield _ = False---- | Get all named threads in the trace.------ @since 0.7.3.0-threadNames :: Trace -> [(Int, String)]-threadNames = mapMaybe go where- go (_, _, Fork (ThreadId (Just name) i)) = Just (i, name)- go _ = Nothing---- | Count the number of pre-emptions in a schedule prefix.------ Commit threads complicate this a bit. Conceptually, commits are--- happening truly in parallel, nondeterministically. The commit--- thread implementation is just there to unify the two sources of--- nondeterminism: commit timing and thread scheduling.------ SO, we don't count a switch TO a commit thread as a--- preemption. HOWEVER, the switch FROM a commit thread counts as a--- preemption if it is not to the thread that the commit interrupted.------ @since 0.5.0.0-preEmpCount :: [(Decision, ThreadAction)]- -> (Decision, Lookahead)- -> Int-preEmpCount (x:xs) (d, _) = go initialThread x xs where- go _ (_, Yield) (r@(SwitchTo t, _):rest) = go t r rest- go _ (_, ThreadDelay _) (r@(SwitchTo t, _):rest) = go t r rest- go tid prior (r@(SwitchTo t, _):rest)- | isCommitThread t = go tid prior (skip rest)- | otherwise = 1 + go t r rest- go _ _ (r@(Start t, _):rest) = go t r rest- go tid _ (r@(Continue, _):rest) = go tid r rest- go _ prior [] = case (prior, d) of- ((_, Yield), SwitchTo _) -> 0- ((_, ThreadDelay _), SwitchTo _) -> 0- (_, SwitchTo _) -> 1- _ -> 0-- -- Commit threads have negative thread IDs for easy identification.- isCommitThread = (< initialThread)-- -- Skip until the next context switch.- skip = dropWhile (not . isContextSwitch . fst)- isContextSwitch Continue = False- isContextSwitch _ = True-preEmpCount [] _ = 0------------------------------------------------------------------------------------ Failures----- | An indication of how a concurrent computation failed.------ The @Eq@, @Ord@, and @NFData@ instances compare/evaluate the--- exception with @show@ in the @UncaughtException@ case.------ @since 0.9.0.0-data Failure- = InternalError- -- ^ Will be raised if the scheduler does something bad. This should- -- never arise unless you write your own, faulty, scheduler! If it- -- does, please file a bug report.- | Abort- -- ^ The scheduler chose to abort execution. This will be produced- -- if, for example, all possible decisions exceed the specified- -- bounds (there have been too many pre-emptions, the computation- -- has executed for too long, or there have been too many yields).- | Deadlock- -- ^ The computation became blocked indefinitely on @MVar@s.- | STMDeadlock- -- ^ The computation became blocked indefinitely on @TVar@s.- | UncaughtException SomeException- -- ^ An uncaught exception bubbled to the top of the computation.- | IllegalSubconcurrency- -- ^ Calls to @subconcurrency@ were nested, or attempted when- -- multiple threads existed.- deriving Show--instance Eq Failure where- (==) = (==) `on` _other--instance Ord Failure where- compare = compare `on` _other--instance NFData Failure where- rnf = rnf . _other---- | Convert failures into a different representation we can Eq / Ord--- / NFData.-_other :: Failure -> (Int, Maybe String)-_other InternalError = (0, Nothing)-_other Abort = (1, Nothing)-_other Deadlock = (2, Nothing)-_other STMDeadlock = (3, Nothing)-_other (UncaughtException e) = (4, Just (show e))-_other IllegalSubconcurrency = (5, Nothing)---- | Pretty-print a failure------ @since 0.4.0.0-showFail :: Failure -> String-showFail Abort = "[abort]"-showFail Deadlock = "[deadlock]"-showFail STMDeadlock = "[stm-deadlock]"-showFail InternalError = "[internal-error]"-showFail (UncaughtException exc) = "[" ++ displayException exc ++ "]"-showFail IllegalSubconcurrency = "[illegal-subconcurrency]"---- | Check if a failure is an @InternalError@.------ @since 0.9.0.0-isInternalError :: Failure -> Bool-isInternalError InternalError = True-isInternalError _ = False---- | Check if a failure is an @Abort@.------ @since 0.9.0.0-isAbort :: Failure -> Bool-isAbort Abort = True-isAbort _ = False---- | Check if a failure is a @Deadlock@ or an @STMDeadlock@.------ @since 0.9.0.0-isDeadlock :: Failure -> Bool-isDeadlock Deadlock = True-isDeadlock STMDeadlock = True-isDeadlock _ = False---- | Check if a failure is an @UncaughtException@------ @since 0.9.0.0-isUncaughtException :: Failure -> Bool-isUncaughtException (UncaughtException _) = True-isUncaughtException _ = False---- | Check if a failure is an @IllegalSubconcurrency@------ @since 0.9.0.0-isIllegalSubconcurrency :: Failure -> Bool-isIllegalSubconcurrency IllegalSubconcurrency = True-isIllegalSubconcurrency _ = False------------------------------------------------------------------------------------ Memory Models---- | The memory model to use for non-synchronised 'CRef' operations.------ @since 0.4.0.0-data MemType =- SequentialConsistency- -- ^ The most intuitive model: a program behaves as a simple- -- interleaving of the actions in different threads. When a 'CRef'- -- is written to, that write is immediately visible to all threads.- | TotalStoreOrder- -- ^ Each thread has a write buffer. A thread sees its writes- -- immediately, but other threads will only see writes when they are- -- committed, which may happen later. Writes are committed in the- -- same order that they are created.- | PartialStoreOrder- -- ^ Each 'CRef' has a write buffer. A thread sees its writes- -- immediately, but other threads will only see writes when they are- -- committed, which may happen later. Writes to different 'CRef's- -- are not necessarily committed in the same order that they are- -- created.- deriving (Eq, Show, Read, Ord, Enum, Bounded)---- | @since 0.5.1.0-instance NFData MemType where- rnf m = m `seq` ()------------------------------------------------------------------------------------ Miscellaneous---- | An exception for errors in testing caused by use of 'fail'.-newtype MonadFailException = MonadFailException String- deriving Show--instance Exception MonadFailException---- | Run with a continuation that writes its value into a reference,--- returning the computation and the reference. Using the reference--- is non-blocking, it is up to you to ensure you wait sufficiently.-runRefCont :: MonadRef r n => (n () -> x) -> (a -> Maybe b) -> ((a -> x) -> x) -> n (x, r (Maybe b))-runRefCont act f k = do- ref <- newRef Nothing- let c = k (act . writeRef ref . f)- pure (c, ref)---- | 'head' but with a better error message if it fails. Use this--- only where it shouldn't fail!-ehead :: String -> [a] -> a-ehead _ (x:_) = x-ehead src _ = fatal src "head: empty list"---- | 'tail' but with a better error message if it fails. Use this--- only where it shouldn't fail!-etail :: String -> [a] -> [a]-etail _ (_:xs) = xs-etail src _ = fatal src "tail: empty list"---- | '(!!)' but with a better error message if it fails. Use this--- only where it shouldn't fail!-eidx :: String -> [a] -> Int -> a-eidx src xs i- | i < length xs = xs !! i- | otherwise = fatal src "(!!): index too large"---- | 'fromJust' but with a better error message if it fails. Use this--- only where it shouldn't fail!-efromJust :: String -> Maybe a -> a-efromJust _ (Just x) = x-efromJust src _ = fatal src "fromJust: Nothing"---- | 'fromList' but with a better error message if it fails. Use this--- only where it shouldn't fail!-efromList :: String -> [a] -> NonEmpty a-efromList _ (x:xs) = x:|xs-efromList src _ = fatal src "fromList: empty list"---- | 'error' but saying where it came from-fatal :: String -> String -> a-fatal src msg = error ("(dejafu: " ++ src ++ ") " ++ msg)------------------------------------------------------------------------------------- Utilities---- | Helper for @next*@-nextId :: String -> [String] -> (Maybe String, [String])-nextId name used = (newName, newUsed) where- newName- | null name = Nothing- | occurrences > 0 = Just (name ++ "-" ++ show occurrences)- | otherwise = Just name- newUsed- | null name = used- | otherwise = name : used- occurrences = length (filter (==name) used)
Test/DejaFu/Conc.hs view
@@ -4,15 +4,14 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeSynonymInstances #-} -- | -- Module : Test.DejaFu.Conc--- Copyright : (c) 2016 Michael Walker+-- Copyright : (c) 2016--2017 Michael Walker -- License : MIT -- Maintainer : Michael Walker <mike@barrucadu.co.uk> -- Stability : experimental--- Portability : CPP, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, RankNTypes, TypeFamilies, TypeSynonymInstances+-- Portability : CPP, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, TypeFamilies -- -- Deterministic traced execution of concurrent computations. --@@ -22,7 +21,6 @@ module Test.DejaFu.Conc ( -- * The @ConcT@ monad transformer ConcT- , ConcST , ConcIO -- * Executing computations@@ -48,23 +46,22 @@ ) where import Control.Exception (MaskingState(..))-import qualified Control.Monad.Base as Ba import qualified Control.Monad.Catch as Ca import qualified Control.Monad.IO.Class as IO import Control.Monad.Ref (MonadRef) import qualified Control.Monad.Ref as Re-import Control.Monad.ST (ST) import Control.Monad.Trans.Class (MonadTrans(..)) import qualified Data.Foldable as F import Data.IORef (IORef)-import Data.STRef (STRef) import Test.DejaFu.Schedule import qualified Control.Monad.Conc.Class as C-import Test.DejaFu.Common import Test.DejaFu.Conc.Internal import Test.DejaFu.Conc.Internal.Common-import Test.DejaFu.STM+import Test.DejaFu.Conc.Internal.STM+import Test.DejaFu.Internal+import Test.DejaFu.Types+import Test.DejaFu.Utils #if MIN_VERSION_base(4,9,0) import qualified Control.Monad.Fail as Fail@@ -79,12 +76,6 @@ fail = C . fail #endif --- | A 'MonadConc' implementation using @ST@, this should be preferred--- if you do not need 'liftIO'.------ @since 0.4.0.0-type ConcST t = ConcT (STRef t) (ST t)- -- | A 'MonadConc' implementation using @IO@. -- -- @since 0.4.0.0@@ -96,11 +87,9 @@ wrap :: (M n r a -> M n r a) -> ConcT r n a -> ConcT r n a wrap f = C . f . unC -instance IO.MonadIO ConcIO where- liftIO ma = toConc (\c -> ALift (fmap c ma))--instance Ba.MonadBase IO ConcIO where- liftBase = IO.liftIO+-- | @since 1.0.0.0+instance IO.MonadIO n => IO.MonadIO (ConcT r n) where+ liftIO ma = toConc (\c -> ALift (fmap c (IO.liftIO ma))) instance Re.MonadRef (CRef r) (ConcT r n) where newRef a = toConc (ANewCRef "" a)@@ -131,14 +120,17 @@ type MVar (ConcT r n) = MVar r type CRef (ConcT r n) = CRef r type Ticket (ConcT r n) = Ticket- type STM (ConcT r n) = STMLike n r+ type STM (ConcT r n) = S n r type ThreadId (ConcT r n) = ThreadId -- ---------- - forkWithUnmaskN n ma = toConc (AFork n (\umask -> runCont (unC $ ma $ wrap umask) (\_ -> AStop (pure ()))))+ forkWithUnmaskN n ma = toConc (AFork n (\umask -> runCont (unC $ ma $ wrap umask) (\_ -> AStop (pure ())))) forkOnWithUnmaskN n _ = C.forkWithUnmaskN n+ forkOSN n ma = forkOSWithUnmaskN n (const ma) + isCurrentThreadBound = toConc AIsBound+ -- This implementation lies and returns 2 until a value is set. This -- will potentially avoid special-case behaviour for 1 capability, -- so it seems a sane choice.@@ -185,10 +177,22 @@ atomically = toConc . AAtom +-- move this into the instance defn when forkOSWithUnmaskN is added to MonadConc in 2018+forkOSWithUnmaskN :: Applicative n => String -> ((forall a. ConcT r n a -> ConcT r n a) -> ConcT r n ()) -> ConcT r n ThreadId+forkOSWithUnmaskN n ma+ | C.rtsSupportsBoundThreads = toConc (AForkOS n (\umask -> runCont (unC $ ma $ wrap umask) (\_ -> AStop (pure ()))))+ | otherwise = fail "RTS doesn't support multiple OS threads (use ghc -threaded when linking)"+ -- | Run a concurrent computation with a given 'Scheduler' and initial -- state, returning a failure reason on error. Also returned is the -- final state of the scheduler, and an execution trace. --+-- If the RTS supports bound threads (ghc -threaded when linking) then+-- the main thread of the concurrent computation will be bound, and+-- @forkOS@ / @forkOSN@ will work during execution. If not, then the+-- main thread will not be found, and attempting to fork a bound+-- thread will raise an error.+-- -- __Warning:__ Blocking on the action of another thread in 'liftIO' -- cannot be detected! So if you perform some potentially blocking -- action in a 'liftIO' the entire collection of threads may deadlock!@@ -202,8 +206,8 @@ -- nonexistent thread. In either of those cases, the computation will -- be halted. ----- @since 0.8.0.0-runConcurrent :: MonadRef r n+-- @since 1.0.0.0+runConcurrent :: (C.MonadConc n, MonadRef r n) => Scheduler s -> MemType -> s
Test/DejaFu/Conc/Internal.hs view
@@ -4,7 +4,7 @@ -- | -- Module : Test.DejaFu.Conc.Internal--- Copyright : (c) 2016 Michael Walker+-- Copyright : (c) 2016--2017 Michael Walker -- License : MIT -- Maintainer : Michael Walker <mike@barrucadu.co.uk> -- Stability : experimental@@ -17,6 +17,8 @@ import Control.Exception (MaskingState(..), toException)+import Control.Monad.Conc.Class (MonadConc,+ rtsSupportsBoundThreads) import Control.Monad.Ref (MonadRef, newRef, readRef, writeRef) import Data.Functor (void)@@ -27,13 +29,13 @@ import Data.Sequence (Seq, (<|)) import qualified Data.Sequence as Seq -import Test.DejaFu.Common import Test.DejaFu.Conc.Internal.Common import Test.DejaFu.Conc.Internal.Memory+import Test.DejaFu.Conc.Internal.STM import Test.DejaFu.Conc.Internal.Threading+import Test.DejaFu.Internal import Test.DejaFu.Schedule-import Test.DejaFu.STM (Result(..),- runTransaction)+import Test.DejaFu.Types -------------------------------------------------------------------------------- -- * Execution@@ -45,23 +47,27 @@ -- | Run a concurrent computation with a given 'Scheduler' and initial -- state, returning a failure reason on error. Also returned is the -- final state of the scheduler, and an execution trace.-runConcurrency :: MonadRef r n- => Scheduler g- -> MemType- -> g- -> IdSource- -> Int- -> M n r a- -> n (Either Failure a, Context n r g, SeqTrace, Maybe (ThreadId, ThreadAction))+runConcurrency :: (MonadConc n, MonadRef r n)+ => Scheduler g+ -> MemType+ -> g+ -> IdSource+ -> Int+ -> M n r a+ -> n (Either Failure a, Context n r g, SeqTrace, Maybe (ThreadId, ThreadAction)) runConcurrency sched memtype g idsrc caps ma = do (c, ref) <- runRefCont AStop (Just . Right) (runM ma)+ let threads0 = launch' Unmasked initialThread (const c) M.empty+ threads <- (if rtsSupportsBoundThreads then makeBound initialThread else pure) threads0 let ctx = Context { cSchedState = g , cIdSource = idsrc- , cThreads = launch' Unmasked initialThread (const c) M.empty+ , cThreads = threads , cWriteBuf = emptyBuffer , cCaps = caps } (finalCtx, trace, finalAction) <- runThreads sched memtype ref ctx+ let finalThreads = cThreads finalCtx+ mapM_ (`kill` finalThreads) (M.keys finalThreads) out <- readRef ref pure (efromJust "runConcurrency" out, finalCtx, trace, finalAction) @@ -75,7 +81,7 @@ } -- | Run a collection of threads, until there are no threads left.-runThreads :: MonadRef r n+runThreads :: (MonadConc n, MonadRef r n) => Scheduler g -> MemType -> r (Maybe (Either Failure a))@@ -140,9 +146,11 @@ | (fst <$> prior) `notElem` map (Just . fst) runnable' = Start chosen | otherwise = SwitchTo chosen - getTrc (Single a) = Seq.singleton (decision, runnable', a)- getTrc (SubC as _) = (decision, runnable', Subconcurrency) <| as+ getTrc (Single a) = Seq.singleton (decision, alternatives, a)+ getTrc (SubC as _) = (decision, alternatives, Subconcurrency) <| as + alternatives = filter (\(t, _) -> t /= chosen) runnable'+ getPrior (Single a) = Just (chosen, a) getPrior (SubC _ finalD) = finalD @@ -159,7 +167,7 @@ -- | Run a single thread one step, by dispatching on the type of -- 'Action'.-stepThread :: forall n r g. MonadRef r n+stepThread :: forall n r g. (MonadConc n, MonadRef r n) => Scheduler g -- ^ The scheduler. -> MemType@@ -174,10 +182,22 @@ stepThread sched memtype tid action ctx = case action of -- start a new thread, assigning it the next 'ThreadId' AFork n a b -> pure $- let threads' = launch tid newtid a (cThreads ctx)- (idSource', newtid) = nextTId n (cIdSource ctx)- in (Right ctx { cThreads = goto (b newtid) tid threads', cIdSource = idSource' }, Single (Fork newtid))+ let threads' = launch tid newtid a (cThreads ctx)+ (idSource', newtid) = nextTId n (cIdSource ctx)+ in (Right ctx { cThreads = goto (b newtid) tid threads', cIdSource = idSource' }, Single (Fork newtid)) + -- start a new bound thread, assigning it the next 'ThreadId'+ AForkOS n a b -> do+ let (idSource', newtid) = nextTId n (cIdSource ctx)+ let threads' = launch tid newtid a (cThreads ctx)+ threads'' <- makeBound newtid threads'+ pure (Right ctx { cThreads = goto (b newtid) tid threads'', cIdSource = idSource' }, Single (Fork newtid))++ -- check if the current thread is bound+ AIsBound c ->+ let isBound = isJust (_bound =<< M.lookup tid (cThreads ctx))+ in simple (goto (c isBound) tid (cThreads ctx)) (IsCurrentThreadBound isBound)+ -- get the 'ThreadId' of the current thread AMyTId c -> simple (goto (c tid) tid (cThreads ctx)) MyThreadId @@ -316,7 +336,7 @@ -- lift an action from the underlying monad into the @Conc@ -- computation. ALift na -> do- a <- na+ a <- runLiftedAct tid (cThreads ctx) na simple (goto a tid (cThreads ctx)) LiftIO -- throw an exception, and propagate it to the appropriate@@ -367,7 +387,10 @@ AReturn c -> simple (goto c tid (cThreads ctx)) Return -- kill the current thread.- AStop na -> na >> simple (kill tid (cThreads ctx)) Stop+ AStop na -> do+ na+ threads' <- kill tid (cThreads ctx)+ simple threads' Stop -- run a subconcurrent computation. ASub ma c@@ -394,7 +417,9 @@ Just ts' -> simple ts' act Nothing | t == initialThread -> pure (Left (UncaughtException some), Single act)- | otherwise -> simple (kill t ts) act+ | otherwise -> do+ ts' <- kill t ts+ simple ts' act -- helper for actions which only change the threads. simple threads' act = pure (Right ctx { cThreads = threads' }, Single act)
Test/DejaFu/Conc/Internal/Common.hs view
@@ -4,7 +4,7 @@ -- | -- Module : Test.DejaFu.Conc.Internal.Common--- Copyright : (c) 2016 Michael Walker+-- Copyright : (c) 2016--2017 Michael Walker -- License : MIT -- Maintainer : Michael Walker <mike@barrucadu.co.uk> -- Stability : experimental@@ -12,15 +12,16 @@ -- -- Common types and utility functions for deterministic execution of -- 'MonadConc' implementations. This module is NOT considered to form+-- part of the public interface of this library. module Test.DejaFu.Conc.Internal.Common where -import Control.Exception (Exception, MaskingState(..))-import Data.Map.Strict (Map)-import Test.DejaFu.Common-import Test.DejaFu.STM (STMLike)+import Control.Exception (Exception, MaskingState(..))+import Data.Map.Strict (Map)+import Test.DejaFu.Conc.Internal.STM (S)+import Test.DejaFu.Types #if MIN_VERSION_base(4,9,0)-import qualified Control.Monad.Fail as Fail+import qualified Control.Monad.Fail as Fail #endif --------------------------------------------------------------------------------@@ -109,7 +110,9 @@ -- primitives of the concurrency. 'spawn' is absent as it is -- implemented in terms of 'newEmptyMVar', 'fork', and 'putMVar'. data Action n r =- AFork String ((forall b. M n r b -> M n r b) -> Action n r) (ThreadId -> Action n r)+ AFork String ((forall b. M n r b -> M n r b) -> Action n r) (ThreadId -> Action n r)+ | AForkOS String ((forall b. M n r b -> M n r b) -> Action n r) (ThreadId -> Action n r)+ | AIsBound (Bool -> Action n r) | AMyTId (ThreadId -> Action n r) | AGetNumCapabilities (Int -> Action n r)@@ -138,7 +141,7 @@ | forall a. AMasking MaskingState ((forall b. M n r b -> M n r b) -> M n r a) (a -> Action n r) | AResetMask Bool Bool MaskingState (Action n r) - | forall a. AAtom (STMLike n r a) (a -> Action n r)+ | forall a. AAtom (S n r a) (a -> Action n r) | ALift (n (Action n r)) | AYield (Action n r) | ADelay Int (Action n r)@@ -155,6 +158,8 @@ -- | Look as far ahead in the given continuation as possible. lookahead :: Action n r -> Lookahead lookahead (AFork _ _ _) = WillFork+lookahead (AForkOS _ _ _) = WillForkOS+lookahead (AIsBound _) = WillIsCurrentThreadBound lookahead (AMyTId _) = WillMyThreadId lookahead (AGetNumCapabilities _) = WillGetNumCapabilities lookahead (ASetNumCapabilities i _) = WillSetNumCapabilities i
Test/DejaFu/Conc/Internal/Memory.hs view
@@ -4,7 +4,7 @@ -- | -- Module : Test.DejaFu.Conc.Internal.Memory--- Copyright : (c) 2016 Michael Walker+-- Copyright : (c) 2016--2017 Michael Walker -- License : MIT -- Maintainer : Michael Walker <mike@barrucadu.co.uk> -- Stability : experimental@@ -26,16 +26,16 @@ import Control.Monad.Ref (MonadRef, readRef, writeRef) import Data.Map.Strict (Map)+import qualified Data.Map.Strict as M import Data.Maybe (maybeToList) import Data.Monoid ((<>)) import Data.Sequence (Seq, ViewL(..), singleton, viewl, (><)) -import Test.DejaFu.Common import Test.DejaFu.Conc.Internal.Common import Test.DejaFu.Conc.Internal.Threading--import qualified Data.Map.Strict as M+import Test.DejaFu.Internal+import Test.DejaFu.Types -------------------------------------------------------------------------------- -- * Manipulating @CRef@s@@ -129,7 +129,7 @@ -- | Add phantom threads to the thread list to commit pending writes. addCommitThreads :: WriteBuffer r -> Threads n r -> Threads n r addCommitThreads (WriteBuffer wb) ts = ts <> M.fromList phantoms where- phantoms = [ (ThreadId Nothing $ negate tid, mkthread c)+ phantoms = [ (ThreadId (Id Nothing $ negate tid), mkthread c) | ((_, b), tid) <- zip (M.toList wb) [1..] , c <- maybeToList (go $ viewl b) ]
+ Test/DejaFu/Conc/Internal/STM.hs view
@@ -0,0 +1,239 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++-- Must come after TypeFamilies+{-# LANGUAGE NoMonoLocalBinds #-}++-- |+-- Module : Test.DejaFu.Conc.Internal.STM+-- Copyright : (c) 2017 Michael Walker+-- License : MIT+-- Maintainer : Michael Walker <mike@barrucadu.co.uk>+-- Stability : experimental+-- Portability : CPP, ExistentialQuantification, MultiParamTypeClasses, NoMonoLocalBinds, TypeFamilies+--+-- 'MonadSTM' testing implementation, internal types and definitions.+-- This module is NOT considered to form part of the public interface+-- of this library.+module Test.DejaFu.Conc.Internal.STM where++import Control.Applicative (Alternative(..))+import Control.Exception (Exception, SomeException,+ fromException, toException)+import Control.Monad (MonadPlus(..))+import Control.Monad.Catch (MonadCatch(..), MonadThrow(..))+import Control.Monad.Ref (MonadRef, newRef, readRef, writeRef)+import Data.List (nub)++import qualified Control.Monad.STM.Class as C+import Test.DejaFu.Internal+import Test.DejaFu.Types++#if MIN_VERSION_base(4,9,0)+import qualified Control.Monad.Fail as Fail+#endif++--------------------------------------------------------------------------------+-- * The @S@ monad++-- | The underlying monad is based on continuations over primitive+-- actions.+--+-- This is not @Cont@ because we want to give it a custom @MonadFail@+-- instance.+newtype S n r a = S { runSTM :: (a -> STMAction n r) -> STMAction n r }++instance Functor (S n r) where+ fmap f m = S $ \c -> runSTM m (c . f)++instance Applicative (S n r) where+ pure x = S $ \c -> c x+ f <*> v = S $ \c -> runSTM f (\g -> runSTM v (c . g))++instance Monad (S n r) where+ return = pure+ m >>= k = S $ \c -> runSTM m (\x -> runSTM (k x) c)++#if MIN_VERSION_base(4,9,0)+ fail = Fail.fail++instance Fail.MonadFail (S n r) where+#endif+ fail e = S $ \_ -> SThrow (MonadFailException e)++instance MonadThrow (S n r) where+ throwM e = S $ \_ -> SThrow e++instance MonadCatch (S n r) where+ catch stm handler = S $ SCatch handler stm++instance Alternative (S n r) where+ a <|> b = S $ SOrElse a b+ empty = S $ const SRetry++instance MonadPlus (S n r)++instance C.MonadSTM (S n r) where+ type TVar (S n r) = TVar r++ newTVarN n = S . SNew n++ readTVar = S . SRead++ writeTVar tvar a = S $ \c -> SWrite tvar a (c ())++--------------------------------------------------------------------------------+-- * Primitive actions++-- | STM transactions are represented as a sequence of primitive+-- actions.+data STMAction n r+ = forall a e. Exception e => SCatch (e -> S n r a) (S n r a) (a -> STMAction n r)+ | forall a. SRead (TVar r a) (a -> STMAction n r)+ | forall a. SWrite (TVar r a) a (STMAction n r)+ | forall a. SOrElse (S n r a) (S n r a) (a -> STMAction n r)+ | forall a. SNew String a (TVar r a -> STMAction n r)+ | forall e. Exception e => SThrow e+ | SRetry+ | SStop (n ())++--------------------------------------------------------------------------------+-- * @TVar@s++-- | A 'TVar' is a tuple of a unique ID and the value contained. The+-- ID is so that blocked transactions can be re-run when a 'TVar' they+-- depend on has changed.+newtype TVar r a = TVar (TVarId, r a)++--------------------------------------------------------------------------------+-- * Output++-- | The result of an STM transaction, along with which 'TVar's it+-- touched whilst executing.+data Result a =+ Success [TVarId] [TVarId] a+ -- ^ The transaction completed successfully, reading the first list+ -- 'TVar's and writing to the second.+ | Retry [TVarId]+ -- ^ The transaction aborted by calling 'retry', and read the+ -- returned 'TVar's. It should be retried when at least one of the+ -- 'TVar's has been mutated.+ | Exception SomeException+ -- ^ The transaction aborted by throwing an exception.+ deriving Show+++--------------------------------------------------------------------------------+-- * Execution++-- | Run a transaction, returning the result and new initial 'TVarId'.+-- If the transaction failed, any effects are undone.+runTransaction :: MonadRef r n+ => S n r a+ -> IdSource+ -> n (Result a, IdSource, [TAction])+runTransaction ma tvid = do+ (res, _, tvid', trace) <- doTransaction ma tvid+ pure (res, tvid', trace)++-- | Run a STM transaction, returning an action to undo its effects.+--+-- If the transaction fails, its effects will automatically be undone,+-- so the undo action returned will be @pure ()@.+doTransaction :: MonadRef r n+ => S n r a+ -> IdSource+ -> n (Result a, n (), IdSource, [TAction])+doTransaction ma idsource = do+ (c, ref) <- runRefCont SStop (Just . Right) (runSTM ma)+ (idsource', undo, readen, written, trace) <- go ref c (pure ()) idsource [] [] []+ res <- readRef ref++ case res of+ Just (Right val) -> pure (Success (nub readen) (nub written) val, undo, idsource', reverse trace)+ Just (Left exc) -> undo >> pure (Exception exc, pure (), idsource, reverse trace)+ Nothing -> undo >> pure (Retry $ nub readen, pure (), idsource, reverse trace)++ where+ go ref act undo nidsrc readen written sofar = do+ (act', undo', nidsrc', readen', written', tact) <- stepTrans act nidsrc++ let newIDSource = nidsrc'+ newAct = act'+ newUndo = undo' >> undo+ newReaden = readen' ++ readen+ newWritten = written' ++ written+ newSofar = tact : sofar++ case tact of+ TStop -> pure (newIDSource, newUndo, newReaden, newWritten, TStop:newSofar)+ TRetry -> do+ writeRef ref Nothing+ pure (newIDSource, newUndo, newReaden, newWritten, TRetry:newSofar)+ TThrow -> do+ writeRef ref (Just . Left $ case act of SThrow e -> toException e; _ -> undefined)+ pure (newIDSource, newUndo, newReaden, newWritten, TThrow:newSofar)+ _ -> go ref newAct newUndo newIDSource newReaden newWritten newSofar++-- | Run a transaction for one step.+stepTrans :: MonadRef r n+ => STMAction n r+ -> IdSource+ -> n (STMAction n r, n (), IdSource, [TVarId], [TVarId], TAction)+stepTrans act idsource = case act of+ SCatch h stm c -> stepCatch h stm c+ SRead ref c -> stepRead ref c+ SWrite ref a c -> stepWrite ref a c+ SNew n a c -> stepNew n a c+ SOrElse a b c -> stepOrElse a b c+ SStop na -> stepStop na++ SThrow e -> pure (SThrow e, nothing, idsource, [], [], TThrow)+ SRetry -> pure (SRetry, nothing, idsource, [], [], TRetry)++ where+ nothing = pure ()++ stepCatch h stm c = cases TCatch stm c+ (\trace -> pure (SRetry, nothing, idsource, [], [], TCatch trace Nothing))+ (\trace exc -> case fromException exc of+ Just exc' -> transaction (TCatch trace . Just) (h exc') c+ Nothing -> pure (SThrow exc, nothing, idsource, [], [], TCatch trace Nothing))++ stepRead (TVar (tvid, ref)) c = do+ val <- readRef ref+ pure (c val, nothing, idsource, [tvid], [], TRead tvid)++ stepWrite (TVar (tvid, ref)) a c = do+ old <- readRef ref+ writeRef ref a+ pure (c, writeRef ref old, idsource, [], [tvid], TWrite tvid)++ stepNew n a c = do+ let (idsource', tvid) = nextTVId n idsource+ ref <- newRef a+ let tvar = TVar (tvid, ref)+ pure (c tvar, nothing, idsource', [], [tvid], TNew tvid)++ stepOrElse a b c = cases TOrElse a c+ (\trace -> transaction (TOrElse trace . Just) b c)+ (\trace exc -> pure (SThrow exc, nothing, idsource, [], [], TOrElse trace Nothing))++ stepStop na = do+ na+ pure (SStop na, nothing, idsource, [], [], TStop)++ cases tact stm onSuccess onRetry onException = do+ (res, undo, idsource', trace) <- doTransaction stm idsource+ case res of+ Success readen written val -> pure (onSuccess val, undo, idsource', readen, written, tact trace Nothing)+ Retry readen -> do+ (res', undo', idsource'', readen', written', trace') <- onRetry trace+ pure (res', undo', idsource'', readen ++ readen', written', trace')+ Exception exc -> onException trace exc++ transaction tact stm onSuccess = cases (\t _ -> tact t) stm onSuccess+ (\trace -> pure (SRetry, nothing, idsource, [], [], tact trace))+ (\trace exc -> pure (SThrow exc, nothing, idsource, [], [], tact trace))
Test/DejaFu/Conc/Internal/Threading.hs view
@@ -3,7 +3,7 @@ -- | -- Module : Test.DejaFu.Conc.Internal.Threading--- Copyright : (c) 2016 Michael Walker+-- Copyright : (c) 2016--2017 Michael Walker -- License : MIT -- Maintainer : Michael Walker <mike@barrucadu.co.uk> -- Stability : experimental@@ -13,14 +13,16 @@ -- form part of the public interface of this library. module Test.DejaFu.Conc.Internal.Threading where +import qualified Control.Concurrent.Classy as C import Control.Exception (Exception, MaskingState(..), SomeException, fromException) import Data.List (intersect) import Data.Map.Strict (Map) import Data.Maybe (isJust) -import Test.DejaFu.Common import Test.DejaFu.Conc.Internal.Common+import Test.DejaFu.Internal+import Test.DejaFu.Types import qualified Data.Map.Strict as M @@ -40,11 +42,23 @@ -- ^ Stack of exception handlers , _masking :: MaskingState -- ^ The exception masking state.+ , _bound :: Maybe (BoundThread n r)+ -- ^ State for the associated bound thread, if it exists. } +-- | The state of a bound thread.+data BoundThread n r = BoundThread+ { _runboundIO :: C.MVar n (n (Action n r))+ -- ^ Run an @IO@ action in the bound thread by writing to this.+ , _getboundIO :: C.MVar n (Action n r)+ -- ^ Get the result of the above by reading from this.+ , _boundTId :: C.ThreadId n+ -- ^ Thread ID+ }+ -- | Construct a thread with just one action mkthread :: Action n r -> Thread n r-mkthread c = Thread c Nothing [] Unmasked+mkthread c = Thread c Nothing [] Unmasked Nothing -------------------------------------------------------------------------------- -- * Blocking@@ -122,15 +136,11 @@ -- | Start a thread with the given ID and masking state. This must not already be in use! launch' :: MaskingState -> ThreadId -> ((forall b. M n r b -> M n r b) -> Action n r) -> Threads n r -> Threads n r launch' ms tid a = M.insert tid thread where- thread = Thread { _continuation = a umask, _blocking = Nothing, _handlers = [], _masking = ms }+ thread = Thread (a umask) Nothing [] ms Nothing umask mb = resetMask True Unmasked >> mb >>= \b -> resetMask False ms >> pure b resetMask typ m = cont $ \k -> AResetMask typ True m $ k () --- | Kill a thread.-kill :: ThreadId -> Threads n r -> Threads n r-kill = M.delete- -- | Block a thread. block :: BlockedOn -> ThreadId -> Threads n r -> Threads n r block blockedOn = M.adjust $ \thread -> thread { _blocking = Just blockedOn }@@ -147,3 +157,44 @@ isBlocked thread = case (_blocking thread, blockedOn) of (Just (OnTVar tvids), OnTVar blockedOn') -> tvids `intersect` blockedOn' /= [] (theblock, _) -> theblock == Just blockedOn++-------------------------------------------------------------------------------+-- ** Bound threads++-- | Turn a thread into a bound thread.+makeBound :: C.MonadConc n => ThreadId -> Threads n r -> n (Threads n r)+makeBound tid threads = do+ runboundIO <- C.newEmptyMVar+ getboundIO <- C.newEmptyMVar+ btid <- C.forkOSN ("bound worker for '" ++ show tid ++ "'") (go runboundIO getboundIO)+ let bt = BoundThread runboundIO getboundIO btid+ pure (M.adjust (\t -> t { _bound = Just bt }) tid threads)+ where+ go runboundIO getboundIO =+ let loop = do+ na <- C.takeMVar runboundIO+ C.putMVar getboundIO =<< na+ loop+ in loop++-- | Kill a thread and remove it from the thread map.+--+-- If the thread is bound, the worker thread is cleaned up.+kill :: C.MonadConc n => ThreadId -> Threads n r -> n (Threads n r)+kill tid threads = case M.lookup tid threads of+ Just thread -> case _bound thread of+ Just bt -> do+ C.killThread (_boundTId bt)+ pure (M.delete tid threads)+ Nothing -> pure (M.delete tid threads)+ Nothing -> pure threads++-- | Run an action.+--+-- If the thread is bound, the action is run in the worker thread.+runLiftedAct :: C.MonadConc n => ThreadId -> Threads n r -> n (Action n r) -> n (Action n r)+runLiftedAct tid threads ma = case _bound =<< M.lookup tid threads of+ Just bt -> do+ C.putMVar (_runboundIO bt) ma+ C.takeMVar (_getboundIO bt)+ Nothing -> ma
Test/DejaFu/Defaults.hs view
@@ -9,8 +9,8 @@ -- Default parameters for test execution. module Test.DejaFu.Defaults where -import Test.DejaFu.Common import Test.DejaFu.SCT+import Test.DejaFu.Types -- | A default way to execute concurrent programs: systematically -- using 'defaultBounds'.
+ Test/DejaFu/Internal.hs view
@@ -0,0 +1,338 @@+-- |+-- Module : Test.DejaFu.Internal+-- Copyright : (c) 2017 Michael Walker+-- License : MIT+-- Maintainer : Michael Walker <mike@barrucadu.co.uk>+-- Stability : experimental+-- Portability : portable+--+-- Internal types and functions used throughout DejaFu. This module+-- is NOT considered to form part of the public interface of this+-- library.+module Test.DejaFu.Internal where++import Control.DeepSeq (NFData(..))+import Control.Monad.Ref (MonadRef(..))+import Data.List.NonEmpty (NonEmpty(..))+import Data.Maybe (fromMaybe)+import Data.Set (Set)+import qualified Data.Set as S++import Test.DejaFu.Types++-------------------------------------------------------------------------------+-- * Identifiers++-- | The number of ID parameters was getting a bit unwieldy, so this+-- hides them all away.+data IdSource = IdSource+ { _crids :: (Int, [String])+ , _mvids :: (Int, [String])+ , _tvids :: (Int, [String])+ , _tids :: (Int, [String])+ } deriving (Eq, Ord, Show)++instance NFData IdSource where+ rnf idsource = rnf ( _crids idsource+ , _mvids idsource+ , _tvids idsource+ , _tids idsource+ )++-- | Get the next free 'CRefId'.+nextCRId :: String -> IdSource -> (IdSource, CRefId)+nextCRId name idsource =+ let (crid, crids') = nextId name (_crids idsource)+ in (idsource { _crids = crids' }, CRefId crid)++-- | Get the next free 'MVarId'.+nextMVId :: String -> IdSource -> (IdSource, MVarId)+nextMVId name idsource =+ let (mvid, mvids') = nextId name (_mvids idsource)+ in (idsource { _mvids = mvids' }, MVarId mvid)++-- | Get the next free 'TVarId'.+nextTVId :: String -> IdSource -> (IdSource, TVarId)+nextTVId name idsource =+ let (tvid, tvids') = nextId name (_tvids idsource)+ in (idsource { _tvids = tvids' }, TVarId tvid)++-- | Get the next free 'ThreadId'.+nextTId :: String -> IdSource -> (IdSource, ThreadId)+nextTId name idsource =+ let (tid, tids') = nextId name (_tids idsource)+ in (idsource { _tids = tids' }, ThreadId tid)++-- | Helper for @next*@+nextId :: String -> (Int, [String]) -> (Id, (Int, [String]))+nextId name (num, used) = (Id newName (num+1), (num+1, newUsed)) where+ newName+ | null name = Nothing+ | occurrences > 0 = Just (name ++ "-" ++ show occurrences)+ | otherwise = Just name+ newUsed+ | null name = used+ | otherwise = name : used+ occurrences = length (filter (==name) used)++-- | The initial ID source.+initialIdSource :: IdSource+initialIdSource = IdSource (0, []) (0, []) (0, []) (0, [])++-------------------------------------------------------------------------------+-- * Actions++-- | Check if a @ThreadAction@ immediately blocks.+isBlock :: ThreadAction -> Bool+isBlock (BlockedThrowTo _) = True+isBlock (BlockedTakeMVar _) = True+isBlock (BlockedReadMVar _) = True+isBlock (BlockedPutMVar _) = True+isBlock (BlockedSTM _) = True+isBlock _ = False++-- | Get the @TVar@s affected by a @ThreadAction@.+tvarsOf :: ThreadAction -> Set TVarId+tvarsOf act = tvarsRead act `S.union` tvarsWritten act++-- | Get the @TVar@s a transaction wrote to (or would have, if it+-- didn't @retry@).+tvarsWritten :: ThreadAction -> Set TVarId+tvarsWritten act = S.fromList $ case act of+ STM trc _ -> concatMap tvarsOf' trc+ BlockedSTM trc -> concatMap tvarsOf' trc+ _ -> []++ where+ tvarsOf' (TWrite tv) = [tv]+ tvarsOf' (TOrElse ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)+ tvarsOf' (TCatch ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)+ tvarsOf' _ = []++-- | Get the @TVar@s a transaction read from.+tvarsRead :: ThreadAction -> Set TVarId+tvarsRead act = S.fromList $ case act of+ STM trc _ -> concatMap tvarsOf' trc+ BlockedSTM trc -> concatMap tvarsOf' trc+ _ -> []++ where+ tvarsOf' (TRead tv) = [tv]+ tvarsOf' (TOrElse ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)+ tvarsOf' (TCatch ta tb) = concatMap tvarsOf' (ta ++ fromMaybe [] tb)+ tvarsOf' _ = []++-- | Convert a 'ThreadAction' into a 'Lookahead': \"rewind\" what has+-- happened. 'Killed' has no 'Lookahead' counterpart.+rewind :: ThreadAction -> Maybe Lookahead+rewind (Fork _) = Just WillFork+rewind (ForkOS _) = Just WillForkOS+rewind (IsCurrentThreadBound _) = Just WillIsCurrentThreadBound+rewind MyThreadId = Just WillMyThreadId+rewind (GetNumCapabilities _) = Just WillGetNumCapabilities+rewind (SetNumCapabilities i) = Just (WillSetNumCapabilities i)+rewind Yield = Just WillYield+rewind (ThreadDelay n) = Just (WillThreadDelay n)+rewind (NewMVar _) = Just WillNewMVar+rewind (PutMVar c _) = Just (WillPutMVar c)+rewind (BlockedPutMVar c) = Just (WillPutMVar c)+rewind (TryPutMVar c _ _) = Just (WillTryPutMVar c)+rewind (ReadMVar c) = Just (WillReadMVar c)+rewind (BlockedReadMVar c) = Just (WillReadMVar c)+rewind (TryReadMVar c _) = Just (WillTryReadMVar c)+rewind (TakeMVar c _) = Just (WillTakeMVar c)+rewind (BlockedTakeMVar c) = Just (WillTakeMVar c)+rewind (TryTakeMVar c _ _) = Just (WillTryTakeMVar c)+rewind (NewCRef _) = Just WillNewCRef+rewind (ReadCRef c) = Just (WillReadCRef c)+rewind (ReadCRefCas c) = Just (WillReadCRefCas c)+rewind (ModCRef c) = Just (WillModCRef c)+rewind (ModCRefCas c) = Just (WillModCRefCas c)+rewind (WriteCRef c) = Just (WillWriteCRef c)+rewind (CasCRef c _) = Just (WillCasCRef c)+rewind (CommitCRef t c) = Just (WillCommitCRef t c)+rewind (STM _ _) = Just WillSTM+rewind (BlockedSTM _) = Just WillSTM+rewind Catching = Just WillCatching+rewind PopCatching = Just WillPopCatching+rewind Throw = Just WillThrow+rewind (ThrowTo t) = Just (WillThrowTo t)+rewind (BlockedThrowTo t) = Just (WillThrowTo t)+rewind Killed = Nothing+rewind (SetMasking b m) = Just (WillSetMasking b m)+rewind (ResetMasking b m) = Just (WillResetMasking b m)+rewind LiftIO = Just WillLiftIO+rewind Return = Just WillReturn+rewind Stop = Just WillStop+rewind Subconcurrency = Just WillSubconcurrency+rewind StopSubconcurrency = Just WillStopSubconcurrency++-- | Check if an operation could enable another thread.+willRelease :: Lookahead -> Bool+willRelease WillFork = True+willRelease WillForkOS = True+willRelease WillYield = True+willRelease (WillThreadDelay _) = True+willRelease (WillPutMVar _) = True+willRelease (WillTryPutMVar _) = True+willRelease (WillReadMVar _) = True+willRelease (WillTakeMVar _) = True+willRelease (WillTryTakeMVar _) = True+willRelease WillSTM = True+willRelease WillThrow = True+willRelease (WillSetMasking _ _) = True+willRelease (WillResetMasking _ _) = True+willRelease WillStop = True+willRelease _ = False++-------------------------------------------------------------------------------+-- * Simplified actions++-- | A simplified view of the possible actions a thread can perform.+data ActionType =+ UnsynchronisedRead CRefId+ -- ^ A 'readCRef' or a 'readForCAS'.+ | UnsynchronisedWrite CRefId+ -- ^ A 'writeCRef'.+ | UnsynchronisedOther+ -- ^ Some other action which doesn't require cross-thread+ -- communication.+ | PartiallySynchronisedCommit CRefId+ -- ^ A commit.+ | PartiallySynchronisedWrite CRefId+ -- ^ A 'casCRef'+ | PartiallySynchronisedModify CRefId+ -- ^ A 'modifyCRefCAS'+ | SynchronisedModify CRefId+ -- ^ An 'atomicModifyCRef'.+ | SynchronisedRead MVarId+ -- ^ A 'readMVar' or 'takeMVar' (or @try@/@blocked@ variants).+ | SynchronisedWrite MVarId+ -- ^ A 'putMVar' (or @try@/@blocked@ variant).+ | SynchronisedOther+ -- ^ Some other action which does require cross-thread+ -- communication.+ deriving (Eq, Show)++instance NFData ActionType where+ rnf (UnsynchronisedRead c) = rnf c+ rnf (UnsynchronisedWrite c) = rnf c+ rnf (PartiallySynchronisedCommit c) = rnf c+ rnf (PartiallySynchronisedWrite c) = rnf c+ rnf (PartiallySynchronisedModify c) = rnf c+ rnf (SynchronisedModify c) = rnf c+ rnf (SynchronisedRead m) = rnf m+ rnf (SynchronisedWrite m) = rnf m+ rnf a = a `seq` ()++-- | Check if an action imposes a write barrier.+isBarrier :: ActionType -> Bool+isBarrier (SynchronisedModify _) = True+isBarrier (SynchronisedRead _) = True+isBarrier (SynchronisedWrite _) = True+isBarrier SynchronisedOther = True+isBarrier _ = False++-- | Check if an action commits a given 'CRef'.+isCommit :: ActionType -> CRefId -> Bool+isCommit (PartiallySynchronisedCommit c) r = c == r+isCommit (PartiallySynchronisedWrite c) r = c == r+isCommit (PartiallySynchronisedModify c) r = c == r+isCommit _ _ = False++-- | Check if an action synchronises a given 'CRef'.+synchronises :: ActionType -> CRefId -> Bool+synchronises a r = isCommit a r || isBarrier a++-- | Get the 'CRef' affected.+crefOf :: ActionType -> Maybe CRefId+crefOf (UnsynchronisedRead r) = Just r+crefOf (UnsynchronisedWrite r) = Just r+crefOf (SynchronisedModify r) = Just r+crefOf (PartiallySynchronisedCommit r) = Just r+crefOf (PartiallySynchronisedWrite r) = Just r+crefOf (PartiallySynchronisedModify r) = Just r+crefOf _ = Nothing++-- | Get the 'MVar' affected.+mvarOf :: ActionType -> Maybe MVarId+mvarOf (SynchronisedRead c) = Just c+mvarOf (SynchronisedWrite c) = Just c+mvarOf _ = Nothing++-- | Throw away information from a 'ThreadAction' and give a+-- simplified view of what is happening.+--+-- This is used in the SCT code to help determine interesting+-- alternative scheduling decisions.+simplifyAction :: ThreadAction -> ActionType+simplifyAction = maybe UnsynchronisedOther simplifyLookahead . rewind++-- | Variant of 'simplifyAction' that takes a 'Lookahead'.+simplifyLookahead :: Lookahead -> ActionType+simplifyLookahead (WillPutMVar c) = SynchronisedWrite c+simplifyLookahead (WillTryPutMVar c) = SynchronisedWrite c+simplifyLookahead (WillReadMVar c) = SynchronisedRead c+simplifyLookahead (WillTryReadMVar c) = SynchronisedRead c+simplifyLookahead (WillTakeMVar c) = SynchronisedRead c+simplifyLookahead (WillTryTakeMVar c) = SynchronisedRead c+simplifyLookahead (WillReadCRef r) = UnsynchronisedRead r+simplifyLookahead (WillReadCRefCas r) = UnsynchronisedRead r+simplifyLookahead (WillModCRef r) = SynchronisedModify r+simplifyLookahead (WillModCRefCas r) = PartiallySynchronisedModify r+simplifyLookahead (WillWriteCRef r) = UnsynchronisedWrite r+simplifyLookahead (WillCasCRef r) = PartiallySynchronisedWrite r+simplifyLookahead (WillCommitCRef _ r) = PartiallySynchronisedCommit r+simplifyLookahead WillSTM = SynchronisedOther+simplifyLookahead (WillThrowTo _) = SynchronisedOther+simplifyLookahead _ = UnsynchronisedOther++-------------------------------------------------------------------------------+-- * Error reporting++-- | 'head' but with a better error message if it fails. Use this+-- only where it shouldn't fail!+ehead :: String -> [a] -> a+ehead _ (x:_) = x+ehead src _ = fatal src "head: empty list"++-- | 'tail' but with a better error message if it fails. Use this+-- only where it shouldn't fail!+etail :: String -> [a] -> [a]+etail _ (_:xs) = xs+etail src _ = fatal src "tail: empty list"++-- | '(!!)' but with a better error message if it fails. Use this+-- only where it shouldn't fail!+eidx :: String -> [a] -> Int -> a+eidx src xs i+ | i < length xs = xs !! i+ | otherwise = fatal src "(!!): index too large"++-- | 'fromJust' but with a better error message if it fails. Use this+-- only where it shouldn't fail!+efromJust :: String -> Maybe a -> a+efromJust _ (Just x) = x+efromJust src _ = fatal src "fromJust: Nothing"++-- | 'fromList' but with a better error message if it fails. Use this+-- only where it shouldn't fail!+efromList :: String -> [a] -> NonEmpty a+efromList _ (x:xs) = x:|xs+efromList src _ = fatal src "fromList: empty list"++-- | 'error' but saying where it came from+fatal :: String -> String -> a+fatal src msg = error ("(dejafu: " ++ src ++ ") " ++ msg)++-------------------------------------------------------------------------------+-- * Miscellaneous++-- | Run with a continuation that writes its value into a reference,+-- returning the computation and the reference. Using the reference+-- is non-blocking, it is up to you to ensure you wait sufficiently.+runRefCont :: MonadRef r n => (n () -> x) -> (a -> Maybe b) -> ((a -> x) -> x) -> n (x, r (Maybe b))+runRefCont act f k = do+ ref <- newRef Nothing+ let c = k (act . writeRef ref . f)+ pure (c, ref)
Test/DejaFu/Refinement.hs view
@@ -106,8 +106,7 @@ ) where import Control.Arrow (first)-import Control.Monad (void)-import Control.Monad.Conc.Class (readMVar, spawn)+import Control.Monad.Conc.Class (fork) import Data.Maybe (isNothing) import Data.Set (Set) import qualified Data.Set as S@@ -410,9 +409,9 @@ results <- runSCT defaultWay defaultMemType $ do s <- initialise sig x r <- subconcurrency $ do- j <- spawn (interfere sig s x)- void (expression sig s)- void (readMVar j)+ _ <- fork (interfere sig s x)+ _ <- expression sig s+ pure () o <- observe sig s x pure (either Just (const Nothing) r, o) pure . S.fromList $ map (\(Right a, _) -> a) results
Test/DejaFu/SCT.hs view
@@ -4,7 +4,7 @@ -- | -- Module : Test.DejaFu.SCT--- Copyright : (c) 2016 Michael Walker+-- Copyright : (c) 2015--2017 Michael Walker -- License : MIT -- Maintainer : Michael Walker <mike@barrucadu.co.uk> -- Stability : experimental@@ -59,37 +59,12 @@ -- K. McKinley for more details. , Bounds(..)- , noBounds- , sctBound- , sctBoundDiscard-- -- ** Pre-emption Bounding-- -- | BPOR using pre-emption bounding. This adds conservative- -- backtracking points at the prior context switch whenever a- -- non-conervative backtracking point is added, as alternative- -- decisions can influence the reachability of different states.- --- -- See the BPOR paper for more details.- , PreemptionBound(..)-- -- ** Fair Bounding-- -- | BPOR using fair bounding. This bounds the maximum difference- -- between the number of yield operations different threads have- -- performed.- --- -- See the BPOR paper for more details.- , FairBound(..)-- -- ** Length Bounding-- -- | BPOR using length bounding. This bounds the maximum length (in- -- terms of primitive actions) of an execution.- , LengthBound(..)+ , noBounds+ , sctBound+ , sctBoundDiscard -- * Random Scheduling @@ -106,19 +81,23 @@ , sctWeightedRandomDiscard ) where -import Control.Applicative ((<|>))-import Control.DeepSeq (NFData(..), force)-import Control.Monad.Ref (MonadRef)-import Data.List (foldl')-import qualified Data.Map.Strict as M-import Data.Maybe (fromMaybe)-import Data.Set (Set)-import qualified Data.Set as S-import System.Random (RandomGen, randomR)+import Control.Applicative ((<|>))+import Control.DeepSeq (NFData(..), force)+import Control.Monad.Conc.Class (MonadConc)+import Control.Monad.Ref (MonadRef)+import Data.List (foldl')+import qualified Data.Map.Strict as M+import Data.Maybe (fromMaybe)+import Data.Set (Set)+import qualified Data.Set as S+import System.Random (RandomGen, randomR) -import Test.DejaFu.Common import Test.DejaFu.Conc-import Test.DejaFu.SCT.Internal+import Test.DejaFu.Internal+import Test.DejaFu.SCT.Internal.DPOR+import Test.DejaFu.SCT.Internal.Weighted+import Test.DejaFu.Types+import Test.DejaFu.Utils ------------------------------------------------------------------------------- -- Running Concurrent Programs@@ -207,8 +186,11 @@ -- | Explore possible executions of a concurrent program according to -- the given 'Way'. ----- @since 0.6.0.0-runSCT :: MonadRef r n+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases.+--+-- @since 1.0.0.0+runSCT :: (MonadConc n, MonadRef r n) => Way -- ^ How to run the concurrent program. -> MemType@@ -220,8 +202,8 @@ -- | Return the set of results of a concurrent program. ----- @since 0.6.0.0-resultsSet :: (MonadRef r n, Ord a)+-- @since 1.0.0.0+resultsSet :: (MonadConc n, MonadRef r n, Ord a) => Way -- ^ How to run the concurrent program. -> MemType@@ -231,26 +213,13 @@ -> n (Set (Either Failure a)) resultsSet = resultsSetDiscard (const Nothing) --- | An @Either Failure a -> Maybe Discard@ value can be used to--- selectively discard results.------ @since 0.7.1.0-data Discard- = DiscardTrace- -- ^ Discard the trace but keep the result. The result will appear- -- to have an empty trace.- | DiscardResultAndTrace- -- ^ Discard the result and the trace. It will simply not be- -- reported as a possible behaviour of the program.- deriving (Eq, Show, Read, Ord, Enum, Bounded)--instance NFData Discard where- rnf d = d `seq` ()- -- | A variant of 'runSCT' which can selectively discard results. ----- @since 0.7.1.0-runSCTDiscard :: MonadRef r n+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases.+--+-- @since 1.0.0.0+runSCTDiscard :: (MonadConc n, MonadRef r n) => (Either Failure a -> Maybe Discard) -- ^ Selectively discard results. -> Way@@ -266,8 +235,8 @@ -- | A variant of 'resultsSet' which can selectively discard results. ----- @since 0.7.1.0-resultsSetDiscard :: (MonadRef r n, Ord a)+-- @since 1.0.0.0+resultsSetDiscard :: (MonadConc n, MonadRef r n, Ord a) => (Either Failure a -> Maybe Discard) -- ^ Selectively discard results. Traces are always discarded. -> Way@@ -286,8 +255,11 @@ -- Demanding the result of this will force it to normal form, which -- may be more efficient in some situations. ----- @since 0.6.0.0-runSCT' :: (MonadRef r n, NFData a)+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases.+--+-- @since 1.0.0.0+runSCT' :: (MonadConc n, MonadRef r n, NFData a) => Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)] runSCT' = runSCTDiscard' (const Nothing) @@ -296,8 +268,8 @@ -- Demanding the result of this will force it to normal form, which -- may be more efficient in some situations. ----- @since 0.6.0.0-resultsSet' :: (MonadRef r n, Ord a, NFData a)+-- @since 1.0.0.0+resultsSet' :: (MonadConc n, MonadRef r n, Ord a, NFData a) => Way -> MemType -> ConcT r n a -> n (Set (Either Failure a)) resultsSet' = resultsSetDiscard' (const Nothing) @@ -306,8 +278,11 @@ -- Demanding the result of this will force it to normal form, which -- may be more efficient in some situations. ----- @since 0.7.1.0-runSCTDiscard' :: (MonadRef r n, NFData a)+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases.+--+-- @since 1.0.0.0+runSCTDiscard' :: (MonadConc n, MonadRef r n, NFData a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n [(Either Failure a, Trace)] runSCTDiscard' discard way memtype conc = do res <- runSCTDiscard discard way memtype conc@@ -318,8 +293,8 @@ -- Demanding the result of this will force it to normal form, which -- may be more efficient in some situations. ----- @since 0.7.1.0-resultsSetDiscard' :: (MonadRef r n, Ord a, NFData a)+-- @since 1.0.0.0+resultsSetDiscard' :: (MonadConc n, MonadRef r n, Ord a, NFData a) => (Either Failure a -> Maybe Discard) -> Way -> MemType -> ConcT r n a -> n (Set (Either Failure a)) resultsSetDiscard' discard way memtype conc = do res <- resultsSetDiscard discard way memtype conc@@ -376,7 +351,14 @@ ------------------------------------------------------------------------------- -- Pre-emption bounding --- | @since 0.2.0.0+-- | BPOR using pre-emption bounding. This adds conservative+-- backtracking points at the prior context switch whenever a+-- non-conervative backtracking point is added, as alternative+-- decisions can influence the reachability of different states.+--+-- See the BPOR paper for more details.+--+-- @since 0.2.0.0 newtype PreemptionBound = PreemptionBound Int deriving (Enum, Eq, Ord, Num, Real, Integral, Read, Show) @@ -415,7 +397,13 @@ ------------------------------------------------------------------------------- -- Fair bounding --- | @since 0.2.0.0+-- | BPOR using fair bounding. This bounds the maximum difference+-- between the number of yield operations different threads have+-- performed.+--+-- See the BPOR paper for more details.+--+-- @since 0.2.0.0 newtype FairBound = FairBound Int deriving (Enum, Eq, Ord, Num, Real, Integral, Read, Show) @@ -432,8 +420,8 @@ then Just k' else Nothing --- | Add a backtrack point. If the thread isn't runnable, or performs--- a release operation, add all runnable threads.+-- | Add a backtrack point. If the thread doesn't exist or is blocked,+-- or performs a release operation, add all unblocked threads. fBacktrack :: BacktrackFunc fBacktrack = backtrackAt check where -- True if a release operation is performed.@@ -442,7 +430,10 @@ ------------------------------------------------------------------------------- -- Length bounding --- | @since 0.2.0.0+-- | BPOR using length bounding. This bounds the maximum length (in+-- terms of primitive actions) of an execution.+--+-- @since 0.2.0.0 newtype LengthBound = LengthBound Int deriving (Enum, Eq, Ord, Num, Real, Integral, Read, Show) @@ -457,8 +448,8 @@ let len' = maybe 1 (+1) len in if len' < lb then Just len' else Nothing --- | Add a backtrack point. If the thread isn't runnable, add all--- runnable threads.+-- | Add a backtrack point. If the thread doesn't exist or is blocked,+-- add all unblocked threads. lBacktrack :: BacktrackFunc lBacktrack = backtrackAt (\_ _ -> False) @@ -477,8 +468,11 @@ -- do some redundant work as the introduction of a bound can make -- previously non-interfering events interfere with each other. ----- @since 0.5.0.0-sctBound :: MonadRef r n+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases.+--+-- @since 1.0.0.0+sctBound :: (MonadConc n, MonadRef r n) => MemType -- ^ The memory model to use for non-synchronised @CRef@ operations. -> Bounds@@ -490,8 +484,11 @@ -- | A variant of 'sctBound' which can selectively discard results. ----- @since 0.7.1.0-sctBoundDiscard :: MonadRef r n+-- The exact executions tried, and the order in which results are+-- found, is unspecified and may change between releases.+--+-- @since 1.0.0.0+sctBoundDiscard :: (MonadConc n, MonadRef r n) => (Either Failure a -> Maybe Discard) -- ^ Selectively discard results. -> MemType@@ -533,8 +530,8 @@ -- -- This is not guaranteed to find all distinct results. ----- @since 0.7.0.0-sctUniformRandom :: (MonadRef r n, RandomGen g)+-- @since 1.0.0.0+sctUniformRandom :: (MonadConc n, MonadRef r n, RandomGen g) => MemType -- ^ The memory model to use for non-synchronised @CRef@ operations. -> g@@ -549,8 +546,10 @@ -- | A variant of 'sctUniformRandom' which can selectively discard -- results. ----- @since 0.7.1.0-sctUniformRandomDiscard :: (MonadRef r n, RandomGen g)+-- This is not guaranteed to find all distinct results.+--+-- @since 1.0.0.0+sctUniformRandomDiscard :: (MonadConc n, MonadRef r n, RandomGen g) => (Either Failure a -> Maybe Discard) -- ^ Selectively discard results. -> MemType@@ -578,8 +577,8 @@ -- -- This is not guaranteed to find all distinct results. ----- @since 0.7.0.0-sctWeightedRandom :: (MonadRef r n, RandomGen g)+-- @since 1.0.0.0+sctWeightedRandom :: (MonadConc n, MonadRef r n, RandomGen g) => MemType -- ^ The memory model to use for non-synchronised @CRef@ operations. -> g@@ -596,8 +595,10 @@ -- | A variant of 'sctWeightedRandom' which can selectively discard -- results. ----- @since 0.7.1.0-sctWeightedRandomDiscard :: (MonadRef r n, RandomGen g)+-- This is not guaranteed to find all distinct results.+--+-- @since 1.0.0.0+sctWeightedRandomDiscard :: (MonadConc n, MonadRef r n, RandomGen g) => (Either Failure a -> Maybe Discard) -- ^ Selectively discard results. -> MemType
− Test/DejaFu/SCT/Internal.hs
@@ -1,816 +0,0 @@-{-# LANGUAGE TupleSections #-}---- |--- Module : Test.DejaFu.SCT.Internal--- Copyright : (c) 2016 Michael Walker--- License : MIT--- Maintainer : Michael Walker <mike@barrucadu.co.uk>--- Stability : experimental--- Portability : TupleSections------ Internal types and functions for dynamic partial-order--- reduction. This module is NOT considered to form part of the public--- interface of this library.-module Test.DejaFu.SCT.Internal where--import Control.Applicative ((<|>))-import Control.DeepSeq (NFData(..))-import Control.Exception (MaskingState(..))-import qualified Data.Foldable as F-import Data.Function (on)-import Data.List (nubBy, partition, sortOn)-import Data.List.NonEmpty (toList)-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as M-import Data.Maybe (fromMaybe, isJust, isNothing,- listToMaybe)-import Data.Sequence (Seq, (|>))-import qualified Data.Sequence as Sq-import Data.Set (Set)-import qualified Data.Set as S-import System.Random (RandomGen, randomR)--import Test.DejaFu.Common-import Test.DejaFu.Schedule (Scheduler(..), decisionOf, tidOf)------------------------------------------------------------------------------------ * Dynamic partial-order reduction---- | DPOR execution is represented as a tree of states, characterised--- by the decisions that lead to that state.-data DPOR = DPOR- { dporRunnable :: Set ThreadId- -- ^ What threads are runnable at this step.- , dporTodo :: Map ThreadId Bool- -- ^ Follow-on decisions still to make, and whether that decision- -- was added conservatively due to the bound.- , dporNext :: Maybe (ThreadId, DPOR)- -- ^ The next decision made. Executions are explored in a- -- depth-first fashion, so this changes as old subtrees are- -- exhausted and new ones explored.- , dporDone :: Set ThreadId- -- ^ All transitions which have been taken from this point,- -- including conservatively-added ones.- , dporSleep :: Map ThreadId ThreadAction- -- ^ Transitions to ignore (in this node and children) until a- -- dependent transition happens.- , dporTaken :: Map ThreadId ThreadAction- -- ^ Transitions which have been taken, excluding- -- conservatively-added ones. This is used in implementing sleep- -- sets.- } deriving (Eq, Show)--instance NFData DPOR where- rnf dpor = rnf ( dporRunnable dpor- , dporTodo dpor- , dporNext dpor- , dporDone dpor- , dporSleep dpor- , dporTaken dpor- )---- | One step of the execution, including information for backtracking--- purposes. This backtracking information is used to generate new--- schedules.-data BacktrackStep = BacktrackStep- { bcktThreadid :: ThreadId- -- ^ The thread running at this step- , bcktDecision :: Decision- -- ^ What was decided at this step.- , bcktAction :: ThreadAction- -- ^ What happened at this step.- , bcktRunnable :: Map ThreadId Lookahead- -- ^ The threads runnable at this step- , bcktBacktracks :: Map ThreadId Bool- -- ^ The list of alternative threads to run, and whether those- -- alternatives were added conservatively due to the bound.- , bcktState :: DepState- -- ^ Some domain-specific state at this point.- } deriving (Eq, Show)--instance NFData BacktrackStep where- rnf bs = rnf ( bcktThreadid bs- , bcktDecision bs- , bcktAction bs- , bcktRunnable bs- , bcktBacktracks bs- , bcktState bs- )---- | Initial DPOR state, given an initial thread ID. This initial--- thread should exist and be runnable at the start of execution.-initialState :: DPOR-initialState = DPOR- { dporRunnable = S.singleton initialThread- , dporTodo = M.singleton initialThread False- , dporNext = Nothing- , dporDone = S.empty- , dporSleep = M.empty- , dporTaken = M.empty- }---- | Produce a new schedule prefix from a @DPOR@ tree. If there are no new--- prefixes remaining, return 'Nothing'. Also returns whether the--- decision was added conservatively, and the sleep set at the point--- where divergence happens.------ A schedule prefix is a possibly empty sequence of decisions that--- have already been made, terminated by a single decision from the--- to-do set. The intent is to put the system into a new state when--- executed with this initial sequence of scheduling decisions.-findSchedulePrefix- :: DPOR- -> Maybe ([ThreadId], Bool, Map ThreadId ThreadAction)-findSchedulePrefix dpor = case dporNext dpor of- Just (tid, child) -> go tid child <|> here- Nothing -> here- where- go tid child = (\(ts,c,slp) -> (tid:ts,c,slp)) <$> findSchedulePrefix child-- -- Prefix traces terminating with a to-do decision at this point.- here =- let todos = [([t], c, sleeps) | (t, c) <- M.toList $ dporTodo dpor]- (best, worst) = partition (\([t],_,_) -> t >= initialThread) todos- in listToMaybe best <|> listToMaybe worst-- -- The new sleep set is the union of the sleep set of the node- -- we're branching from, plus all the decisions we've already- -- explored.- sleeps = dporSleep dpor `M.union` dporTaken dpor---- | Add a new trace to the stack. This won't work if to-dos aren't explored depth-first.-incorporateTrace- :: Bool- -- ^ Whether the \"to-do\" point which was used to create this new- -- execution was conservative or not.- -> Trace- -- ^ The execution trace: the decision made, the runnable threads,- -- and the action performed.- -> DPOR- -> DPOR-incorporateTrace conservative trace dpor0 = grow initialDepState (initialDPORThread dpor0) trace dpor0 where- grow state tid trc@((d, _, a):rest) dpor =- let tid' = tidOf tid d- state' = updateDepState state tid' a- in case dporNext dpor of- Just (t, child)- | t == tid' -> dpor { dporNext = Just (tid', grow state' tid' rest child) }- | hasTodos child -> fatal "incorporateTrace" "replacing child with todos!"- _ ->- let taken = M.insert tid' a (dporTaken dpor)- sleep = dporSleep dpor `M.union` dporTaken dpor- in dpor { dporTaken = if conservative then dporTaken dpor else taken- , dporTodo = M.delete tid' (dporTodo dpor)- , dporNext = Just (tid', subtree state' tid' sleep trc)- , dporDone = S.insert tid' (dporDone dpor)- }- grow _ _ [] _ = fatal "incorporateTrace" "trace exhausted without reading a to-do point!"-- -- check if there are to-do points in a tree- hasTodos dpor = not (M.null (dporTodo dpor)) || (case dporNext dpor of Just (_, dpor') -> hasTodos dpor'; _ -> False)-- -- Construct a new subtree corresponding to a trace suffix.- subtree state tid sleep ((_, _, a):rest) =- let state' = updateDepState state tid a- sleep' = M.filterWithKey (\t a' -> not $ dependent state' tid a t a') sleep- in DPOR- { dporRunnable = S.fromList $ case rest of- ((_, runnable, _):_) -> map fst runnable- [] -> []- , dporTodo = M.empty- , dporNext = case rest of- ((d', _, _):_) ->- let tid' = tidOf tid d'- in Just (tid', subtree state' tid' sleep' rest)- [] -> Nothing- , dporDone = case rest of- ((d', _, _):_) -> S.singleton (tidOf tid d')- [] -> S.empty- , dporSleep = sleep'- , dporTaken = case rest of- ((d', _, a'):_) -> M.singleton (tidOf tid d') a'- [] -> M.empty- }- subtree _ _ _ [] = fatal "incorporateTrace" "subtree suffix empty!"---- | Produce a list of new backtracking points from an execution--- trace. These are then used to inform new \"to-do\" points in the--- @DPOR@ tree.------ Two traces are passed in to this function: the first is generated--- from the special DPOR scheduler, the other from the execution of--- the concurrent program.------ If the trace ends with any threads other than the initial one still--- runnable, a dependency is imposed between this final action and--- everything else.-findBacktrackSteps- :: BacktrackFunc- -- ^ Backtracking function. Given a list of backtracking points, and- -- a thread to backtrack to at a specific point in that list, add- -- the new backtracking points. There will be at least one: this- -- chosen one, but the function may add others.- -> Bool- -- ^ Whether the computation was aborted due to no decisions being- -- in-bounds.- -> Seq ([(ThreadId, Lookahead)], [ThreadId])- -- ^ A sequence of threads at each step: the list of runnable- -- in-bound threads (with lookahead values), and the list of threads- -- still to try. The reason for the two separate lists is because- -- the threads chosen to try will be dependent on the specific- -- domain.- -> Trace- -- ^ The execution trace.- -> [BacktrackStep]-findBacktrackSteps backtrack boundKill = go initialDepState S.empty initialThread [] . F.toList where- -- Walk through the traces one step at a time, building up a list of- -- new backtracking points.- go state allThreads tid bs ((e,i):is) ((d,_,a):ts) =- let tid' = tidOf tid d- state' = updateDepState state tid' a- this = BacktrackStep- { bcktThreadid = tid'- , bcktDecision = d- , bcktAction = a- , bcktRunnable = M.fromList e- , bcktBacktracks = M.fromList $ map (\i' -> (i', False)) i- , bcktState = state- }- bs' = doBacktrack killsEarly allThreads' e (bs++[this])- runnable = S.fromList (M.keys $ bcktRunnable this)- allThreads' = allThreads `S.union` runnable- killsEarly = null ts && boundKill- in go state' allThreads' tid' bs' is ts- go _ _ _ bs _ _ = bs-- -- Find the prior actions dependent with this one and add- -- backtracking points.- doBacktrack killsEarly allThreads enabledThreads bs =- let tagged = reverse $ zip [0..] bs- idxs = [ (ehead "doBacktrack.idxs" is, False, u)- | (u, n) <- enabledThreads- , v <- S.toList allThreads- , u /= v- , let is = idxs' u n v tagged- , not $ null is]-- idxs' u n v = go' True where- {-# INLINE go' #-}- go' final ((i,b):rest)- -- Don't cross subconcurrency boundaries- | isSubC final b = []- -- If this is the final action in the trace and the- -- execution was killed due to nothing being within bounds- -- (@killsEarly == True@) assume worst-case dependency.- | bcktThreadid b == v && (killsEarly || isDependent b) = i : go' False rest- | otherwise = go' False rest- go' _ [] = []-- {-# INLINE isSubC #-}- isSubC final b = case bcktAction b of- Stop -> not final && bcktThreadid b == initialThread- Subconcurrency -> bcktThreadid b == initialThread- _ -> False-- {-# INLINE isDependent #-}- isDependent b- -- Don't impose a dependency if the other thread will- -- immediately block already. This is safe because a- -- context switch will occur anyway so there's no point- -- pre-empting the action UNLESS the pre-emption would- -- possibly allow for a different relaxed memory stage.- | isBlock (bcktAction b) && isBarrier (simplifyLookahead n) = False- | otherwise = dependent' (bcktState b) (bcktThreadid b) (bcktAction b) u n- in backtrack bs idxs---- | Add new backtracking points, if they have not already been--- visited and aren't in the sleep set.-incorporateBacktrackSteps :: [BacktrackStep] -> DPOR -> DPOR-incorporateBacktrackSteps (b:bs) dpor = dpor' where- tid = bcktThreadid b-- dpor' = dpor- { dporTodo = dporTodo dpor `M.union` M.fromList todo- , dporNext = Just (tid, child)- }-- todo =- [ x- | x@(t,c) <- M.toList $ bcktBacktracks b- , Just t /= (fst <$> dporNext dpor)- , S.notMember t (dporDone dpor)- , c || M.notMember t (dporSleep dpor)- ]-- child = case dporNext dpor of- Just (t, d)- | t /= tid -> fatal "incorporateBacktrackSteps" "incorporating wrong trace!"- | otherwise -> incorporateBacktrackSteps bs d- Nothing -> fatal "incorporateBacktrackSteps" "child is missing!"-incorporateBacktrackSteps [] dpor = dpor------------------------------------------------------------------------------------ * DPOR scheduler---- | The scheduler state-data DPORSchedState k = DPORSchedState- { schedSleep :: Map ThreadId ThreadAction- -- ^ The sleep set: decisions not to make until something dependent- -- with them happens.- , schedPrefix :: [ThreadId]- -- ^ Decisions still to make- , schedBPoints :: Seq ([(ThreadId, Lookahead)], [ThreadId])- -- ^ Which threads are runnable and in-bound at each step, and the- -- alternative decisions still to make.- , schedIgnore :: Bool- -- ^ Whether to ignore this execution or not: @True@ if the- -- execution is aborted due to all possible decisions being in the- -- sleep set, as then everything in this execution is covered by- -- another.- , schedBoundKill :: Bool- -- ^ Whether the execution was terminated due to all decisions being- -- out of bounds.- , schedDepState :: DepState- -- ^ State used by the dependency function to determine when to- -- remove decisions from the sleep set.- , schedBState :: Maybe k- -- ^ State used by the incremental bounding function.- } deriving (Eq, Show)--instance NFData k => NFData (DPORSchedState k) where- rnf s = rnf ( schedSleep s- , schedPrefix s- , schedBPoints s- , schedIgnore s- , schedBoundKill s- , schedDepState s- , schedBState s- )---- | Initial DPOR scheduler state for a given prefix-initialDPORSchedState :: Map ThreadId ThreadAction- -- ^ The initial sleep set.- -> [ThreadId]- -- ^ The schedule prefix.- -> DPORSchedState k-initialDPORSchedState sleep prefix = DPORSchedState- { schedSleep = sleep- , schedPrefix = prefix- , schedBPoints = Sq.empty- , schedIgnore = False- , schedBoundKill = False- , schedDepState = initialDepState- , schedBState = Nothing- }---- | An incremental bounding function is a stateful function that--- takes the last and next decisions, and returns a new state only if--- the next decision is within the bound.-type IncrementalBoundFunc k- = Maybe k -> Maybe (ThreadId, ThreadAction) -> (Decision, Lookahead) -> Maybe k---- | A backtracking step is a point in the execution where another--- decision needs to be made, in order to explore interesting new--- schedules. A backtracking /function/ takes the steps identified so--- far and a list of points and thread at that point to backtrack--- to. More points be added to compensate for the effects of the--- bounding function. For example, under pre-emption bounding a--- conservative backtracking point is added at the prior context--- switch. The bool is whether the point is conservative. Conservative--- points are always explored, whereas non-conservative ones might be--- skipped based on future information.------ In general, a backtracking function should identify one or more--- backtracking points, and then use @backtrackAt@ to do the actual--- work.-type BacktrackFunc- = [BacktrackStep] -> [(Int, Bool, ThreadId)] -> [BacktrackStep]---- | Add a backtracking point. If the thread isn't runnable, add all--- runnable threads. If the backtracking point is already present,--- don't re-add it UNLESS this would make it conservative.-backtrackAt- :: (ThreadId -> BacktrackStep -> Bool)- -- ^ If this returns @True@, backtrack to all runnable threads,- -- rather than just the given thread.- -> BacktrackFunc-backtrackAt toAll bs0 = backtrackAt' . nubBy ((==) `on` fst') . sortOn fst' where- fst' (x,_,_) = x-- backtrackAt' ((i,c,t):is) = go i bs0 i c t is- backtrackAt' [] = bs0-- go i0 (b:bs) 0 c tid is- -- If the backtracking point is already present, don't re-add it,- -- UNLESS this would force it to backtrack (it's conservative)- -- where before it might not.- | not (toAll tid b) && tid `M.member` bcktRunnable b =- let val = M.lookup tid $ bcktBacktracks b- b' = if isNothing val || (val == Just False && c)- then b { bcktBacktracks = backtrackTo tid c b }- else b- in b' : case is of- ((i',c',t'):is') -> go i' bs (i'-i0-1) c' t' is'- [] -> bs- -- Otherwise just backtrack to everything runnable.- | otherwise =- let b' = b { bcktBacktracks = backtrackAll c b }- in b' : case is of- ((i',c',t'):is') -> go i' bs (i'-i0-1) c' t' is'- [] -> bs- go i0 (b:bs) i c tid is = b : go i0 bs (i-1) c tid is- go _ [] _ _ _ _ = fatal "backtrackAt" "ran out of schedule whilst backtracking!"-- -- Backtrack to a single thread- backtrackTo tid c = M.insert tid c . bcktBacktracks-- -- Backtrack to all runnable threads- backtrackAll c = M.map (const c) . bcktRunnable---- | DPOR scheduler: takes a list of decisions, and maintains a trace--- including the runnable threads, and the alternative choices allowed--- by the bound-specific initialise function.------ After the initial decisions are exhausted, this prefers choosing--- the prior thread if it's (1) still runnable and (2) hasn't just--- yielded. Furthermore, threads which /will/ yield are ignored in--- preference of those which will not.-dporSched- :: IncrementalBoundFunc k- -- ^ Bound function: returns true if that schedule prefix terminated- -- with the lookahead decision fits within the bound.- -> Scheduler (DPORSchedState k)-dporSched boundf = Scheduler $ \prior threads s ->- let- -- The next scheduler state- nextState rest = s- { schedBPoints = schedBPoints s |> (restrictToBound fst threads', rest)- , schedDepState = nextDepState- }- nextDepState = let ds = schedDepState s in maybe ds (uncurry $ updateDepState ds) prior-- -- Pick a new thread to run, not considering bounds. Choose the- -- current thread if available and it hasn't just yielded,- -- otherwise add all runnable threads.- initialise = tryDaemons . yieldsToEnd $ case prior of- Just (tid, act)- | not (didYield act) && tid `elem` tids && isInBound tid -> [tid]- _ -> tids-- -- If one of the chosen actions will kill the computation, and- -- there are daemon threads, try them instead.- --- -- This is necessary if the killing action is NOT dependent with- -- every other action, according to the dependency function. This- -- is, strictly speaking, wrong; an action that kills another- -- thread is definitely dependent with everything in that- -- thread. HOWEVER, implementing it that way leads to an explosion- -- of schedules tried. Really, all that needs to happen is for the- -- thread-that-would-be-killed to be executed fully ONCE, and then- -- the normal dependency mechanism will identify any other- -- backtracking points that should be tried. This is achieved by- -- adding every thread that would be killed to the to-do list.- -- Furthermore, these threads MUST be ahead of the killing thread,- -- or the killing thread will end up in the sleep set and so the- -- killing action not performed. This is, again, because of the- -- lack of the dependency messing things up in the name of- -- performance.- --- -- See commits a056f54 and 8554ce9, and my 4th June comment in- -- issue #52.- tryDaemons ts- | any doesKill ts = case partition doesKill tids of- (kills, nokills) -> nokills ++ kills- | otherwise = ts- doesKill t = killsDaemons t (action t)-- -- Restrict the possible decisions to those in the bound.- restrictToBound f = filter (isInBound . f)- isInBound t = isJust $ boundf (schedBState s) prior (decision t, action t)-- -- Move the threads which will immediately yield to the end of the list- yieldsToEnd ts = case partition (willYield . action) ts of- (yields, noyields) -> noyields ++ yields-- -- Get the decision that will lead to a thread being scheduled.- decision = decisionOf (fst <$> prior) (S.fromList tids)-- -- Get the action of a thread- action t = efromJust "dporSched.action" (lookup t threads')-- -- The runnable thread IDs- tids = map fst threads'-- -- The runnable threads as a normal list.- threads' = toList threads- in case schedPrefix s of- -- If there is a decision available, make it- (t:ts) ->- let bstate' = boundf (schedBState s) prior (decision t, action t)- in (Just t, (nextState []) { schedPrefix = ts, schedBState = bstate' })-- -- Otherwise query the initialise function for a list of possible- -- choices, filter out anything in the sleep set, and make one of- -- them arbitrarily (recording the others).- [] ->- let choices = restrictToBound id initialise- checkDep t a = case prior of- Just (tid, act) -> dependent (schedDepState s) tid act t a- Nothing -> False- ssleep' = M.filterWithKey (\t a -> not $ checkDep t a) $ schedSleep s- choices' = filter (`notElem` M.keys ssleep') choices- signore' = not (null choices) && all (`elem` M.keys ssleep') choices- sbkill' = not (null initialise) && null choices- in case choices' of- (nextTid:rest) ->- let bstate' = boundf (schedBState s) prior (decision nextTid, action nextTid)- in (Just nextTid, (nextState rest) { schedSleep = ssleep', schedBState = bstate' })- [] ->- (Nothing, (nextState []) { schedIgnore = signore', schedBoundKill = sbkill', schedBState = Nothing })------------------------------------------------------------------------------------ Weighted random scheduler---- | The scheduler state-data RandSchedState g = RandSchedState- { schedWeights :: Map ThreadId Int- -- ^ The thread weights: used in determining which to run.- , schedGen :: g- -- ^ The random number generator.- } deriving (Eq, Show)--instance NFData g => NFData (RandSchedState g) where- rnf s = rnf ( schedWeights s- , schedGen s- )---- | Initial weighted random scheduler state.-initialRandSchedState :: Maybe (Map ThreadId Int) -> g -> RandSchedState g-initialRandSchedState = RandSchedState . fromMaybe M.empty---- | Weighted random scheduler: assigns to each new thread a weight,--- and makes a weighted random choice out of the runnable threads at--- every step.-randSched :: RandomGen g => (g -> (Int, g)) -> Scheduler (RandSchedState g)-randSched weightf = Scheduler $ \_ threads s ->- let- -- Select a thread- pick idx ((x, f):xs)- | idx < f = Just x- | otherwise = pick (idx - f) xs- pick _ [] = Nothing- (choice, g'') = randomR (0, sum (map snd enabled) - 1) g'- enabled = M.toList $ M.filterWithKey (\tid _ -> tid `elem` tids) weights'-- -- The weights, with any new threads added.- (weights', g') = foldr assignWeight (M.empty, schedGen s) tids- assignWeight tid ~(ws, g0) =- let (w, g) = maybe (weightf g0) (,g0) (M.lookup tid (schedWeights s))- in (M.insert tid w ws, g)-- -- The runnable threads.- tids = map fst (toList threads)- in (pick choice enabled, RandSchedState weights' g'')------------------------------------------------------------------------------------ Dependency function---- | Check if an action is dependent on another.------ This is basically the same as 'dependent'', but can make use of the--- additional information in a 'ThreadAction' to make better decisions--- in a few cases.-dependent :: DepState -> ThreadId -> ThreadAction -> ThreadId -> ThreadAction -> Bool-dependent ds t1 a1 t2 a2 = case (a1, a2) of- -- @SetNumCapabilities@ and @GetNumCapabilities@ are NOT dependent- -- IF the value read is the same as the value written. 'dependent''- -- can not see the value read (as it hasn't happened yet!), and so- -- is more pessimistic here.- (SetNumCapabilities a, GetNumCapabilities b) | a == b -> False- (GetNumCapabilities a, SetNumCapabilities b) | a == b -> False-- -- When masked interruptible, a thread can only be interrupted when- -- actually blocked. 'dependent'' has to assume that all- -- potentially-blocking operations can block, and so is more- -- pessimistic in this case.- (ThrowTo t, _) | t == t2 -> canInterrupt ds t2 a2 && a2 /= Stop- (_, ThrowTo t) | t == t1 -> canInterrupt ds t1 a1 && a1 /= Stop-- -- Dependency of STM transactions can be /greatly/ improved here, as- -- the 'Lookahead' does not know which @TVar@s will be touched, and- -- so has to assume all transactions are dependent.- (STM _ _, STM _ _) -> checkSTM- (STM _ _, BlockedSTM _) -> checkSTM- (BlockedSTM _, STM _ _) -> checkSTM- (BlockedSTM _, BlockedSTM _) -> checkSTM-- _ -> case (,) <$> rewind a1 <*> rewind a2 of- Just (l1, l2) -> dependent' ds t1 a1 t2 l2 && dependent' ds t2 a2 t1 l1- _ -> dependentActions ds (simplifyAction a1) (simplifyAction a2)-- where- -- STM actions A and B are dependent if A wrote to anything B- -- touched, or vice versa.- checkSTM = checkSTM' a1 a2 || checkSTM' a2 a1- checkSTM' a b = not . S.null $ tvarsWritten a `S.intersection` tvarsOf b---- | Variant of 'dependent' to handle 'Lookahead'.------ Termination of the initial thread is handled specially in the DPOR--- implementation.-dependent' :: DepState -> ThreadId -> ThreadAction -> ThreadId -> Lookahead -> Bool-dependent' ds t1 a1 t2 l2 = case (a1, l2) of- -- Worst-case assumption: all IO is dependent.- (LiftIO, WillLiftIO) -> True-- -- Throwing an exception is only dependent with actions in that- -- thread and if the actions can be interrupted. We can also- -- slightly improve on that by not considering interrupting the- -- normal termination of a thread: it doesn't make a difference.- (ThrowTo t, WillStop) | t == t2 -> False- (Stop, WillThrowTo t) | t == t1 -> False- (ThrowTo t, _) | t == t2 -> canInterruptL ds t2 l2- (_, WillThrowTo t) | t == t1 -> canInterrupt ds t1 a1-- -- Another worst-case: assume all STM is dependent.- (STM _ _, WillSTM) -> True-- -- This is a bit pessimistic: Set/Get are only dependent if the- -- value set is not the same as the value that will be got, but we- -- can't know that here. 'dependent' optimises this case.- (GetNumCapabilities a, WillSetNumCapabilities b) -> a /= b- (SetNumCapabilities _, WillGetNumCapabilities) -> True- (SetNumCapabilities a, WillSetNumCapabilities b) -> a /= b-- _ -> dependentActions ds (simplifyAction a1) (simplifyLookahead l2)---- | Check if two 'ActionType's are dependent. Note that this is not--- sufficient to know if two 'ThreadAction's are dependent, without--- being so great an over-approximation as to be useless!-dependentActions :: DepState -> ActionType -> ActionType -> Bool-dependentActions ds a1 a2 = case (a1, a2) of- -- Unsynchronised reads and writes are always dependent, even under- -- a relaxed memory model, as an unsynchronised write gives rise to- -- a commit, which synchronises.- (UnsynchronisedRead r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> a2 /= UnsynchronisedRead r1- (UnsynchronisedWrite r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True- (PartiallySynchronisedWrite r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True- (PartiallySynchronisedModify r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True- (SynchronisedModify r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True-- -- Unsynchronised writes and synchronisation where the buffer is not- -- empty.- --- -- See [RMMVerification], lemma 5.25.- (UnsynchronisedWrite r1, PartiallySynchronisedCommit _) | same crefOf && isBuffered ds r1 -> False- (PartiallySynchronisedCommit _, UnsynchronisedWrite r2) | same crefOf && isBuffered ds r2 -> False-- -- Unsynchronised reads where a memory barrier would flush a- -- buffered write- (UnsynchronisedRead r1, _) | isBarrier a2 -> isBuffered ds r1- (_, UnsynchronisedRead r2) | isBarrier a1 -> isBuffered ds r2-- -- Commits and memory barriers must be dependent, as memory barriers- -- (currently) flush in a consistent order. Alternative orders need- -- to be explored as well. Perhaps a better implementation of- -- memory barriers would just block every non-commit thread while- -- any buffer is nonempty.- (PartiallySynchronisedCommit _, _) | isBarrier a2 -> True- (_, PartiallySynchronisedCommit _) | isBarrier a1 -> True-- (_, _) -> case getSame crefOf of- -- Two actions on the same CRef where at least one is synchronised- Just r -> synchronises a1 r || synchronises a2 r- -- Two actions on the same MVar- _ -> same mvarOf-- where- same :: Eq a => (ActionType -> Maybe a) -> Bool- same = isJust . getSame-- getSame :: Eq a => (ActionType -> Maybe a) -> Maybe a- getSame f =- let f1 = f a1- f2 = f a2- in if f1 == f2 then f1 else Nothing------------------------------------------------------------------------------------ Dependency function state--data DepState = DepState- { depCRState :: Map CRefId Bool- -- ^ Keep track of which @CRef@s have buffered writes.- , depMaskState :: Map ThreadId MaskingState- -- ^ Keep track of thread masking states. If a thread isn't present,- -- the masking state is assumed to be @Unmasked@. This nicely- -- provides compatibility with dpor-0.1, where the thread IDs are- -- not available.- } deriving (Eq, Show)--instance NFData DepState where- rnf depstate = rnf ( depCRState depstate- , [(t, m `seq` ()) | (t, m) <- M.toList (depMaskState depstate)]- )---- | Initial dependency state.-initialDepState :: DepState-initialDepState = DepState M.empty M.empty---- | Update the 'CRef' buffer state with the action that has just--- happened.-updateDepState :: DepState -> ThreadId -> ThreadAction -> DepState-updateDepState depstate tid act = DepState- { depCRState = updateCRState act $ depCRState depstate- , depMaskState = updateMaskState tid act $ depMaskState depstate- }---- | Update the 'CRef' buffer state with the action that has just--- happened.-updateCRState :: ThreadAction -> Map CRefId Bool -> Map CRefId Bool-updateCRState (CommitCRef _ r) = M.delete r-updateCRState (WriteCRef r) = M.insert r True-updateCRState ta- | isBarrier $ simplifyAction ta = const M.empty- | otherwise = id---- | Update the thread masking state with the action that has just--- happened.-updateMaskState :: ThreadId -> ThreadAction -> Map ThreadId MaskingState -> Map ThreadId MaskingState-updateMaskState tid (Fork tid2) = \masks -> case M.lookup tid masks of- -- A thread inherits the masking state of its parent.- Just ms -> M.insert tid2 ms masks- Nothing -> masks-updateMaskState tid (SetMasking _ ms) = M.insert tid ms-updateMaskState tid (ResetMasking _ ms) = M.insert tid ms-updateMaskState _ _ = id---- | Check if a 'CRef' has a buffered write pending.-isBuffered :: DepState -> CRefId -> Bool-isBuffered depstate r = M.findWithDefault False r (depCRState depstate)---- | Check if an exception can interrupt a thread (action).-canInterrupt :: DepState -> ThreadId -> ThreadAction -> Bool-canInterrupt depstate tid act- -- If masked interruptible, blocked actions can be interrupted.- | isMaskedInterruptible depstate tid = case act of- BlockedPutMVar _ -> True- BlockedReadMVar _ -> True- BlockedTakeMVar _ -> True- BlockedSTM _ -> True- BlockedThrowTo _ -> True- _ -> False- -- If masked uninterruptible, nothing can be.- | isMaskedUninterruptible depstate tid = False- -- If no mask, anything can be.- | otherwise = True---- | Check if an exception can interrupt a thread (lookahead).-canInterruptL :: DepState -> ThreadId -> Lookahead -> Bool-canInterruptL depstate tid lh- -- If masked interruptible, actions which can block may be- -- interrupted.- | isMaskedInterruptible depstate tid = case lh of- WillPutMVar _ -> True- WillReadMVar _ -> True- WillTakeMVar _ -> True- WillSTM -> True- WillThrowTo _ -> True- _ -> False- -- If masked uninterruptible, nothing can be.- | isMaskedUninterruptible depstate tid = False- -- If no mask, anything can be.- | otherwise = True---- | Check if a thread is masked interruptible.-isMaskedInterruptible :: DepState -> ThreadId -> Bool-isMaskedInterruptible depstate tid =- M.lookup tid (depMaskState depstate) == Just MaskedInterruptible---- | Check if a thread is masked uninterruptible.-isMaskedUninterruptible :: DepState -> ThreadId -> Bool-isMaskedUninterruptible depstate tid =- M.lookup tid (depMaskState depstate) == Just MaskedUninterruptible------------------------------------------------------------------------------------ * Utilities---- The initial thread of a DPOR tree.-initialDPORThread :: DPOR -> ThreadId-initialDPORThread = S.elemAt 0 . dporRunnable---- | Check if a thread yielded.-didYield :: ThreadAction -> Bool-didYield Yield = True-didYield (ThreadDelay _) = True-didYield _ = False---- | Check if a thread will yield.-willYield :: Lookahead -> Bool-willYield WillYield = True-willYield (WillThreadDelay _) = True-willYield _ = False---- | Check if an action will kill daemon threads.-killsDaemons :: ThreadId -> Lookahead -> Bool-killsDaemons t WillStop = t == initialThread-killsDaemons _ _ = False
+ Test/DejaFu/SCT/Internal/DPOR.hs view
@@ -0,0 +1,793 @@+-- |+-- Module : Test.DejaFu.SCT.Internal.DPOR+-- Copyright : (c) 2015--2017 Michael Walker+-- License : MIT+-- Maintainer : Michael Walker <mike@barrucadu.co.uk>+-- Stability : experimental+-- Portability : portable+--+-- Internal types and functions for SCT via dynamic partial-order+-- reduction. This module is NOT considered to form part of the+-- public interface of this library.+module Test.DejaFu.SCT.Internal.DPOR where++import Control.Applicative ((<|>))+import Control.DeepSeq (NFData(..))+import Control.Exception (MaskingState(..))+import qualified Data.Foldable as F+import Data.Function (on)+import Data.List (nubBy, partition, sortOn)+import Data.List.NonEmpty (toList)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as M+import Data.Maybe (isJust, isNothing, listToMaybe)+import Data.Sequence (Seq, (|>))+import qualified Data.Sequence as Sq+import Data.Set (Set)+import qualified Data.Set as S++import Test.DejaFu.Internal+import Test.DejaFu.Schedule (Scheduler(..))+import Test.DejaFu.Types+import Test.DejaFu.Utils (decisionOf, tidOf)++-------------------------------------------------------------------------------+-- * Dynamic partial-order reduction++-- | DPOR execution is represented as a tree of states, characterised+-- by the decisions that lead to that state.+data DPOR = DPOR+ { dporRunnable :: Set ThreadId+ -- ^ What threads are runnable at this step.+ , dporTodo :: Map ThreadId Bool+ -- ^ Follow-on decisions still to make, and whether that decision+ -- was added conservatively due to the bound.+ , dporNext :: Maybe (ThreadId, DPOR)+ -- ^ The next decision made. Executions are explored in a+ -- depth-first fashion, so this changes as old subtrees are+ -- exhausted and new ones explored.+ , dporDone :: Set ThreadId+ -- ^ All transitions which have been taken from this point,+ -- including conservatively-added ones.+ , dporSleep :: Map ThreadId ThreadAction+ -- ^ Transitions to ignore (in this node and children) until a+ -- dependent transition happens.+ , dporTaken :: Map ThreadId ThreadAction+ -- ^ Transitions which have been taken, excluding+ -- conservatively-added ones. This is used in implementing sleep+ -- sets.+ } deriving (Eq, Show)++instance NFData DPOR where+ rnf dpor = rnf ( dporRunnable dpor+ , dporTodo dpor+ , dporNext dpor+ , dporDone dpor+ , dporSleep dpor+ , dporTaken dpor+ )++-- | One step of the execution, including information for backtracking+-- purposes. This backtracking information is used to generate new+-- schedules.+data BacktrackStep = BacktrackStep+ { bcktThreadid :: ThreadId+ -- ^ The thread running at this step+ , bcktDecision :: Decision+ -- ^ What was decided at this step.+ , bcktAction :: ThreadAction+ -- ^ What happened at this step.+ , bcktRunnable :: Map ThreadId Lookahead+ -- ^ The threads runnable at this step+ , bcktBacktracks :: Map ThreadId Bool+ -- ^ The list of alternative threads to run, and whether those+ -- alternatives were added conservatively due to the bound.+ , bcktState :: DepState+ -- ^ Some domain-specific state at this point.+ } deriving (Eq, Show)++instance NFData BacktrackStep where+ rnf bs = rnf ( bcktThreadid bs+ , bcktDecision bs+ , bcktAction bs+ , bcktRunnable bs+ , bcktBacktracks bs+ , bcktState bs+ )++-- | Initial DPOR state, given an initial thread ID. This initial+-- thread should exist and be runnable at the start of execution.+initialState :: DPOR+initialState = DPOR+ { dporRunnable = S.singleton initialThread+ , dporTodo = M.singleton initialThread False+ , dporNext = Nothing+ , dporDone = S.empty+ , dporSleep = M.empty+ , dporTaken = M.empty+ }++-- | Produce a new schedule prefix from a @DPOR@ tree. If there are no new+-- prefixes remaining, return 'Nothing'. Also returns whether the+-- decision was added conservatively, and the sleep set at the point+-- where divergence happens.+--+-- A schedule prefix is a possibly empty sequence of decisions that+-- have already been made, terminated by a single decision from the+-- to-do set. The intent is to put the system into a new state when+-- executed with this initial sequence of scheduling decisions.+findSchedulePrefix+ :: DPOR+ -> Maybe ([ThreadId], Bool, Map ThreadId ThreadAction)+findSchedulePrefix dpor = case dporNext dpor of+ Just (tid, child) -> go tid child <|> here+ Nothing -> here+ where+ go tid child = (\(ts,c,slp) -> (tid:ts,c,slp)) <$> findSchedulePrefix child++ -- Prefix traces terminating with a to-do decision at this point.+ here =+ let todos = [([t], c, sleeps) | (t, c) <- M.toList $ dporTodo dpor]+ (best, worst) = partition (\([t],_,_) -> t >= initialThread) todos+ in listToMaybe best <|> listToMaybe worst++ -- The new sleep set is the union of the sleep set of the node+ -- we're branching from, plus all the decisions we've already+ -- explored.+ sleeps = dporSleep dpor `M.union` dporTaken dpor++-- | Add a new trace to the stack. This won't work if to-dos aren't explored depth-first.+incorporateTrace+ :: Bool+ -- ^ Whether the \"to-do\" point which was used to create this new+ -- execution was conservative or not.+ -> Trace+ -- ^ The execution trace: the decision made, the runnable threads,+ -- and the action performed.+ -> DPOR+ -> DPOR+incorporateTrace conservative trace dpor0 = grow initialDepState (initialDPORThread dpor0) trace dpor0 where+ grow state tid trc@((d, _, a):rest) dpor =+ let tid' = tidOf tid d+ state' = updateDepState state tid' a+ in case dporNext dpor of+ Just (t, child)+ | t == tid' -> dpor { dporNext = Just (tid', grow state' tid' rest child) }+ | hasTodos child -> fatal "incorporateTrace" "replacing child with todos!"+ _ ->+ let taken = M.insert tid' a (dporTaken dpor)+ sleep = dporSleep dpor `M.union` dporTaken dpor+ in dpor { dporTaken = if conservative then dporTaken dpor else taken+ , dporTodo = M.delete tid' (dporTodo dpor)+ , dporNext = Just (tid', subtree state' tid' sleep trc)+ , dporDone = S.insert tid' (dporDone dpor)+ }+ grow _ _ [] _ = fatal "incorporateTrace" "trace exhausted without reading a to-do point!"++ -- check if there are to-do points in a tree+ hasTodos dpor = not (M.null (dporTodo dpor)) || (case dporNext dpor of Just (_, dpor') -> hasTodos dpor'; _ -> False)++ -- Construct a new subtree corresponding to a trace suffix.+ subtree state tid sleep ((_, _, a):rest) =+ let state' = updateDepState state tid a+ sleep' = M.filterWithKey (\t a' -> not $ dependent state' tid a t a') sleep+ in DPOR+ { dporRunnable = S.fromList $ case rest of+ ((_, runnable, _):_) -> map fst runnable+ [] -> []+ , dporTodo = M.empty+ , dporNext = case rest of+ ((d', _, _):_) ->+ let tid' = tidOf tid d'+ in Just (tid', subtree state' tid' sleep' rest)+ [] -> Nothing+ , dporDone = case rest of+ ((d', _, _):_) -> S.singleton (tidOf tid d')+ [] -> S.empty+ , dporSleep = sleep'+ , dporTaken = case rest of+ ((d', _, a'):_) -> M.singleton (tidOf tid d') a'+ [] -> M.empty+ }+ subtree _ _ _ [] = fatal "incorporateTrace" "subtree suffix empty!"++-- | Produce a list of new backtracking points from an execution+-- trace. These are then used to inform new \"to-do\" points in the+-- @DPOR@ tree.+--+-- Two traces are passed in to this function: the first is generated+-- from the special DPOR scheduler, the other from the execution of+-- the concurrent program.+--+-- If the trace ends with any threads other than the initial one still+-- runnable, a dependency is imposed between this final action and+-- everything else.+findBacktrackSteps+ :: BacktrackFunc+ -- ^ Backtracking function. Given a list of backtracking points, and+ -- a thread to backtrack to at a specific point in that list, add+ -- the new backtracking points. There will be at least one: this+ -- chosen one, but the function may add others.+ -> Bool+ -- ^ Whether the computation was aborted due to no decisions being+ -- in-bounds.+ -> Seq ([(ThreadId, Lookahead)], [ThreadId])+ -- ^ A sequence of threads at each step: the list of runnable+ -- in-bound threads (with lookahead values), and the list of threads+ -- still to try. The reason for the two separate lists is because+ -- the threads chosen to try will be dependent on the specific+ -- domain.+ -> Trace+ -- ^ The execution trace.+ -> [BacktrackStep]+findBacktrackSteps backtrack boundKill = go initialDepState S.empty initialThread [] . F.toList where+ -- Walk through the traces one step at a time, building up a list of+ -- new backtracking points.+ go state allThreads tid bs ((e,i):is) ((d,_,a):ts) =+ let tid' = tidOf tid d+ state' = updateDepState state tid' a+ this = BacktrackStep+ { bcktThreadid = tid'+ , bcktDecision = d+ , bcktAction = a+ , bcktRunnable = M.fromList e+ , bcktBacktracks = M.fromList $ map (\i' -> (i', False)) i+ , bcktState = state+ }+ bs' = doBacktrack killsEarly allThreads' e (bs++[this])+ runnable = S.fromList (M.keys $ bcktRunnable this)+ allThreads' = allThreads `S.union` runnable+ killsEarly = null ts && boundKill+ in go state' allThreads' tid' bs' is ts+ go _ _ _ bs _ _ = bs++ -- Find the prior actions dependent with this one and add+ -- backtracking points.+ doBacktrack killsEarly allThreads enabledThreads bs =+ let tagged = reverse $ zip [0..] bs+ idxs = [ (ehead "doBacktrack.idxs" is, False, u)+ | (u, n) <- enabledThreads+ , v <- S.toList allThreads+ , u /= v+ , let is = idxs' u n v tagged+ , not $ null is]++ idxs' u n v = go' True where+ {-# INLINE go' #-}+ go' final ((i,b):rest)+ -- Don't cross subconcurrency boundaries+ | isSubC final b = []+ -- If this is the final action in the trace and the+ -- execution was killed due to nothing being within bounds+ -- (@killsEarly == True@) assume worst-case dependency.+ | bcktThreadid b == v && (killsEarly || isDependent b) = i : go' False rest+ | otherwise = go' False rest+ go' _ [] = []++ {-# INLINE isSubC #-}+ isSubC final b = case bcktAction b of+ Stop -> not final && bcktThreadid b == initialThread+ Subconcurrency -> bcktThreadid b == initialThread+ _ -> False++ {-# INLINE isDependent #-}+ isDependent b+ -- Don't impose a dependency if the other thread will+ -- immediately block already. This is safe because a+ -- context switch will occur anyway so there's no point+ -- pre-empting the action UNLESS the pre-emption would+ -- possibly allow for a different relaxed memory stage.+ | isBlock (bcktAction b) && isBarrier (simplifyLookahead n) = False+ | otherwise = dependent' (bcktState b) (bcktThreadid b) (bcktAction b) u n+ in backtrack bs idxs++-- | Add new backtracking points, if they have not already been+-- visited and aren't in the sleep set.+incorporateBacktrackSteps :: [BacktrackStep] -> DPOR -> DPOR+incorporateBacktrackSteps (b:bs) dpor = dpor' where+ tid = bcktThreadid b++ dpor' = dpor+ { dporTodo = dporTodo dpor `M.union` M.fromList todo+ , dporNext = Just (tid, child)+ }++ todo =+ [ x+ | x@(t,c) <- M.toList $ bcktBacktracks b+ , Just t /= (fst <$> dporNext dpor)+ , S.notMember t (dporDone dpor)+ , c || M.notMember t (dporSleep dpor)+ ]++ child = case dporNext dpor of+ Just (t, d)+ | t /= tid -> fatal "incorporateBacktrackSteps" "incorporating wrong trace!"+ | otherwise -> incorporateBacktrackSteps bs d+ Nothing -> fatal "incorporateBacktrackSteps" "child is missing!"+incorporateBacktrackSteps [] dpor = dpor++-------------------------------------------------------------------------------+-- * DPOR scheduler++-- | The scheduler state+data DPORSchedState k = DPORSchedState+ { schedSleep :: Map ThreadId ThreadAction+ -- ^ The sleep set: decisions not to make until something dependent+ -- with them happens.+ , schedPrefix :: [ThreadId]+ -- ^ Decisions still to make+ , schedBPoints :: Seq ([(ThreadId, Lookahead)], [ThreadId])+ -- ^ Which threads are runnable and in-bound at each step, and the+ -- alternative decisions still to make.+ , schedIgnore :: Bool+ -- ^ Whether to ignore this execution or not: @True@ if the+ -- execution is aborted due to all possible decisions being in the+ -- sleep set, as then everything in this execution is covered by+ -- another.+ , schedBoundKill :: Bool+ -- ^ Whether the execution was terminated due to all decisions being+ -- out of bounds.+ , schedDepState :: DepState+ -- ^ State used by the dependency function to determine when to+ -- remove decisions from the sleep set.+ , schedBState :: Maybe k+ -- ^ State used by the incremental bounding function.+ } deriving (Eq, Show)++instance NFData k => NFData (DPORSchedState k) where+ rnf s = rnf ( schedSleep s+ , schedPrefix s+ , schedBPoints s+ , schedIgnore s+ , schedBoundKill s+ , schedDepState s+ , schedBState s+ )++-- | Initial DPOR scheduler state for a given prefix+initialDPORSchedState :: Map ThreadId ThreadAction+ -- ^ The initial sleep set.+ -> [ThreadId]+ -- ^ The schedule prefix.+ -> DPORSchedState k+initialDPORSchedState sleep prefix = DPORSchedState+ { schedSleep = sleep+ , schedPrefix = prefix+ , schedBPoints = Sq.empty+ , schedIgnore = False+ , schedBoundKill = False+ , schedDepState = initialDepState+ , schedBState = Nothing+ }++-- | An incremental bounding function is a stateful function that+-- takes the last and next decisions, and returns a new state only if+-- the next decision is within the bound.+type IncrementalBoundFunc k+ = Maybe k -> Maybe (ThreadId, ThreadAction) -> (Decision, Lookahead) -> Maybe k++-- | A backtracking step is a point in the execution where another+-- decision needs to be made, in order to explore interesting new+-- schedules. A backtracking /function/ takes the steps identified so+-- far and a list of points and thread at that point to backtrack+-- to. More points be added to compensate for the effects of the+-- bounding function. For example, under pre-emption bounding a+-- conservative backtracking point is added at the prior context+-- switch. The bool is whether the point is conservative. Conservative+-- points are always explored, whereas non-conservative ones might be+-- skipped based on future information.+--+-- In general, a backtracking function should identify one or more+-- backtracking points, and then use @backtrackAt@ to do the actual+-- work.+type BacktrackFunc+ = [BacktrackStep] -> [(Int, Bool, ThreadId)] -> [BacktrackStep]++-- | Add a backtracking point. If the thread isn't runnable, add all+-- runnable threads. If the backtracking point is already present,+-- don't re-add it UNLESS this would make it conservative.+backtrackAt+ :: (ThreadId -> BacktrackStep -> Bool)+ -- ^ If this returns @True@, backtrack to all runnable threads,+ -- rather than just the given thread.+ -> BacktrackFunc+backtrackAt toAll bs0 = backtrackAt' . nubBy ((==) `on` fst') . sortOn fst' where+ fst' (x,_,_) = x++ backtrackAt' ((i,c,t):is) = go i bs0 i c t is+ backtrackAt' [] = bs0++ go i0 (b:bs) 0 c tid is+ -- If the backtracking point is already present, don't re-add it,+ -- UNLESS this would force it to backtrack (it's conservative)+ -- where before it might not.+ | not (toAll tid b) && tid `M.member` bcktRunnable b =+ let val = M.lookup tid $ bcktBacktracks b+ b' = if isNothing val || (val == Just False && c)+ then b { bcktBacktracks = backtrackTo tid c b }+ else b+ in b' : case is of+ ((i',c',t'):is') -> go i' bs (i'-i0-1) c' t' is'+ [] -> bs+ -- Otherwise just backtrack to everything runnable.+ | otherwise =+ let b' = b { bcktBacktracks = backtrackAll c b }+ in b' : case is of+ ((i',c',t'):is') -> go i' bs (i'-i0-1) c' t' is'+ [] -> bs+ go i0 (b:bs) i c tid is = b : go i0 bs (i-1) c tid is+ go _ [] _ _ _ _ = fatal "backtrackAt" "ran out of schedule whilst backtracking!"++ -- Backtrack to a single thread+ backtrackTo tid c = M.insert tid c . bcktBacktracks++ -- Backtrack to all runnable threads+ backtrackAll c = M.map (const c) . bcktRunnable++-- | DPOR scheduler: takes a list of decisions, and maintains a trace+-- including the runnable threads, and the alternative choices allowed+-- by the bound-specific initialise function.+--+-- After the initial decisions are exhausted, this prefers choosing+-- the prior thread if it's (1) still runnable and (2) hasn't just+-- yielded. Furthermore, threads which /will/ yield are ignored in+-- preference of those which will not.+dporSched+ :: IncrementalBoundFunc k+ -- ^ Bound function: returns true if that schedule prefix terminated+ -- with the lookahead decision fits within the bound.+ -> Scheduler (DPORSchedState k)+dporSched boundf = Scheduler $ \prior threads s ->+ let+ -- The next scheduler state+ nextState rest = s+ { schedBPoints = schedBPoints s |> (restrictToBound fst threads', rest)+ , schedDepState = nextDepState+ }+ nextDepState = let ds = schedDepState s in maybe ds (uncurry $ updateDepState ds) prior++ -- Pick a new thread to run, not considering bounds. Choose the+ -- current thread if available and it hasn't just yielded,+ -- otherwise add all runnable threads.+ initialise = tryDaemons . yieldsToEnd $ case prior of+ Just (tid, act)+ | not (didYield act) && tid `elem` tids && isInBound tid -> [tid]+ _ -> tids++ -- If one of the chosen actions will kill the computation, and+ -- there are daemon threads, try them instead.+ --+ -- This is necessary if the killing action is NOT dependent with+ -- every other action, according to the dependency function. This+ -- is, strictly speaking, wrong; an action that kills another+ -- thread is definitely dependent with everything in that+ -- thread. HOWEVER, implementing it that way leads to an explosion+ -- of schedules tried. Really, all that needs to happen is for the+ -- thread-that-would-be-killed to be executed fully ONCE, and then+ -- the normal dependency mechanism will identify any other+ -- backtracking points that should be tried. This is achieved by+ -- adding every thread that would be killed to the to-do list.+ -- Furthermore, these threads MUST be ahead of the killing thread,+ -- or the killing thread will end up in the sleep set and so the+ -- killing action not performed. This is, again, because of the+ -- lack of the dependency messing things up in the name of+ -- performance.+ --+ -- See commits a056f54 and 8554ce9, and my 4th June comment in+ -- issue #52.+ tryDaemons ts+ | any doesKill ts = case partition doesKill tids of+ (kills, nokills) -> nokills ++ kills+ | otherwise = ts+ doesKill t = killsDaemons t (action t)++ -- Restrict the possible decisions to those in the bound.+ restrictToBound f = filter (isInBound . f)+ isInBound t = isJust $ boundf (schedBState s) prior (decision t, action t)++ -- Move the threads which will immediately yield to the end of the list+ yieldsToEnd ts = case partition (willYield . action) ts of+ (yields, noyields) -> noyields ++ yields++ -- Get the decision that will lead to a thread being scheduled.+ decision = decisionOf (fst <$> prior) (S.fromList tids)++ -- Get the action of a thread+ action t = efromJust "dporSched.action" (lookup t threads')++ -- The runnable thread IDs+ tids = map fst threads'++ -- The runnable threads as a normal list.+ threads' = toList threads+ in case schedPrefix s of+ -- If there is a decision available, make it+ (t:ts) ->+ let bstate' = boundf (schedBState s) prior (decision t, action t)+ in (Just t, (nextState []) { schedPrefix = ts, schedBState = bstate' })++ -- Otherwise query the initialise function for a list of possible+ -- choices, filter out anything in the sleep set, and make one of+ -- them arbitrarily (recording the others).+ [] ->+ let choices = restrictToBound id initialise+ checkDep t a = case prior of+ Just (tid, act) -> dependent (schedDepState s) tid act t a+ Nothing -> False+ ssleep' = M.filterWithKey (\t a -> not $ checkDep t a) $ schedSleep s+ choices' = filter (`notElem` M.keys ssleep') choices+ signore' = not (null choices) && all (`elem` M.keys ssleep') choices+ sbkill' = not (null initialise) && null choices+ in case choices' of+ (nextTid:rest) ->+ let bstate' = boundf (schedBState s) prior (decision nextTid, action nextTid)+ in (Just nextTid, (nextState rest) { schedSleep = ssleep', schedBState = bstate' })+ [] ->+ (Nothing, (nextState []) { schedIgnore = signore', schedBoundKill = sbkill', schedBState = Nothing })++-------------------------------------------------------------------------------+-- * Dependency function++-- | Check if an action is dependent on another.+--+-- This is basically the same as 'dependent'', but can make use of the+-- additional information in a 'ThreadAction' to make better decisions+-- in a few cases.+dependent :: DepState -> ThreadId -> ThreadAction -> ThreadId -> ThreadAction -> Bool+dependent ds t1 a1 t2 a2 = case (a1, a2) of+ -- @SetNumCapabilities@ and @GetNumCapabilities@ are NOT dependent+ -- IF the value read is the same as the value written. 'dependent''+ -- can not see the value read (as it hasn't happened yet!), and so+ -- is more pessimistic here.+ (SetNumCapabilities a, GetNumCapabilities b) | a == b -> False+ (GetNumCapabilities a, SetNumCapabilities b) | a == b -> False++ -- When masked interruptible, a thread can only be interrupted when+ -- actually blocked. 'dependent'' has to assume that all+ -- potentially-blocking operations can block, and so is more+ -- pessimistic in this case.+ (ThrowTo t, _) | t == t2 -> canInterrupt ds t2 a2 && a2 /= Stop+ (_, ThrowTo t) | t == t1 -> canInterrupt ds t1 a1 && a1 /= Stop++ -- Dependency of STM transactions can be /greatly/ improved here, as+ -- the 'Lookahead' does not know which @TVar@s will be touched, and+ -- so has to assume all transactions are dependent.+ (STM _ _, STM _ _) -> checkSTM+ (STM _ _, BlockedSTM _) -> checkSTM+ (BlockedSTM _, STM _ _) -> checkSTM+ (BlockedSTM _, BlockedSTM _) -> checkSTM++ _ -> case (,) <$> rewind a1 <*> rewind a2 of+ Just (l1, l2) -> dependent' ds t1 a1 t2 l2 && dependent' ds t2 a2 t1 l1+ _ -> dependentActions ds (simplifyAction a1) (simplifyAction a2)++ where+ -- STM actions A and B are dependent if A wrote to anything B+ -- touched, or vice versa.+ checkSTM = checkSTM' a1 a2 || checkSTM' a2 a1+ checkSTM' a b = not . S.null $ tvarsWritten a `S.intersection` tvarsOf b++-- | Variant of 'dependent' to handle 'Lookahead'.+--+-- Termination of the initial thread is handled specially in the DPOR+-- implementation.+dependent' :: DepState -> ThreadId -> ThreadAction -> ThreadId -> Lookahead -> Bool+dependent' ds t1 a1 t2 l2 = case (a1, l2) of+ -- Worst-case assumption: all IO is dependent.+ (LiftIO, WillLiftIO) -> True++ -- Throwing an exception is only dependent with actions in that+ -- thread and if the actions can be interrupted. We can also+ -- slightly improve on that by not considering interrupting the+ -- normal termination of a thread: it doesn't make a difference.+ (ThrowTo t, WillStop) | t == t2 -> False+ (Stop, WillThrowTo t) | t == t1 -> False+ (ThrowTo t, _) | t == t2 -> canInterruptL ds t2 l2+ (_, WillThrowTo t) | t == t1 -> canInterrupt ds t1 a1++ -- Another worst-case: assume all STM is dependent.+ (STM _ _, WillSTM) -> True++ -- This is a bit pessimistic: Set/Get are only dependent if the+ -- value set is not the same as the value that will be got, but we+ -- can't know that here. 'dependent' optimises this case.+ (GetNumCapabilities a, WillSetNumCapabilities b) -> a /= b+ (SetNumCapabilities _, WillGetNumCapabilities) -> True+ (SetNumCapabilities a, WillSetNumCapabilities b) -> a /= b++ _ -> dependentActions ds (simplifyAction a1) (simplifyLookahead l2)++-- | Check if two 'ActionType's are dependent. Note that this is not+-- sufficient to know if two 'ThreadAction's are dependent, without+-- being so great an over-approximation as to be useless!+dependentActions :: DepState -> ActionType -> ActionType -> Bool+dependentActions ds a1 a2 = case (a1, a2) of+ -- Unsynchronised reads and writes are always dependent, even under+ -- a relaxed memory model, as an unsynchronised write gives rise to+ -- a commit, which synchronises.+ (UnsynchronisedRead r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> a2 /= UnsynchronisedRead r1+ (UnsynchronisedWrite r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True+ (PartiallySynchronisedWrite r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True+ (PartiallySynchronisedModify r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True+ (SynchronisedModify r1, _) | same crefOf && a2 /= PartiallySynchronisedCommit r1 -> True++ -- Unsynchronised writes and synchronisation where the buffer is not+ -- empty.+ --+ -- See [RMMVerification], lemma 5.25.+ (UnsynchronisedWrite r1, PartiallySynchronisedCommit _) | same crefOf && isBuffered ds r1 -> False+ (PartiallySynchronisedCommit _, UnsynchronisedWrite r2) | same crefOf && isBuffered ds r2 -> False++ -- Unsynchronised reads where a memory barrier would flush a+ -- buffered write+ (UnsynchronisedRead r1, _) | isBarrier a2 -> isBuffered ds r1+ (_, UnsynchronisedRead r2) | isBarrier a1 -> isBuffered ds r2++ -- Commits and memory barriers must be dependent, as memory barriers+ -- (currently) flush in a consistent order. Alternative orders need+ -- to be explored as well. Perhaps a better implementation of+ -- memory barriers would just block every non-commit thread while+ -- any buffer is nonempty.+ (PartiallySynchronisedCommit _, _) | isBarrier a2 -> True+ (_, PartiallySynchronisedCommit _) | isBarrier a1 -> True++ -- Two @MVar@ puts are dependent if they're to the same empty+ -- @MVar@, and two takes are dependent if they're to the same full+ -- @MVar@.+ (SynchronisedWrite v1, SynchronisedWrite v2) -> v1 == v2 && not (isFull ds v1)+ (SynchronisedRead v1, SynchronisedRead v2) -> v1 == v2 && isFull ds v1++ (_, _) -> case getSame crefOf of+ -- Two actions on the same CRef where at least one is synchronised+ Just r -> synchronises a1 r || synchronises a2 r+ -- Two actions on the same MVar+ _ -> same mvarOf++ where+ same :: Eq a => (ActionType -> Maybe a) -> Bool+ same = isJust . getSame++ getSame :: Eq a => (ActionType -> Maybe a) -> Maybe a+ getSame f =+ let f1 = f a1+ f2 = f a2+ in if f1 == f2 then f1 else Nothing++-------------------------------------------------------------------------------+-- ** Dependency function state++data DepState = DepState+ { depCRState :: Map CRefId Bool+ -- ^ Keep track of which @CRef@s have buffered writes.+ , depMVState :: Set MVarId+ -- ^ Keep track of which @MVar@s are full.+ , depMaskState :: Map ThreadId MaskingState+ -- ^ Keep track of thread masking states. If a thread isn't present,+ -- the masking state is assumed to be @Unmasked@. This nicely+ -- provides compatibility with dpor-0.1, where the thread IDs are+ -- not available.+ } deriving (Eq, Show)++instance NFData DepState where+ rnf depstate = rnf ( depCRState depstate+ , depMVState depstate+ , [(t, m `seq` ()) | (t, m) <- M.toList (depMaskState depstate)]+ )++-- | Initial dependency state.+initialDepState :: DepState+initialDepState = DepState M.empty S.empty M.empty++-- | Update the dependency state with the action that has just+-- happened.+updateDepState :: DepState -> ThreadId -> ThreadAction -> DepState+updateDepState depstate tid act = DepState+ { depCRState = updateCRState act $ depCRState depstate+ , depMVState = updateMVState act $ depMVState depstate+ , depMaskState = updateMaskState tid act $ depMaskState depstate+ }++-- | Update the @CRef@ buffer state with the action that has just+-- happened.+updateCRState :: ThreadAction -> Map CRefId Bool -> Map CRefId Bool+updateCRState (CommitCRef _ r) = M.delete r+updateCRState (WriteCRef r) = M.insert r True+updateCRState ta+ | isBarrier $ simplifyAction ta = const M.empty+ | otherwise = id++-- | Update the @MVar@ full/empty state with the action that has just+-- happened.+updateMVState :: ThreadAction -> Set MVarId -> Set MVarId+updateMVState (PutMVar mvid _) = S.insert mvid+updateMVState (TryPutMVar mvid True _) = S.insert mvid+updateMVState (TakeMVar mvid _) = S.delete mvid+updateMVState (TryTakeMVar mvid True _) = S.delete mvid+updateMVState _ = id++-- | Update the thread masking state with the action that has just+-- happened.+updateMaskState :: ThreadId -> ThreadAction -> Map ThreadId MaskingState -> Map ThreadId MaskingState+updateMaskState tid (Fork tid2) = \masks -> case M.lookup tid masks of+ -- A thread inherits the masking state of its parent.+ Just ms -> M.insert tid2 ms masks+ Nothing -> masks+updateMaskState tid (SetMasking _ ms) = M.insert tid ms+updateMaskState tid (ResetMasking _ ms) = M.insert tid ms+updateMaskState _ _ = id++-- | Check if a @CRef@ has a buffered write pending.+isBuffered :: DepState -> CRefId -> Bool+isBuffered depstate r = M.findWithDefault False r (depCRState depstate)++-- | Check if an @MVar@ is full.+isFull :: DepState -> MVarId -> Bool+isFull depstate v = S.member v (depMVState depstate)++-- | Check if an exception can interrupt a thread (action).+canInterrupt :: DepState -> ThreadId -> ThreadAction -> Bool+canInterrupt depstate tid act+ -- If masked interruptible, blocked actions can be interrupted.+ | isMaskedInterruptible depstate tid = case act of+ BlockedPutMVar _ -> True+ BlockedReadMVar _ -> True+ BlockedTakeMVar _ -> True+ BlockedSTM _ -> True+ BlockedThrowTo _ -> True+ _ -> False+ -- If masked uninterruptible, nothing can be.+ | isMaskedUninterruptible depstate tid = False+ -- If no mask, anything can be.+ | otherwise = True++-- | Check if an exception can interrupt a thread (lookahead).+canInterruptL :: DepState -> ThreadId -> Lookahead -> Bool+canInterruptL depstate tid lh+ -- If masked interruptible, actions which can block may be+ -- interrupted.+ | isMaskedInterruptible depstate tid = case lh of+ WillPutMVar _ -> True+ WillReadMVar _ -> True+ WillTakeMVar _ -> True+ WillSTM -> True+ WillThrowTo _ -> True+ _ -> False+ -- If masked uninterruptible, nothing can be.+ | isMaskedUninterruptible depstate tid = False+ -- If no mask, anything can be.+ | otherwise = True++-- | Check if a thread is masked interruptible.+isMaskedInterruptible :: DepState -> ThreadId -> Bool+isMaskedInterruptible depstate tid =+ M.lookup tid (depMaskState depstate) == Just MaskedInterruptible++-- | Check if a thread is masked uninterruptible.+isMaskedUninterruptible :: DepState -> ThreadId -> Bool+isMaskedUninterruptible depstate tid =+ M.lookup tid (depMaskState depstate) == Just MaskedUninterruptible++-------------------------------------------------------------------------------+-- * Utilities++-- The initial thread of a DPOR tree.+initialDPORThread :: DPOR -> ThreadId+initialDPORThread = S.elemAt 0 . dporRunnable++-- | Check if a thread yielded.+didYield :: ThreadAction -> Bool+didYield Yield = True+didYield (ThreadDelay _) = True+didYield _ = False++-- | Check if a thread will yield.+willYield :: Lookahead -> Bool+willYield WillYield = True+willYield (WillThreadDelay _) = True+willYield _ = False++-- | Check if an action will kill daemon threads.+killsDaemons :: ThreadId -> Lookahead -> Bool+killsDaemons t WillStop = t == initialThread+killsDaemons _ _ = False
+ Test/DejaFu/SCT/Internal/Weighted.hs view
@@ -0,0 +1,66 @@+-- |+-- Module : Test.DejaFu.SCT.Internal.Weighted+-- Copyright : (c) 2015--2017 Michael Walker+-- License : MIT+-- Maintainer : Michael Walker <mike@barrucadu.co.uk>+-- Stability : experimental+-- Portability : portable+--+-- Internal types and functions for SCT via weighted random+-- scheduling. This module is NOT considered to form part of the+-- public interface of this library.+module Test.DejaFu.SCT.Internal.Weighted where++import Control.DeepSeq (NFData(..))+import Data.List.NonEmpty (toList)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as M+import Data.Maybe (fromMaybe)+import System.Random (RandomGen, randomR)++import Test.DejaFu.Schedule (Scheduler(..))+import Test.DejaFu.Types++-------------------------------------------------------------------------------+-- * Weighted random scheduler++-- | The scheduler state+data RandSchedState g = RandSchedState+ { schedWeights :: Map ThreadId Int+ -- ^ The thread weights: used in determining which to run.+ , schedGen :: g+ -- ^ The random number generator.+ } deriving (Eq, Show)++instance NFData g => NFData (RandSchedState g) where+ rnf s = rnf ( schedWeights s+ , schedGen s+ )++-- | Initial weighted random scheduler state.+initialRandSchedState :: Maybe (Map ThreadId Int) -> g -> RandSchedState g+initialRandSchedState = RandSchedState . fromMaybe M.empty++-- | Weighted random scheduler: assigns to each new thread a weight,+-- and makes a weighted random choice out of the runnable threads at+-- every step.+randSched :: RandomGen g => (g -> (Int, g)) -> Scheduler (RandSchedState g)+randSched weightf = Scheduler $ \_ threads s ->+ let+ -- Select a thread+ pick idx ((x, f):xs)+ | idx < f = Just x+ | otherwise = pick (idx - f) xs+ pick _ [] = Nothing+ (choice, g'') = randomR (0, sum (map snd enabled) - 1) g'+ enabled = M.toList $ M.filterWithKey (\tid _ -> tid `elem` tids) weights'++ -- The weights, with any new threads added.+ (weights', g') = foldr assignWeight (M.empty, schedGen s) tids+ assignWeight tid ~(ws, g0) =+ let (w, g) = maybe (weightf g0) (\w0 -> (w0, g0)) (M.lookup tid (schedWeights s))+ in (M.insert tid w ws, g)++ -- The runnable threads.+ tids = map fst (toList threads)+ in (pick choice enabled, RandSchedState weights' g'')
− Test/DejaFu/STM.hs
@@ -1,122 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeFamilies #-}---- |--- Module : Test.DejaFu.STM--- Copyright : (c) 2016 Michael Walker--- License : MIT--- Maintainer : Michael Walker <mike@barrucadu.co.uk>--- Stability : experimental--- Portability : CPP, GeneralizedNewtypeDeriving, RankNTypes, TypeFamilies------ A 'MonadSTM' implementation, which can be run on top of 'IO' or--- 'ST'.-module Test.DejaFu.STM- ( -- * The @STMLike@ Monad- STMLike- , STMST- , STMIO-- -- * Executing Transactions- , Result(..)- , TTrace- , TAction(..)- , TVarId- , runTransaction- ) where--import Control.Applicative (Alternative(..))-import Control.Monad (MonadPlus(..), unless)-import Control.Monad.Catch (MonadCatch(..), MonadThrow(..))-import Control.Monad.Ref (MonadRef)-import Control.Monad.ST (ST)-import Data.IORef (IORef)-import Data.STRef (STRef)--import qualified Control.Monad.STM.Class as C-import Test.DejaFu.Common-import Test.DejaFu.STM.Internal--#if MIN_VERSION_base(4,9,0)-import qualified Control.Monad.Fail as Fail-#endif---- | @since 0.3.0.0-newtype STMLike n r a = S { runSTM :: M n r a } deriving (Functor, Applicative, Monad)--#if MIN_VERSION_base(4,9,0)--- | @since 0.9.1.0-instance Fail.MonadFail (STMLike r n) where- fail = S . fail-#endif---- | Create a new STM continuation.-toSTM :: ((a -> STMAction n r) -> STMAction n r) -> STMLike n r a-toSTM = S . cont---- | A 'MonadSTM' implementation using @ST@, it encapsulates a single--- atomic transaction. The environment, that is, the collection of--- defined 'TVar's is implicit, there is no list of them, they exist--- purely as references. This makes the types simpler, but means you--- can't really get an aggregate of them (if you ever wanted to for--- some reason).------ @since 0.3.0.0-type STMST t = STMLike (ST t) (STRef t)---- | A 'MonadSTM' implementation using @ST@, it encapsulates a single--- atomic transaction. The environment, that is, the collection of--- defined 'TVar's is implicit, there is no list of them, they exist--- purely as references. This makes the types simpler, but means you--- can't really get an aggregate of them (if you ever wanted to for--- some reason).------ @since 0.3.0.0-type STMIO = STMLike IO IORef--instance MonadThrow (STMLike n r) where- throwM = toSTM . const . SThrow--instance MonadCatch (STMLike n r) where- catch (S stm) handler = toSTM (SCatch (runSTM . handler) stm)---- | @since 0.7.2.0-instance Alternative (STMLike n r) where- S a <|> S b = toSTM (SOrElse a b)- empty = toSTM (const SRetry)---- | @since 0.7.2.0-instance MonadPlus (STMLike n r)--instance C.MonadSTM (STMLike n r) where- type TVar (STMLike n r) = TVar r--#if MIN_VERSION_concurrency(1,2,0)- -- retry and orElse are top-level definitions in- -- Control.Monad.STM.Class in 1.2 and up-#else- retry = empty- orElse = (<|>)-#endif-- newTVarN n = toSTM . SNew n-- readTVar = toSTM . SRead-- writeTVar tvar a = toSTM (\c -> SWrite tvar a (c ()))---- | Run a transaction, returning the result and new initial--- 'TVarId'. If the transaction ended by calling 'retry', any 'TVar'--- modifications are undone.------ @since 0.4.0.0-runTransaction :: MonadRef r n- => STMLike n r a -> IdSource -> n (Result a, IdSource, TTrace)-runTransaction ma tvid = do- (res, undo, tvid', trace) <- doTransaction (runSTM ma) tvid-- unless (isSTMSuccess res) undo-- pure (res, tvid', trace)
− Test/DejaFu/STM/Internal.hs
@@ -1,225 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RankNTypes #-}---- |--- Module : Test.DejaFu.STM.Internal--- Copyright : (c) 2016 Michael Walker--- License : MIT--- Maintainer : Michael Walker <mike@barrucadu.co.uk>--- Stability : experimental--- Portability : CPP, ExistentialQuantification, MultiParamTypeClasses, RankNTypes------ 'MonadSTM' testing implementation, internal types and--- definitions. This module is NOT considered to form part of the--- public interface of this library.-module Test.DejaFu.STM.Internal where--import Control.DeepSeq (NFData(..))-import Control.Exception (Exception, SomeException, fromException,- toException)-import Control.Monad.Ref (MonadRef, newRef, readRef, writeRef)-import Data.List (nub)--import Test.DejaFu.Common--#if MIN_VERSION_base(4,9,0)-import qualified Control.Monad.Fail as Fail-#endif------------------------------------------------------------------------------------- The @STMLike@ monad---- | The underlying monad is based on continuations over primitive--- actions.------ This is not @Cont@ because we want to give it a custom @MonadFail@--- instance.-newtype M n r a = M { runM :: (a -> STMAction n r) -> STMAction n r }--instance Functor (M n r) where- fmap f m = M $ \ c -> runM m (c . f)--instance Applicative (M n r) where- pure x = M $ \c -> c x- f <*> v = M $ \c -> runM f (\g -> runM v (c . g))--instance Monad (M n r) where- return = pure- m >>= k = M $ \c -> runM m (\x -> runM (k x) c)--#if MIN_VERSION_base(4,9,0)- fail = Fail.fail---- | @since 0.7.1.2-instance Fail.MonadFail (M n r) where-#endif- fail e = cont (\_ -> SThrow (MonadFailException e))---- | Construct a continuation-passing operation from a function.-cont :: ((a -> STMAction n r) -> STMAction n r) -> M n r a-cont = M---- | Run a CPS computation with the given final computation.-runCont :: M n r a -> (a -> STMAction n r) -> STMAction n r-runCont = runM------------------------------------------------------------------------------------- * Primitive actions---- | STM transactions are represented as a sequence of primitive--- actions.-data STMAction n r- = forall a e. Exception e => SCatch (e -> M n r a) (M n r a) (a -> STMAction n r)- | forall a. SRead (TVar r a) (a -> STMAction n r)- | forall a. SWrite (TVar r a) a (STMAction n r)- | forall a. SOrElse (M n r a) (M n r a) (a -> STMAction n r)- | forall a. SNew String a (TVar r a -> STMAction n r)- | forall e. Exception e => SThrow e- | SRetry- | SStop (n ())------------------------------------------------------------------------------------- * @TVar@s---- | A 'TVar' is a tuple of a unique ID and the value contained. The--- ID is so that blocked transactions can be re-run when a 'TVar' they--- depend on has changed.-newtype TVar r a = TVar (TVarId, r a)------------------------------------------------------------------------------------- * Output---- | The result of an STM transaction, along with which 'TVar's it--- touched whilst executing.------ @since 0.1.0.0-data Result a =- Success [TVarId] [TVarId] a- -- ^ The transaction completed successfully, reading the first list- -- 'TVar's and writing to the second.- | Retry [TVarId]- -- ^ The transaction aborted by calling 'retry', and read the- -- returned 'TVar's. It should be retried when at least one of the- -- 'TVar's has been mutated.- | Exception SomeException- -- ^ The transaction aborted by throwing an exception.- deriving Show---- | This only reduces a 'SomeException' to WHNF.------ @since 0.5.1.0-instance NFData a => NFData (Result a) where- rnf (Success tr1 tr2 a) = rnf (tr1, tr2, a)- rnf (Retry tr) = rnf tr- rnf (Exception e) = e `seq` ()---- | Check if a 'Result' is a @Success@.-isSTMSuccess :: Result a -> Bool-isSTMSuccess (Success _ _ _) = True-isSTMSuccess _ = False--instance Functor Result where- fmap f (Success rs ws a) = Success rs ws $ f a- fmap _ (Retry rs) = Retry rs- fmap _ (Exception e) = Exception e--instance Foldable Result where- foldMap f (Success _ _ a) = f a- foldMap _ _ = mempty------------------------------------------------------------------------------------- * Execution---- | Run a STM transaction, returning an action to undo its effects.-doTransaction :: MonadRef r n => M n r a -> IdSource -> n (Result a, n (), IdSource, TTrace)-doTransaction ma idsource = do- (c, ref) <- runRefCont SStop (Just . Right) (runCont ma)- (idsource', undo, readen, written, trace) <- go ref c (pure ()) idsource [] [] []- res <- readRef ref-- case res of- Just (Right val) -> pure (Success (nub readen) (nub written) val, undo, idsource', reverse trace)-- Just (Left exc) -> undo >> pure (Exception exc, pure (), idsource, reverse trace)- Nothing -> undo >> pure (Retry $ nub readen, pure (), idsource, reverse trace)-- where- go ref act undo nidsrc readen written sofar = do- (act', undo', nidsrc', readen', written', tact) <- stepTrans act nidsrc-- let newIDSource = nidsrc'- newAct = act'- newUndo = undo' >> undo- newReaden = readen' ++ readen- newWritten = written' ++ written- newSofar = tact : sofar-- case tact of- TStop -> pure (newIDSource, newUndo, newReaden, newWritten, TStop:newSofar)- TRetry -> do- writeRef ref Nothing- pure (newIDSource, newUndo, newReaden, newWritten, TRetry:newSofar)- TThrow -> do- writeRef ref (Just . Left $ case act of SThrow e -> toException e; _ -> undefined)- pure (newIDSource, newUndo, newReaden, newWritten, TThrow:newSofar)- _ -> go ref newAct newUndo newIDSource newReaden newWritten newSofar---- | Run a transaction for one step.-stepTrans :: MonadRef r n => STMAction n r -> IdSource -> n (STMAction n r, n (), IdSource, [TVarId], [TVarId], TAction)-stepTrans act idsource = case act of- SCatch h stm c -> stepCatch h stm c- SRead ref c -> stepRead ref c- SWrite ref a c -> stepWrite ref a c- SNew n a c -> stepNew n a c- SOrElse a b c -> stepOrElse a b c- SStop na -> stepStop na-- SThrow e -> pure (SThrow e, nothing, idsource, [], [], TThrow)- SRetry -> pure (SRetry, nothing, idsource, [], [], TRetry)-- where- nothing = pure ()-- stepCatch h stm c = cases TCatch stm c- (\trace -> pure (SRetry, nothing, idsource, [], [], TCatch trace Nothing))- (\trace exc -> case fromException exc of- Just exc' -> transaction (TCatch trace . Just) (h exc') c- Nothing -> pure (SThrow exc, nothing, idsource, [], [], TCatch trace Nothing))-- stepRead (TVar (tvid, ref)) c = do- val <- readRef ref- pure (c val, nothing, idsource, [tvid], [], TRead tvid)-- stepWrite (TVar (tvid, ref)) a c = do- old <- readRef ref- writeRef ref a- pure (c, writeRef ref old, idsource, [], [tvid], TWrite tvid)-- stepNew n a c = do- let (idsource', tvid) = nextTVId n idsource- ref <- newRef a- let tvar = TVar (tvid, ref)- pure (c tvar, nothing, idsource', [], [tvid], TNew tvid)-- stepOrElse a b c = cases TOrElse a c- (\trace -> transaction (TOrElse trace . Just) b c)- (\trace exc -> pure (SThrow exc, nothing, idsource, [], [], TOrElse trace Nothing))-- stepStop na = do- na- pure (SStop na, nothing, idsource, [], [], TStop)-- cases tact stm onSuccess onRetry onException = do- (res, undo, idsource', trace) <- doTransaction stm idsource- case res of- Success readen written val -> pure (onSuccess val, undo, idsource', readen, written, tact trace Nothing)- Retry readen -> do- (res', undo', idsource'', readen', written', trace') <- onRetry trace- pure (res', undo', idsource'', readen ++ readen', written', trace')- Exception exc -> onException trace exc-- transaction tact stm onSuccess = cases (\t _ -> tact t) stm onSuccess- (\trace -> pure (SRetry, nothing, idsource, [], [], tact trace))- (\trace exc -> pure (SThrow exc, nothing, idsource, [], [], tact trace))
Test/DejaFu/Schedule.hs view
@@ -1,6 +1,6 @@ -- | -- Module : Test.DejaFu.Schedule--- Copyright : (c) 2016 Michael Walker+-- Copyright : (c) 2016--2017 Michael Walker -- License : MIT -- Maintainer : Michael Walker <mike@barrucadu.co.uk> -- Stability : experimental@@ -11,12 +11,6 @@ ( -- * Scheduling Scheduler(..) - , Decision(..)- , tidOf- , decisionOf-- , NonEmpty(..)- -- ** Preemptive , randomSched , roundRobinSched@@ -29,10 +23,11 @@ , makeNonPreemptive ) where -import Data.List.NonEmpty (NonEmpty(..), toList)-import System.Random (RandomGen, randomR)+import Data.List.NonEmpty (NonEmpty(..), toList)+import System.Random (RandomGen, randomR) -import Test.DejaFu.Common+import Test.DejaFu.Internal+import Test.DejaFu.Types -- | A @Scheduler@ drives the execution of a concurrent program. The -- parameters it takes are:@@ -40,7 +35,7 @@ -- 1. The last thread executed (if this is the first invocation, this -- is @Nothing@). ----- 2. The runnable threads at this point.+-- 2. The unblocked threads. -- -- 3. The state. --@@ -57,36 +52,6 @@ } ---------------------------------------------------------------------------------- Scheduling decisions---- | Get the resultant thread identifier of a 'Decision', with a default case--- for 'Continue'.------ @since 0.5.0.0-tidOf :: ThreadId -> Decision -> ThreadId-tidOf _ (Start t) = t-tidOf _ (SwitchTo t) = t-tidOf tid _ = tid---- | Get the 'Decision' that would have resulted in this thread identifier,--- given a prior thread (if any) and list of runnable threads.------ @since 0.5.0.0-decisionOf :: Foldable f- => Maybe ThreadId- -- ^ The prior thread.- -> f ThreadId- -- ^ The runnable threads.- -> ThreadId- -- ^ The current thread.- -> Decision-decisionOf Nothing _ chosen = Start chosen-decisionOf (Just prior) runnable chosen- | prior == chosen = Continue- | prior `elem` runnable = SwitchTo chosen- | otherwise = Start chosen--------------------------------------------------------------------------------- -- Preemptive -- | A simple random scheduler which, at every step, picks a random@@ -118,16 +83,18 @@ ------------------------------------------------------------------------------- -- Non-preemptive --- | A random scheduler which doesn't preempt the running--- thread. That is, if the last thread scheduled is still runnable,--- run that, otherwise schedule randomly.+-- | A random scheduler which doesn't preempt the running thread. That+-- is, if the previously scheduled thread is not blocked, it is picked+-- again, otherwise schedule randomly. -- -- @since 0.8.0.0 randomSchedNP :: RandomGen g => Scheduler g randomSchedNP = makeNonPreemptive randomSched -- | A round-robin scheduler which doesn't preempt the running--- thread.+-- thread. That is, if the previously scheduled thread is not blocked,+-- it is picked again, otherwise schedule the thread with the next+-- 'ThreadId'. -- -- @since 0.8.0.0 roundRobinSchedNP :: Scheduler ()
+ Test/DejaFu/Types.hs view
@@ -0,0 +1,566 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- |+-- Module : Test.DejaFu.Types+-- Copyright : (c) 2017 Michael Walker+-- License : MIT+-- Maintainer : Michael Walker <mike@barrucadu.co.uk>+-- Stability : experimental+-- Portability : GeneralizedNewtypeDeriving+--+-- Common types and functions used throughout DejaFu.+module Test.DejaFu.Types where++import Control.DeepSeq (NFData(..))+import Control.Exception (Exception(..), MaskingState(..),+ SomeException)+import Data.Function (on)++-------------------------------------------------------------------------------+-- * Identifiers++-- | Every thread has a unique identitifer.+--+-- @since 1.0.0.0+newtype ThreadId = ThreadId Id+ deriving (Eq, Ord, NFData)++instance Show ThreadId where+ show (ThreadId id_) = show id_++-- | Every @CRef@ has a unique identifier.+--+-- @since 1.0.0.0+newtype CRefId = CRefId Id+ deriving (Eq, Ord, NFData)++instance Show CRefId where+ show (CRefId id_) = show id_++-- | Every @MVar@ has a unique identifier.+--+-- @since 1.0.0.0+newtype MVarId = MVarId Id+ deriving (Eq, Ord, NFData)++instance Show MVarId where+ show (MVarId id_) = show id_++-- | Every @TVar@ has a unique identifier.+--+-- @since 1.0.0.0+newtype TVarId = TVarId Id+ deriving (Eq, Ord, NFData)++instance Show TVarId where+ show (TVarId id_) = show id_++-- | An identifier for a thread, @MVar@, @CRef@, or @TVar@.+--+-- The number is the important bit. The string is to make execution+-- traces easier to read, but is meaningless.+data Id = Id (Maybe String) {-# UNPACK #-} !Int++instance Eq Id where+ (Id _ i) == (Id _ j) = i == j++instance Ord Id where+ compare (Id _ i) (Id _ j) = compare i j++instance Show Id where+ show (Id (Just n) _) = n+ show (Id _ i) = show i++instance NFData Id where+ rnf (Id n i) = rnf (n, i)++-- | The ID of the initial thread.+--+-- @since 0.4.0.0+initialThread :: ThreadId+initialThread = ThreadId (Id (Just "main") 0)++-------------------------------------------------------------------------------+-- * Actions++-- | All the actions that a thread can perform.+--+-- @since 1.0.0.0+data ThreadAction =+ Fork ThreadId+ -- ^ Start a new thread.+ | ForkOS ThreadId+ -- ^ Start a new bound thread.+ | IsCurrentThreadBound Bool+ -- ^ Check if the current thread is bound.+ | MyThreadId+ -- ^ Get the 'ThreadId' of the current thread.+ | GetNumCapabilities Int+ -- ^ Get the number of Haskell threads that can run simultaneously.+ | SetNumCapabilities Int+ -- ^ Set the number of Haskell threads that can run simultaneously.+ | Yield+ -- ^ Yield the current thread.+ | ThreadDelay Int+ -- ^ Yield/delay the current thread.+ | NewMVar MVarId+ -- ^ Create a new 'MVar'.+ | PutMVar MVarId [ThreadId]+ -- ^ Put into a 'MVar', possibly waking up some threads.+ | BlockedPutMVar MVarId+ -- ^ Get blocked on a put.+ | TryPutMVar MVarId Bool [ThreadId]+ -- ^ Try to put into a 'MVar', possibly waking up some threads.+ | ReadMVar MVarId+ -- ^ Read from a 'MVar'.+ | TryReadMVar MVarId Bool+ -- ^ Try to read from a 'MVar'.+ | BlockedReadMVar MVarId+ -- ^ Get blocked on a read.+ | TakeMVar MVarId [ThreadId]+ -- ^ Take from a 'MVar', possibly waking up some threads.+ | BlockedTakeMVar MVarId+ -- ^ Get blocked on a take.+ | TryTakeMVar MVarId Bool [ThreadId]+ -- ^ Try to take from a 'MVar', possibly waking up some threads.+ | NewCRef CRefId+ -- ^ Create a new 'CRef'.+ | ReadCRef CRefId+ -- ^ Read from a 'CRef'.+ | ReadCRefCas CRefId+ -- ^ Read from a 'CRef' for a future compare-and-swap.+ | ModCRef CRefId+ -- ^ Modify a 'CRef'.+ | ModCRefCas CRefId+ -- ^ Modify a 'CRef' using a compare-and-swap.+ | WriteCRef CRefId+ -- ^ Write to a 'CRef' without synchronising.+ | CasCRef CRefId Bool+ -- ^ Attempt to to a 'CRef' using a compare-and-swap, synchronising+ -- it.+ | CommitCRef ThreadId CRefId+ -- ^ Commit the last write to the given 'CRef' by the given thread,+ -- so that all threads can see the updated value.+ | STM [TAction] [ThreadId]+ -- ^ An STM transaction was executed, possibly waking up some+ -- threads.+ | BlockedSTM [TAction]+ -- ^ Got blocked in an STM transaction.+ | Catching+ -- ^ Register a new exception handler+ | PopCatching+ -- ^ Pop the innermost exception handler from the stack.+ | Throw+ -- ^ Throw an exception.+ | ThrowTo ThreadId+ -- ^ Throw an exception to a thread.+ | BlockedThrowTo ThreadId+ -- ^ Get blocked on a 'throwTo'.+ | Killed+ -- ^ Killed by an uncaught exception.+ | SetMasking Bool MaskingState+ -- ^ Set the masking state. If 'True', this is being used to set the+ -- masking state to the original state in the argument passed to a+ -- 'mask'ed function.+ | ResetMasking Bool MaskingState+ -- ^ Return to an earlier masking state. If 'True', this is being+ -- used to return to the state of the masked block in the argument+ -- passed to a 'mask'ed function.+ | LiftIO+ -- ^ Lift an IO action. Note that this can only happen with+ -- 'ConcIO'.+ | Return+ -- ^ A 'return' or 'pure' action was executed.+ | Stop+ -- ^ Cease execution and terminate.+ | Subconcurrency+ -- ^ Start executing an action with @subconcurrency@.+ | StopSubconcurrency+ -- ^ Stop executing an action with @subconcurrency@.+ deriving (Eq, Show)++instance NFData ThreadAction where+ rnf (Fork t) = rnf t+ rnf (ForkOS t) = rnf t+ rnf (ThreadDelay n) = rnf n+ rnf (GetNumCapabilities c) = rnf c+ rnf (SetNumCapabilities c) = rnf c+ rnf (NewMVar m) = rnf m+ rnf (PutMVar m ts) = rnf (m, ts)+ rnf (BlockedPutMVar m) = rnf m+ rnf (TryPutMVar m b ts) = rnf (m, b, ts)+ rnf (ReadMVar m) = rnf m+ rnf (TryReadMVar m b) = rnf (m, b)+ rnf (BlockedReadMVar m) = rnf m+ rnf (TakeMVar m ts) = rnf (m, ts)+ rnf (BlockedTakeMVar m) = rnf m+ rnf (TryTakeMVar m b ts) = rnf (m, b, ts)+ rnf (NewCRef c) = rnf c+ rnf (ReadCRef c) = rnf c+ rnf (ReadCRefCas c) = rnf c+ rnf (ModCRef c) = rnf c+ rnf (ModCRefCas c) = rnf c+ rnf (WriteCRef c) = rnf c+ rnf (CasCRef c b) = rnf (c, b)+ rnf (CommitCRef t c) = rnf (t, c)+ rnf (STM tr ts) = rnf (tr, ts)+ rnf (BlockedSTM tr) = rnf tr+ rnf (ThrowTo t) = rnf t+ rnf (BlockedThrowTo t) = rnf t+ rnf (SetMasking b m) = b `seq` m `seq` ()+ rnf (ResetMasking b m) = b `seq` m `seq` ()+ rnf a = a `seq` ()++-- | A one-step look-ahead at what a thread will do next.+--+-- @since 1.0.0.0+data Lookahead =+ WillFork+ -- ^ Will start a new thread.+ | WillForkOS+ -- ^ Will start a new bound thread.+ | WillIsCurrentThreadBound+ -- ^ Will check if the current thread is bound.+ | WillMyThreadId+ -- ^ Will get the 'ThreadId'.+ | WillGetNumCapabilities+ -- ^ Will get the number of Haskell threads that can run+ -- simultaneously.+ | WillSetNumCapabilities Int+ -- ^ Will set the number of Haskell threads that can run+ -- simultaneously.+ | WillYield+ -- ^ Will yield the current thread.+ | WillThreadDelay Int+ -- ^ Will yield/delay the current thread.+ | WillNewMVar+ -- ^ Will create a new 'MVar'.+ | WillPutMVar MVarId+ -- ^ Will put into a 'MVar', possibly waking up some threads.+ | WillTryPutMVar MVarId+ -- ^ Will try to put into a 'MVar', possibly waking up some threads.+ | WillReadMVar MVarId+ -- ^ Will read from a 'MVar'.+ | WillTryReadMVar MVarId+ -- ^ Will try to read from a 'MVar'.+ | WillTakeMVar MVarId+ -- ^ Will take from a 'MVar', possibly waking up some threads.+ | WillTryTakeMVar MVarId+ -- ^ Will try to take from a 'MVar', possibly waking up some threads.+ | WillNewCRef+ -- ^ Will create a new 'CRef'.+ | WillReadCRef CRefId+ -- ^ Will read from a 'CRef'.+ | WillReadCRefCas CRefId+ -- ^ Will read from a 'CRef' for a future compare-and-swap.+ | WillModCRef CRefId+ -- ^ Will modify a 'CRef'.+ | WillModCRefCas CRefId+ -- ^ Will modify a 'CRef' using a compare-and-swap.+ | WillWriteCRef CRefId+ -- ^ Will write to a 'CRef' without synchronising.+ | WillCasCRef CRefId+ -- ^ Will attempt to to a 'CRef' using a compare-and-swap,+ -- synchronising it.+ | WillCommitCRef ThreadId CRefId+ -- ^ Will commit the last write by the given thread to the 'CRef'.+ | WillSTM+ -- ^ Will execute an STM transaction, possibly waking up some+ -- threads.+ | WillCatching+ -- ^ Will register a new exception handler+ | WillPopCatching+ -- ^ Will pop the innermost exception handler from the stack.+ | WillThrow+ -- ^ Will throw an exception.+ | WillThrowTo ThreadId+ -- ^ Will throw an exception to a thread.+ | WillSetMasking Bool MaskingState+ -- ^ Will set the masking state. If 'True', this is being used to+ -- set the masking state to the original state in the argument+ -- passed to a 'mask'ed function.+ | WillResetMasking Bool MaskingState+ -- ^ Will return to an earlier masking state. If 'True', this is+ -- being used to return to the state of the masked block in the+ -- argument passed to a 'mask'ed function.+ | WillLiftIO+ -- ^ Will lift an IO action. Note that this can only happen with+ -- 'ConcIO'.+ | WillReturn+ -- ^ Will execute a 'return' or 'pure' action.+ | WillStop+ -- ^ Will cease execution and terminate.+ | WillSubconcurrency+ -- ^ Will execute an action with @subconcurrency@.+ | WillStopSubconcurrency+ -- ^ Will stop executing an extion with @subconcurrency@.+ deriving (Eq, Show)++instance NFData Lookahead where+ rnf (WillThreadDelay n) = rnf n+ rnf (WillSetNumCapabilities c) = rnf c+ rnf (WillPutMVar m) = rnf m+ rnf (WillTryPutMVar m) = rnf m+ rnf (WillReadMVar m) = rnf m+ rnf (WillTryReadMVar m) = rnf m+ rnf (WillTakeMVar m) = rnf m+ rnf (WillTryTakeMVar m) = rnf m+ rnf (WillReadCRef c) = rnf c+ rnf (WillReadCRefCas c) = rnf c+ rnf (WillModCRef c) = rnf c+ rnf (WillModCRefCas c) = rnf c+ rnf (WillWriteCRef c) = rnf c+ rnf (WillCasCRef c) = rnf c+ rnf (WillCommitCRef t c) = rnf (t, c)+ rnf (WillThrowTo t) = rnf t+ rnf (WillSetMasking b m) = b `seq` m `seq` ()+ rnf (WillResetMasking b m) = b `seq` m `seq` ()+ rnf l = l `seq` ()++-- | All the actions that an STM transaction can perform.+--+-- @since 0.8.0.0+data TAction =+ TNew TVarId+ -- ^ Create a new @TVar@+ | TRead TVarId+ -- ^ Read from a @TVar@.+ | TWrite TVarId+ -- ^ Write to a @TVar@.+ | TRetry+ -- ^ Abort and discard effects.+ | TOrElse [TAction] (Maybe [TAction])+ -- ^ Execute a transaction. If the transaction aborts by calling+ -- @retry@, execute the other transaction.+ | TThrow+ -- ^ Throw an exception, abort, and discard effects.+ | TCatch [TAction] (Maybe [TAction])+ -- ^ Execute a transaction. If the transaction aborts by throwing+ -- an exception of the appropriate type, it is handled and execution+ -- continues; otherwise aborts, propagating the exception upwards.+ | TStop+ -- ^ Terminate successfully and commit effects.+ deriving (Eq, Show)++-- | @since 0.5.1.0+instance NFData TAction where+ rnf (TRead t) = rnf t+ rnf (TWrite t) = rnf t+ rnf (TOrElse tr mtr) = rnf (tr, mtr)+ rnf (TCatch tr mtr) = rnf (tr, mtr)+ rnf ta = ta `seq` ()++-------------------------------------------------------------------------------+-- * Traces++-- | One of the outputs of the runner is a @Trace@, which is a log of+-- decisions made, all the alternative unblocked threads and what they+-- would do, and the action a thread took in its step.+--+-- @since 0.8.0.0+type Trace+ = [(Decision, [(ThreadId, Lookahead)], ThreadAction)]++-- | Scheduling decisions are based on the state of the running+-- program, and so we can capture some of that state in recording what+-- specific decision we made.+--+-- @since 0.5.0.0+data Decision =+ Start ThreadId+ -- ^ Start a new thread, because the last was blocked (or it's the+ -- start of computation).+ | Continue+ -- ^ Continue running the last thread for another step.+ | SwitchTo ThreadId+ -- ^ Pre-empt the running thread, and switch to another.+ deriving (Eq, Show)++-- | @since 0.5.1.0+instance NFData Decision where+ rnf (Start t) = rnf t+ rnf (SwitchTo t) = rnf t+ rnf d = d `seq` ()++-------------------------------------------------------------------------------+-- * Failures++-- | An indication of how a concurrent computation failed.+--+-- The @Eq@, @Ord@, and @NFData@ instances compare/evaluate the+-- exception with @show@ in the @UncaughtException@ case.+--+-- @since 0.9.0.0+data Failure+ = InternalError+ -- ^ Will be raised if the scheduler does something bad. This should+ -- never arise unless you write your own, faulty, scheduler! If it+ -- does, please file a bug report.+ | Abort+ -- ^ The scheduler chose to abort execution. This will be produced+ -- if, for example, all possible decisions exceed the specified+ -- bounds (there have been too many pre-emptions, the computation+ -- has executed for too long, or there have been too many yields).+ | Deadlock+ -- ^ Every thread is blocked, and the main thread is /not/ blocked+ -- in an STM transaction.+ | STMDeadlock+ -- ^ Every thread is blocked, and the main thread is blocked in an+ -- STM transaction.+ | UncaughtException SomeException+ -- ^ An uncaught exception bubbled to the top of the computation.+ | IllegalSubconcurrency+ -- ^ Calls to @subconcurrency@ were nested, or attempted when+ -- multiple threads existed.+ deriving Show++instance Eq Failure where+ InternalError == InternalError = True+ Abort == Abort = True+ Deadlock == Deadlock = True+ STMDeadlock == STMDeadlock = True+ (UncaughtException e1) == (UncaughtException e2) = show e1 == show e2+ IllegalSubconcurrency == IllegalSubconcurrency = True+ _ == _ = False++instance Ord Failure where+ compare = compare `on` transform where+ transform :: Failure -> (Int, Maybe String)+ transform InternalError = (0, Nothing)+ transform Abort = (1, Nothing)+ transform Deadlock = (2, Nothing)+ transform STMDeadlock = (3, Nothing)+ transform (UncaughtException e) = (4, Just (show e))+ transform IllegalSubconcurrency = (5, Nothing)++instance NFData Failure where+ rnf (UncaughtException e) = rnf (show e)+ rnf f = f `seq` ()++-- | Check if a failure is an @InternalError@.+--+-- @since 0.9.0.0+isInternalError :: Failure -> Bool+isInternalError InternalError = True+isInternalError _ = False++-- | Check if a failure is an @Abort@.+--+-- @since 0.9.0.0+isAbort :: Failure -> Bool+isAbort Abort = True+isAbort _ = False++-- | Check if a failure is a @Deadlock@ or an @STMDeadlock@.+--+-- @since 0.9.0.0+isDeadlock :: Failure -> Bool+isDeadlock Deadlock = True+isDeadlock STMDeadlock = True+isDeadlock _ = False++-- | Check if a failure is an @UncaughtException@+--+-- @since 0.9.0.0+isUncaughtException :: Failure -> Bool+isUncaughtException (UncaughtException _) = True+isUncaughtException _ = False++-- | Check if a failure is an @IllegalSubconcurrency@+--+-- @since 0.9.0.0+isIllegalSubconcurrency :: Failure -> Bool+isIllegalSubconcurrency IllegalSubconcurrency = True+isIllegalSubconcurrency _ = False++-------------------------------------------------------------------------------+-- * Discarding results and traces++-- | An @Either Failure a -> Maybe Discard@ value can be used to+-- selectively discard results.+--+-- @since 0.7.1.0+data Discard+ = DiscardTrace+ -- ^ Discard the trace but keep the result. The result will appear+ -- to have an empty trace.+ | DiscardResultAndTrace+ -- ^ Discard the result and the trace. It will simply not be+ -- reported as a possible behaviour of the program.+ deriving (Eq, Show, Read, Ord, Enum, Bounded)++instance NFData Discard where+ rnf d = d `seq` ()++-- | Combine two discard values, keeping the weaker.+--+-- @Nothing@ is weaker than @Just DiscardTrace@, which is weaker than+-- @Just DiscardResultAndTrace@. This forms a commutative monoid+-- where the unit is @const (Just DiscardResultAndTrace)@.+--+-- @since 1.0.0.0+weakenDiscard ::+ (Either Failure a -> Maybe Discard)+ -> (Either Failure a -> Maybe Discard)+ -> Either Failure a -> Maybe Discard+weakenDiscard d1 d2 efa = case (d1 efa, d2 efa) of+ (Nothing, _) -> Nothing+ (_, Nothing) -> Nothing+ (Just DiscardTrace, _) -> Just DiscardTrace+ (_, Just DiscardTrace) -> Just DiscardTrace+ _ -> Just DiscardResultAndTrace++-- | Combine two discard functions, keeping the stronger.+--+-- @Just DiscardResultAndTrace@ is stronger than @Just DiscardTrace@,+-- which is stronger than @Nothing@. This forms a commutative monoid+-- where the unit is @const Nothing@.+--+-- @since 1.0.0.0+strengthenDiscard ::+ (Either Failure a -> Maybe Discard)+ -> (Either Failure a -> Maybe Discard)+ -> Either Failure a -> Maybe Discard+strengthenDiscard d1 d2 efa = case (d1 efa, d2 efa) of+ (Just DiscardResultAndTrace, _) -> Just DiscardResultAndTrace+ (_, Just DiscardResultAndTrace) -> Just DiscardResultAndTrace+ (Just DiscardTrace, _) -> Just DiscardTrace+ (_, Just DiscardTrace) -> Just DiscardTrace+ _ -> Nothing++-------------------------------------------------------------------------------+-- * Memory Models++-- | The memory model to use for non-synchronised 'CRef' operations.+--+-- @since 0.4.0.0+data MemType =+ SequentialConsistency+ -- ^ The most intuitive model: a program behaves as a simple+ -- interleaving of the actions in different threads. When a 'CRef'+ -- is written to, that write is immediately visible to all threads.+ | TotalStoreOrder+ -- ^ Each thread has a write buffer. A thread sees its writes+ -- immediately, but other threads will only see writes when they are+ -- committed, which may happen later. Writes are committed in the+ -- same order that they are created.+ | PartialStoreOrder+ -- ^ Each 'CRef' has a write buffer. A thread sees its writes+ -- immediately, but other threads will only see writes when they are+ -- committed, which may happen later. Writes to different 'CRef's+ -- are not necessarily committed in the same order that they are+ -- created.+ deriving (Eq, Show, Read, Ord, Enum, Bounded)++-- | @since 0.5.1.0+instance NFData MemType where+ rnf m = m `seq` ()++-------------------------------------------------------------------------------+-- * @MonadFail@++-- | An exception for errors in testing caused by use of 'fail'.+newtype MonadFailException = MonadFailException String+ deriving Show++instance Exception MonadFailException
+ Test/DejaFu/Utils.hs view
@@ -0,0 +1,114 @@+-- |+-- Module : Test.DejaFu.utils+-- Copyright : (c) 2017 Michael Walker+-- License : MIT+-- Maintainer : Michael Walker <mike@barrucadu.co.uk>+-- Stability : experimental+-- Portability : portable+--+-- Utility functions for users of dejafu.+module Test.DejaFu.Utils where++import Control.Exception (Exception(..), displayException)+import Data.List (intercalate, minimumBy)+import Data.Maybe (mapMaybe)+import Data.Ord (comparing)++import Test.DejaFu.Types++-------------------------------------------------------------------------------+-- * Traces++-- | Pretty-print a trace, including a key of the thread IDs (not+-- including thread 0). Each line of the key is indented by two+-- spaces.+--+-- @since 0.5.0.0+showTrace :: Trace -> String+showTrace [] = "<trace discarded>"+showTrace trc = intercalate "\n" $ go False trc : strkey where+ go _ ((_,_,CommitCRef _ _):rest) = "C-" ++ go False rest+ go _ ((Start (ThreadId (Id _ i)),_,a):rest) = "S" ++ show i ++ "-" ++ go (didYield a) rest+ go y ((SwitchTo (ThreadId (Id _ i)),_,a):rest) = (if y then "p" else "P") ++ show i ++ "-" ++ go (didYield a) rest+ go _ ((Continue,_,a):rest) = '-' : go (didYield a) rest+ go _ _ = ""++ strkey =+ [" " ++ show i ++ ": " ++ name | (i, name) <- threadNames trc]++ didYield Yield = True+ didYield (ThreadDelay _) = True+ didYield _ = False++-- | Get all named threads in the trace.+--+-- @since 0.7.3.0+threadNames :: Trace -> [(Int, String)]+threadNames = mapMaybe go where+ go (_, _, Fork (ThreadId (Id (Just name) i))) = Just (i, name)+ go (_, _, ForkOS (ThreadId (Id (Just name) i))) = Just (i, name)+ go _ = Nothing++-- | Find the \"simplest\" trace leading to each result.+simplestsBy :: (x -> x -> Bool) -> [(x, Trace)] -> [(x, Trace)]+simplestsBy f = map choose . collect where+ collect = groupBy' [] (\(a,_) (b,_) -> f a b)+ choose = minimumBy . comparing $ \(_, trc) ->+ let switchTos = length . filter (\(d,_,_) -> case d of SwitchTo _ -> True; _ -> False)+ starts = length . filter (\(d,_,_) -> case d of Start _ -> True; _ -> False)+ commits = length . filter (\(_,_,a) -> case a of CommitCRef _ _ -> True; _ -> False)+ in (switchTos trc, commits trc, length trc, starts trc)++ groupBy' res _ [] = res+ groupBy' res eq (y:ys) = groupBy' (insert' eq y res) eq ys++ insert' _ x [] = [[x]]+ insert' eq x (ys@(y:_):yss)+ | x `eq` y = (x:ys) : yss+ | otherwise = ys : insert' eq x yss+ insert' _ _ ([]:_) = undefined++-------------------------------------------------------------------------------+-- * Failures++-- | Pretty-print a failure+--+-- @since 0.4.0.0+showFail :: Failure -> String+showFail Abort = "[abort]"+showFail Deadlock = "[deadlock]"+showFail STMDeadlock = "[stm-deadlock]"+showFail InternalError = "[internal-error]"+showFail (UncaughtException exc) = "[" ++ displayException exc ++ "]"+showFail IllegalSubconcurrency = "[illegal-subconcurrency]"++-------------------------------------------------------------------------------+-- * Scheduling++-- | Get the resultant thread identifier of a 'Decision', with a default case+-- for 'Continue'.+--+-- @since 0.5.0.0+tidOf :: ThreadId -> Decision -> ThreadId+tidOf _ (Start t) = t+tidOf _ (SwitchTo t) = t+tidOf tid _ = tid++-- | Get the 'Decision' that would have resulted in this thread+-- identifier, given a prior thread (if any) and collection of threads+-- which are unblocked at this point.+--+-- @since 0.5.0.0+decisionOf :: Foldable f+ => Maybe ThreadId+ -- ^ The prior thread.+ -> f ThreadId+ -- ^ The threads.+ -> ThreadId+ -- ^ The current thread.+ -> Decision+decisionOf Nothing _ chosen = Start chosen+decisionOf (Just prior) runnable chosen+ | prior == chosen = Continue+ | prior `elem` runnable = SwitchTo chosen+ | otherwise = Start chosen
dejafu.cabal view
@@ -2,29 +2,25 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: dejafu-version: 0.9.1.2-synopsis: Systematic testing for Haskell concurrency.+version: 1.0.0.0+synopsis: A library for unit-testing concurrent programs. description: /[Déjà Fu is] A martial art in which the user's limbs move in time as well as space, […] It is best described as "the feeling that you have been kicked in the head this way before"/ -- Terry Pratchett, Thief of Time .- Concurrency is nice, deadlocks and race conditions not so much. The- @Par@ monad family, as defined in- <https://hackage.haskell.org/package/abstract-par/docs/Control-Monad-Par-Class.html abstract-par>- provides deterministic parallelism, but sometimes we can tolerate a- bit of nondeterminism.- . This package builds on the- <https://hackage.haskell.org/package/concurrency concurrency>- package by enabling you to systematically and deterministically test- your concurrent programs.+ [concurrency](https://hackage.haskell.org/package/concurrency)+ package by enabling you to deterministically test your concurrent+ programs.+ .+ See the [website](https://dejafu.readthedocs.io) or README for more. homepage: https://github.com/barrucadu/dejafu license: MIT license-file: LICENSE author: Michael Walker maintainer: mike@barrucadu.co.uk--- copyright: +copyright: (c) 2015--2017 Michael Walker category: Concurrency build-type: Simple extra-source-files: README.markdown CHANGELOG.markdown@@ -37,37 +33,39 @@ source-repository this type: git location: https://github.com/barrucadu/dejafu.git- tag: dejafu-0.9.1.2+ tag: dejafu-1.0.0.0 library exposed-modules: Test.DejaFu , Test.DejaFu.Conc- , Test.DejaFu.Common , Test.DejaFu.Defaults , Test.DejaFu.Refinement- , Test.DejaFu.Schedule , Test.DejaFu.SCT- , Test.DejaFu.STM+ , Test.DejaFu.Schedule+ , Test.DejaFu.Types+ , Test.DejaFu.Utils , Test.DejaFu.Conc.Internal , Test.DejaFu.Conc.Internal.Common , Test.DejaFu.Conc.Internal.Memory+ , Test.DejaFu.Conc.Internal.STM , Test.DejaFu.Conc.Internal.Threading- , Test.DejaFu.SCT.Internal- , Test.DejaFu.STM.Internal+ , Test.DejaFu.Internal+ , Test.DejaFu.SCT.Internal.DPOR+ , Test.DejaFu.SCT.Internal.Weighted -- other-modules: -- other-extensions: build-depends: base >=4.8 && <5- , concurrency >=1.1 && <1.3+ , concurrency >=1.3 && <1.4 , containers >=0.5 && <0.6 , deepseq >=1.1 && <2 , exceptions >=0.7 && <0.9 , leancheck >=0.6 && <0.8+ , profunctors >=4.0 && <6.0 , random >=1.0 && <1.2 , ref-fd >=0.4 && <0.5- , transformers >=0.4 && <0.6- , transformers-base >=0.4 && <0.5+ , transformers >=0.4 && <0.6 -- hs-source-dirs: default-language: Haskell2010 ghc-options: -Wall