2014-12-27 15:20:45 +03:00
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
2015-01-21 18:31:10 +03:00
|
|
|
{-# LANGUAGE Rank2Types #-}
|
2015-01-28 15:10:19 +03:00
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
2014-12-27 15:20:45 +03:00
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
-- | Deterministic traced execution of concurrent computations which
|
|
|
|
-- may do @IO@.
|
2014-12-27 15:20:45 +03:00
|
|
|
--
|
2015-02-01 04:21:42 +03:00
|
|
|
-- __Caution!__ Blocking on the action of another thread in 'liftIO'
|
2014-12-27 15:20:45 +03:00
|
|
|
-- cannot be detected! So if you perform some potentially blocking
|
|
|
|
-- action in a 'liftIO' the entire collection of threads may deadlock!
|
2015-02-01 04:21:42 +03:00
|
|
|
-- You should therefore keep @IO@ blocks small, and only perform
|
2014-12-27 15:20:45 +03:00
|
|
|
-- blocking operations with the supplied primitives, insofar as
|
|
|
|
-- possible.
|
2015-01-31 18:50:54 +03:00
|
|
|
module Test.DejaFu.Deterministic.IO
|
2015-02-01 04:21:42 +03:00
|
|
|
( -- * The @ConcIO@ Monad
|
|
|
|
ConcIO
|
|
|
|
, runConcIO
|
2014-12-27 15:20:45 +03:00
|
|
|
, liftIO
|
|
|
|
, fork
|
2015-01-28 15:10:19 +03:00
|
|
|
, spawn
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- * Communication: CVars
|
|
|
|
, CVar
|
|
|
|
, newEmptyCVar
|
|
|
|
, putCVar
|
|
|
|
, tryPutCVar
|
|
|
|
, readCVar
|
|
|
|
, takeCVar
|
|
|
|
, tryTakeCVar
|
2015-01-25 19:15:23 +03:00
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
-- * Execution traces
|
|
|
|
, Trace
|
|
|
|
, Decision
|
|
|
|
, ThreadAction
|
|
|
|
, showTrace
|
|
|
|
|
|
|
|
-- * Scheduling
|
|
|
|
, module Test.DejaFu.Deterministic.Schedule
|
2014-12-27 15:20:45 +03:00
|
|
|
) where
|
|
|
|
|
|
|
|
import Control.Applicative (Applicative(..), (<$>))
|
|
|
|
import Control.Monad.Cont (cont, runCont)
|
|
|
|
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
|
2015-01-31 18:50:54 +03:00
|
|
|
import Test.DejaFu.Deterministic.Internal
|
2015-02-01 04:21:42 +03:00
|
|
|
import Test.DejaFu.Deterministic.Schedule
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
import qualified Control.Monad.Conc.Class as C
|
|
|
|
import qualified Control.Monad.IO.Class as IO
|
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
-- | The 'IO' variant of Test.DejaFu.Deterministic's @Conc@ monad.
|
|
|
|
newtype ConcIO t a = C { unC :: M IO IORef a } deriving (Functor, Applicative, Monad)
|
2014-12-27 15:20:45 +03:00
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
instance IO.MonadIO (ConcIO t) where
|
2014-12-27 15:20:45 +03:00
|
|
|
liftIO = liftIO
|
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
instance C.MonadConc (ConcIO t) where
|
|
|
|
type CVar (ConcIO t) = CVar t
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
fork = fork
|
|
|
|
newEmptyCVar = newEmptyCVar
|
|
|
|
putCVar = putCVar
|
|
|
|
tryPutCVar = tryPutCVar
|
2015-01-28 15:10:19 +03:00
|
|
|
readCVar = readCVar
|
2014-12-27 15:20:45 +03:00
|
|
|
takeCVar = takeCVar
|
|
|
|
tryTakeCVar = tryTakeCVar
|
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
fixed :: Fixed ConcIO IO IORef t
|
2014-12-27 15:20:45 +03:00
|
|
|
fixed = F
|
|
|
|
{ newRef = newIORef
|
|
|
|
, readRef = readIORef
|
|
|
|
, writeRef = writeIORef
|
|
|
|
, liftN = liftIO
|
2014-12-28 01:03:37 +03:00
|
|
|
, getCont = unC
|
2014-12-27 15:20:45 +03:00
|
|
|
}
|
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
-- | The concurrent variable type used with the 'ConcIO' monad. These
|
|
|
|
-- behave the same as @Conc@'s @CVar@s
|
2014-12-28 01:03:37 +03:00
|
|
|
newtype CVar t a = V { unV :: R IORef a } deriving Eq
|
2014-12-27 15:20:45 +03:00
|
|
|
|
2015-02-01 04:21:42 +03:00
|
|
|
-- | Lift an 'IO' action into the 'ConcIO' monad.
|
|
|
|
liftIO :: IO a -> ConcIO t a
|
2014-12-27 15:20:45 +03:00
|
|
|
liftIO ma = C $ cont lifted where
|
|
|
|
lifted c = ALift $ c <$> ma
|
|
|
|
|
|
|
|
-- | Run the provided computation concurrently, returning the result.
|
2015-02-01 04:21:42 +03:00
|
|
|
spawn :: ConcIO t a -> ConcIO t (CVar t a)
|
2015-01-28 15:10:19 +03:00
|
|
|
spawn = C.spawn
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- | Block on a 'CVar' until it is full, then read from it (without
|
|
|
|
-- emptying).
|
2015-02-01 04:21:42 +03:00
|
|
|
readCVar :: CVar t a -> ConcIO t a
|
2014-12-28 01:03:37 +03:00
|
|
|
readCVar cvar = C $ cont $ AGet $ unV cvar
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- | Run the provided computation concurrently.
|
2015-02-01 04:21:42 +03:00
|
|
|
fork :: ConcIO t () -> ConcIO t ()
|
2014-12-27 15:20:45 +03:00
|
|
|
fork (C ma) = C $ cont $ \c -> AFork (runCont ma $ const AStop) $ c ()
|
|
|
|
|
|
|
|
-- | Create a new empty 'CVar'.
|
2015-02-01 04:21:42 +03:00
|
|
|
newEmptyCVar :: ConcIO t (CVar t a)
|
2015-01-12 03:08:53 +03:00
|
|
|
newEmptyCVar = C $ cont lifted where
|
|
|
|
lifted c = ANew $ c <$> newEmptyCVar'
|
|
|
|
newEmptyCVar' = V <$> newIORef (Nothing, [])
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- | Block on a 'CVar' until it is empty, then write to it.
|
2015-02-01 04:21:42 +03:00
|
|
|
putCVar :: CVar t a -> a -> ConcIO t ()
|
2014-12-28 01:03:37 +03:00
|
|
|
putCVar cvar a = C $ cont $ \c -> APut (unV cvar) a $ c ()
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- | Put a value into a 'CVar' if there isn't one, without blocking.
|
2015-02-01 04:21:42 +03:00
|
|
|
tryPutCVar :: CVar t a -> a -> ConcIO t Bool
|
2014-12-28 01:03:37 +03:00
|
|
|
tryPutCVar cvar a = C $ cont $ ATryPut (unV cvar) a
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- | Block on a 'CVar' until it is full, then read from it (with
|
|
|
|
-- emptying).
|
2015-02-01 04:21:42 +03:00
|
|
|
takeCVar :: CVar t a -> ConcIO t a
|
2014-12-28 01:03:37 +03:00
|
|
|
takeCVar cvar = C $ cont $ ATake $ unV cvar
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- | Read a value from a 'CVar' if there is one, without blocking.
|
2015-02-01 04:21:42 +03:00
|
|
|
tryTakeCVar :: CVar t a -> ConcIO t (Maybe a)
|
2014-12-28 01:03:37 +03:00
|
|
|
tryTakeCVar cvar = C $ cont $ ATryTake $ unV cvar
|
2014-12-27 15:20:45 +03:00
|
|
|
|
|
|
|
-- | Run a concurrent computation with a given 'Scheduler' and initial
|
2015-01-12 17:24:12 +03:00
|
|
|
-- state, returning 'Just' if it terminates, and 'Nothing' if a
|
2015-02-01 04:21:42 +03:00
|
|
|
-- deadlock is detected. Also returned is the final state of the
|
|
|
|
-- scheduler, and an execution trace.
|
|
|
|
runConcIO :: Scheduler s -> s -> (forall t. ConcIO t a) -> IO (Maybe a, s, Trace)
|
2014-12-27 15:20:45 +03:00
|
|
|
-- Note: Don't eta-reduce, the forall t messes up type inference.
|
2015-02-01 04:21:42 +03:00
|
|
|
runConcIO sched s ma = runFixed fixed sched s ma
|