Copyright | (c) 2016--2019 Michael Walker |
---|---|
License | MIT |
Maintainer | Michael Walker <mike@barrucadu.co.uk> |
Stability | experimental |
Portability | portable |
Safe Haskell | Safe-Inferred |
Language | Haskell2010 |
Deterministic traced execution of concurrent computations.
This works by executing the computation on a single thread, calling out to the supplied scheduler after each step to determine which thread runs next.
Synopsis
- data Program pty n a
- data Basic
- type ConcT = Program Basic
- type ConcIO = ConcT IO
- data WithSetup x
- data WithSetupAndTeardown x y
- withSetup :: Program Basic n x -> (x -> Program Basic n a) -> Program (WithSetup x) n a
- withTeardown :: (x -> Either Condition y -> Program Basic n a) -> Program (WithSetup x) n y -> Program (WithSetupAndTeardown x y) n a
- withSetupAndTeardown :: Program Basic n x -> (x -> Either Condition y -> Program Basic n a) -> (x -> Program Basic n y) -> Program (WithSetupAndTeardown x y) n a
- data Invariant n a
- registerInvariant :: Invariant n a -> Program Basic n ()
- inspectIORef :: ModelIORef n a -> Invariant n a
- inspectMVar :: ModelMVar n a -> Invariant n (Maybe a)
- inspectTVar :: ModelTVar n a -> Invariant n a
- data Snapshot pty n a
- data MemType
- runConcurrent :: MonadDejaFu n => Scheduler s -> MemType -> s -> Program pty n a -> n (Either Condition a, s, Trace)
- recordSnapshot :: MonadDejaFu n => Program pty n a -> n (Maybe (Either Condition (Snapshot pty n a), Trace))
- runSnapshot :: MonadDejaFu n => Scheduler s -> MemType -> s -> Snapshot pty n a -> n (Either Condition a, s, Trace)
- module Test.DejaFu.Schedule
- data Condition
- type Trace = [(Decision, [(ThreadId, Lookahead)], ThreadAction)]
- data Decision
- newtype ThreadId = ThreadId Id
- data ThreadAction
- = Fork ThreadId
- | ForkOS ThreadId
- | SupportsBoundThreads Bool
- | IsCurrentThreadBound Bool
- | MyThreadId
- | GetNumCapabilities Int
- | SetNumCapabilities Int
- | Yield
- | ThreadDelay Int
- | NewMVar MVarId
- | PutMVar MVarId [ThreadId]
- | BlockedPutMVar MVarId
- | TryPutMVar MVarId Bool [ThreadId]
- | ReadMVar MVarId
- | TryReadMVar MVarId Bool
- | BlockedReadMVar MVarId
- | TakeMVar MVarId [ThreadId]
- | BlockedTakeMVar MVarId
- | TryTakeMVar MVarId Bool [ThreadId]
- | NewIORef IORefId
- | ReadIORef IORefId
- | ReadIORefCas IORefId
- | ModIORef IORefId
- | ModIORefCas IORefId
- | WriteIORef IORefId
- | CasIORef IORefId Bool
- | CommitIORef ThreadId IORefId
- | STM [TAction] [ThreadId]
- | ThrownSTM [TAction] (Maybe MaskingState)
- | BlockedSTM [TAction]
- | Catching
- | PopCatching
- | Throw (Maybe MaskingState)
- | ThrowTo ThreadId (Maybe MaskingState)
- | BlockedThrowTo ThreadId
- | SetMasking Bool MaskingState
- | ResetMasking Bool MaskingState
- | GetMaskingState MaskingState
- | LiftIO
- | Return
- | Stop
- | RegisterInvariant
- data Lookahead
- = WillFork
- | WillForkOS
- | WillSupportsBoundThreads
- | WillIsCurrentThreadBound
- | WillMyThreadId
- | WillGetNumCapabilities
- | WillSetNumCapabilities Int
- | WillYield
- | WillThreadDelay Int
- | WillNewMVar
- | WillPutMVar MVarId
- | WillTryPutMVar MVarId
- | WillReadMVar MVarId
- | WillTryReadMVar MVarId
- | WillTakeMVar MVarId
- | WillTryTakeMVar MVarId
- | WillNewIORef
- | WillReadIORef IORefId
- | WillReadIORefCas IORefId
- | WillModIORef IORefId
- | WillModIORefCas IORefId
- | WillWriteIORef IORefId
- | WillCasIORef IORefId
- | WillCommitIORef ThreadId IORefId
- | WillSTM
- | WillCatching
- | WillPopCatching
- | WillThrow
- | WillThrowTo ThreadId
- | WillSetMasking Bool MaskingState
- | WillResetMasking Bool MaskingState
- | WillGetMaskingState
- | WillLiftIO
- | WillReturn
- | WillStop
- | WillRegisterInvariant
- data MVarId
- data IORefId
- data MaskingState
- showTrace :: Trace -> String
- showCondition :: Condition -> String
Expressing concurrent programs
A representation of a concurrent program for testing.
To construct these, use the MonadConc
instance, or see
withSetup
, withTeardown
, and
withSetupAndTeardown
.
Since: 2.0.0.0
Instances
A type used to constrain Program
: a Program Basic
is a
"basic" program with no set-up or teardown.
Construct with the MonadConc
instance.
Since: 2.0.0.0
Setup and teardown
data WithSetupAndTeardown x y Source #
A type used to constrain Program
: a Program
(WithSetupAndTeardown x y)
is a program producing a value of type
y
with some set-up action producing a value of type x
and a
teardown action producing the final result.
Construct with withTeardown
or
withSetupAndTeardown
.
Since: 2.0.0.0
A concurrent program with some set-up action.
In terms of results, this is the same as setup >>= program
.
However, the setup action will be snapshotted (see
recordSnapshot
and runSnapshot
) by the testing functions. This
means that even if dejafu runs this program many many times, the
setup action will only be run the first time, and its effects
remembered for subsequent executions.
Since: 2.0.0.0
:: (x -> Either Condition y -> Program Basic n a) | Teardown action |
-> Program (WithSetup x) n y | Main program |
-> Program (WithSetupAndTeardown x y) n a |
A concurrent program with some set-up and teardown actions.
This is similar to
do x <- setup y <- program x teardown x y
But with two differences:
- The setup action can be snapshotted, as described for
withSetup
- The teardown action will be executed even if the main action fails to produce a value.
Since: 2.0.0.0
:: Program Basic n x | Setup action |
-> (x -> Either Condition y -> Program Basic n a) | Teardown action |
-> (x -> Program Basic n y) | Main program |
-> Program (WithSetupAndTeardown x y) n a |
A combination of withSetup
and withTeardown
for convenience.
withSetupAndTeardown setup teardown = withTeardown teardown . withSetup setup
Since: 2.0.0.0
Invariants
Invariants are atomic actions which can inspect the shared state of your computation, and terminate it on failure. Invariants have no visible effects, and are checked after each scheduling point.
To be checked, an invariant must be created during the setup phase
of your Program
, using registerInvariant
. The
invariant will then be checked in the main phase (but not in the
setup or teardown phase). As a consequence of this, any shared
state you want your invariant to check must also be created in the
setup phase, and passed into the main phase as a parameter.
Since: 2.0.0.0
Instances
MonadFail (Invariant n) Source # | |
Defined in Test.DejaFu.Conc.Internal.Common | |
Applicative (Invariant n) Source # | |
Defined in Test.DejaFu.Conc.Internal.Common | |
Functor (Invariant n) Source # | |
Monad (Invariant n) Source # | |
MonadCatch (Invariant n) Source # | |
MonadThrow (Invariant n) Source # | |
Defined in Test.DejaFu.Conc.Internal.Common |
registerInvariant :: Invariant n a -> Program Basic n () Source #
Call this in the setup phase to register new invariant which will be checked after every scheduling point in the main phase. Invariants are atomic actions which can inspect the shared state of your computation.
If the invariant throws an exception, the execution will be aborted
with n InvariantFailure
. Any teardown action will still be run.
Since: 2.0.0.0
inspectIORef :: ModelIORef n a -> Invariant n a Source #
Read the content of an IORef
.
This returns the globally visible value, which may not be the same
as the value visible to any particular thread when using a memory
model other than SequentialConsistency
.
Since: 2.0.0.0
inspectMVar :: ModelMVar n a -> Invariant n (Maybe a) Source #
Read the content of an MVar
.
This is essentially tryReadMVar
.
Since: 2.0.0.0
inspectTVar :: ModelTVar n a -> Invariant n a Source #
Read the content of a TVar
.
This is essentially readTVar
.
Since: 2.0.0.0
Executing concurrent programs
data Snapshot pty n a Source #
A record of the state of a concurrent program immediately after completing the setup action.
Since: 2.0.0.0
The memory model to use for non-synchronised IORef
operations.
Since: 0.4.0.0
SequentialConsistency | The most intuitive model: a program behaves as a simple
interleaving of the actions in different threads. When a |
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 |
Instances
Bounded MemType Source # | |
Enum MemType Source # | |
Generic MemType Source # | |
Read MemType Source # | |
Show MemType Source # | |
NFData MemType Source # | Since: 0.5.1.0 |
Defined in Test.DejaFu.Types | |
Eq MemType Source # | |
Ord MemType Source # | |
type Rep MemType Source # | Since: 1.3.1.0 |
Defined in Test.DejaFu.Types type Rep MemType = D1 ('MetaData "MemType" "Test.DejaFu.Types" "dejafu-2.4.0.6-2Exh5SnWzcKIwmIryqnn01" 'False) (C1 ('MetaCons "SequentialConsistency" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "TotalStoreOrder" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "PartialStoreOrder" 'PrefixI 'False) (U1 :: Type -> Type))) |
runConcurrent :: MonadDejaFu n => Scheduler s -> MemType -> s -> Program pty n a -> n (Either Condition a, s, Trace) Source #
Run a concurrent computation with a given Scheduler
and initial
state, returning either the final result or the condition which
prevented that. 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!
You should therefore keep IO
blocks small, and only perform
blocking operations with the supplied primitives, insofar as
possible.
Note: In order to prevent computation from hanging, the runtime will assume that a deadlock situation has arisen if the scheduler attempts to (a) schedule a blocked thread, or (b) schedule a nonexistent thread. In either of those cases, the computation will be halted.
Since: 2.1.0.0
recordSnapshot :: MonadDejaFu n => Program pty n a -> n (Maybe (Either Condition (Snapshot pty n a), Trace)) Source #
Runs any setup action and returns a Snapshot
which can be
passed to runSnapshot
. If there is no setup action (this is a
Program Basic
, then Nothing
is returned. The snapshot captures
the state at the end of the setup, so the full program can be run
multiple times without repeating the setup.
The setup action is executed atomically with a deterministic scheduler under sequential consistency. Any forked threads continue to exist in the main program.
If the setup action does not successfully produce a value (deadlock, uncaught exception, etc), no snapshot is produced.
Snapshotting IO
: A snapshot captures entire state of your
concurrent program: the state of every thread, the number of
capabilities, the values of any IORef
s, MVar
s, and TVar
s, and
records any IO
that you performed.
When restoring a snapshot this IO
is replayed, in order. But the
whole snapshotted computation is not. So the effects of the IO
take place again, but any return values are ignored. For example,
this program will not do what you want:
bad_snapshot = withSetup (do r <- liftIO (newIORef 0) liftIO (modifyIORef r (+1)) pure r) (liftIO . readIORef)
When the snapshot is taken, the value in the IORef
will be 1.
When the snapshot is restored for the first time, those IO
actions will be run again, /but their return values will be
discarded/. The value in the IORef
will be 2. When the snapshot
is restored for the second time, the value in the IORef
will be
3. And so on.
To safely use IO
in a snapshotted computation, __the combined
effect must be idempotent__. You should either use actions which
set the state to the final value directly, rather than modifying it
(eg, using a combination of liftIO . readIORef
and liftIO
. writeIORef
here), or reset the state to a known value. Both of
these approaches will work:
good_snapshot1 = withSetup (do let modify r f = liftIO (readIORef r) >>= liftIO . writeIORef r . f r <- liftIO (newIORef 0) modify r (+1) pure r) (liftIO . readIORef) good_snapshot2 = withSetup (do r <- liftIO (newIORef 0) liftIO (writeIORef r 0) liftIO (modifyIORef r (+1)) pure r) (liftIO . readIORef)
Since: 2.1.0.0
runSnapshot :: MonadDejaFu n => Scheduler s -> MemType -> s -> Snapshot pty n a -> n (Either Condition a, s, Trace) Source #
Runs a program with snapshotted setup to completion.
Since: 2.1.0.0
Scheduling
module Test.DejaFu.Schedule
Results
An indication of how a concurrent computation terminated, if it didn't produce a value.
The Eq
, Ord
, and NFData
instances compare/evaluate the
exception with show
in the UncaughtException
and
InvariantFailure
cases.
Since: 2.0.0.0
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 |
UncaughtException SomeException | An uncaught exception bubbled to the top of the computation. |
InvariantFailure SomeException | An uncaught exception caused an invariant to fail. |
Instances
Generic Condition Source # | |
Show Condition Source # | |
NFData Condition Source # | |
Defined in Test.DejaFu.Types | |
Eq Condition Source # | |
Ord Condition Source # | |
Defined in Test.DejaFu.Types | |
type Rep Condition Source # | |
Defined in Test.DejaFu.Types type Rep Condition = D1 ('MetaData "Condition" "Test.DejaFu.Types" "dejafu-2.4.0.6-2Exh5SnWzcKIwmIryqnn01" 'False) ((C1 ('MetaCons "Abort" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Deadlock" 'PrefixI 'False) (U1 :: Type -> Type)) :+: (C1 ('MetaCons "UncaughtException" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 SomeException)) :+: C1 ('MetaCons "InvariantFailure" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 SomeException)))) |
type Trace = [(Decision, [(ThreadId, Lookahead)], ThreadAction)] Source #
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
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
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. |
Instances
Generic Decision Source # | |
Show Decision Source # | |
NFData Decision Source # | Since: 0.5.1.0 |
Defined in Test.DejaFu.Types | |
Eq Decision Source # | |
type Rep Decision Source # | Since: 1.3.1.0 |
Defined in Test.DejaFu.Types type Rep Decision = D1 ('MetaData "Decision" "Test.DejaFu.Types" "dejafu-2.4.0.6-2Exh5SnWzcKIwmIryqnn01" 'False) (C1 ('MetaCons "Start" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ThreadId)) :+: (C1 ('MetaCons "Continue" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "SwitchTo" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ThreadId)))) |
Every thread has a unique identitifer.
Since: 1.0.0.0
data ThreadAction Source #
All the actions that a thread can perform.
Since: 2.2.0.0
Fork ThreadId | Start a new thread. |
ForkOS ThreadId | Start a new bound thread. |
SupportsBoundThreads Bool | Check if bound threads are supported. |
IsCurrentThreadBound Bool | Check if the current thread is bound. |
MyThreadId | Get the |
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 |
PutMVar MVarId [ThreadId] | Put into a |
BlockedPutMVar MVarId | Get blocked on a put. |
TryPutMVar MVarId Bool [ThreadId] | Try to put into a |
ReadMVar MVarId | Read from a |
TryReadMVar MVarId Bool | Try to read from a |
BlockedReadMVar MVarId | Get blocked on a read. |
TakeMVar MVarId [ThreadId] | Take from a |
BlockedTakeMVar MVarId | Get blocked on a take. |
TryTakeMVar MVarId Bool [ThreadId] | Try to take from a |
NewIORef IORefId | Create a new |
ReadIORef IORefId | Read from a |
ReadIORefCas IORefId | Read from a |
ModIORef IORefId | Modify a |
ModIORefCas IORefId | Modify a |
WriteIORef IORefId | Write to a |
CasIORef IORefId Bool | Attempt to to a |
CommitIORef ThreadId IORefId | Commit the last write to the given |
STM [TAction] [ThreadId] | An STM transaction was executed, possibly waking up some threads. |
ThrownSTM [TAction] (Maybe MaskingState) | An STM transaction threw an exception. Give the resultant masking state after jumping to the exception handler (if the thread is still alive). |
BlockedSTM [TAction] | Got blocked in an STM transaction. |
Catching | Register a new exception handler |
PopCatching | Pop the innermost exception handler from the stack. |
Throw (Maybe MaskingState) | Throw an exception, and give the resultant masking state after jumping to the exception handler (if the thread is still alive). |
ThrowTo ThreadId (Maybe MaskingState) | Throw an exception to a thread, and give the resultant masking state after jumping to the exception handler (if the thread is still alive). |
BlockedThrowTo ThreadId | Get blocked on a |
SetMasking Bool MaskingState | Set the masking state. If |
ResetMasking Bool MaskingState | Return to an earlier masking state. If |
GetMaskingState MaskingState | Get the current masking state. |
LiftIO | Lift an IO action. Note that this can only happen with
|
Return | |
Stop | Cease execution and terminate. |
RegisterInvariant | Register an invariant. |
Instances
A one-step look-ahead at what a thread will do next.
Since: 2.2.0.0
WillFork | Will start a new thread. |
WillForkOS | Will start a new bound thread. |
WillSupportsBoundThreads | Will check if bound threads are supported. |
WillIsCurrentThreadBound | Will check if the current thread is bound. |
WillMyThreadId | Will get the |
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 |
WillPutMVar MVarId | Will put into a |
WillTryPutMVar MVarId | Will try to put into a |
WillReadMVar MVarId | Will read from a |
WillTryReadMVar MVarId | Will try to read from a |
WillTakeMVar MVarId | Will take from a |
WillTryTakeMVar MVarId | Will try to take from a |
WillNewIORef | Will create a new |
WillReadIORef IORefId | Will read from a |
WillReadIORefCas IORefId | Will read from a |
WillModIORef IORefId | Will modify a |
WillModIORefCas IORefId | Will modify a |
WillWriteIORef IORefId | Will write to a |
WillCasIORef IORefId | Will attempt to to a |
WillCommitIORef ThreadId IORefId | Will commit the last write by the given thread to the |
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 |
WillResetMasking Bool MaskingState | Will return to an earlier masking state. If |
WillGetMaskingState | Will get the masking state. |
WillLiftIO | Will lift an IO action. Note that this can only happen with
|
WillReturn | |
WillStop | Will cease execution and terminate. |
WillRegisterInvariant | Will register an invariant |
Instances
Every MVar
has a unique identifier.
Since: 1.0.0.0
Every IORef
has a unique identifier.
Since: 1.11.0.0
data MaskingState #
Describes the behaviour of a thread when an asynchronous exception is received.
Unmasked | asynchronous exceptions are unmasked (the normal state) |
MaskedInterruptible | the state during |
MaskedUninterruptible | the state during |
Instances
Show MaskingState | Since: base-4.3.0.0 |
Defined in GHC.IO showsPrec :: Int -> MaskingState -> ShowS # show :: MaskingState -> String # showList :: [MaskingState] -> ShowS # | |
NFData MaskingState | Since: deepseq-1.4.4.0 |
Defined in Control.DeepSeq rnf :: MaskingState -> () # | |
Eq MaskingState | Since: base-4.3.0.0 |
Defined in GHC.IO (==) :: MaskingState -> MaskingState -> Bool # (/=) :: MaskingState -> MaskingState -> Bool # |
showTrace :: Trace -> String Source #
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
showCondition :: Condition -> String Source #
Pretty-print a condition
Since: 1.12.0.0