stm-io-hooks (empty) → 0.0.1
raw patch · 11 files changed
+1007/−0 lines, 11 filesdep +arraydep +basedep +containerssetup-changed
Dependencies added: array, base, containers, mtl, stm
Files
- Control/Concurrent/AdvSTM.hs +253/−0
- Control/Concurrent/AdvSTM/TArray.hs +46/−0
- Control/Concurrent/AdvSTM/TChan.hs +97/−0
- Control/Concurrent/AdvSTM/TMVar.hs +143/−0
- Control/Concurrent/AdvSTM/TVar.hs +112/−0
- Control/Monad/AdvSTM.hs +82/−0
- Control/Monad/AdvSTM/Class.hs +85/−0
- Control/Monad/AdvSTM/Def.hs +47/−0
- LICENSE +90/−0
- Setup.lhs +3/−0
- stm-io-hooks.cabal +49/−0
+ Control/Concurrent/AdvSTM.hs view
@@ -0,0 +1,253 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.AdvSTM+-- Copyright : (c) Chris Kuklewicz 2006, Peter Robinson 2009+-- License : BSD3+-- +-- Maintainer : Peter Robinson <robinson@ecs.tuwien.ac.at>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- Extends Control.Concurrent.STM with IO hooks+-- +-----------------------------------------------------------------------------+ +module Control.Concurrent.AdvSTM( -- * Class MonadAdvSTM+ MonadAdvSTM(..)+ -- * Monad AdvSTM+ , AdvSTM+ , unsafeRetryWith+ , atomically+ , unsafeIOToAdvSTM+ , debugAdvSTM+ , debugMode+ ) where+ +import Prelude hiding (catch)+import Control.Monad.AdvSTM.Def(AdvSTM(..),Env(..),TVarValue(..))+import Control.Monad.AdvSTM.Class(MonadAdvSTM(..))++import Control.Exception(throw,catch,SomeException,fromException,try) +import Control.Monad(mplus,when)+import Control.Monad.Reader(MonadReader(..),ReaderT,runReaderT,lift,asks)+import Control.Concurrent(threadDelay,forkIO,ThreadId,myThreadId)+import Control.Concurrent.Chan(Chan,newChan,readChan,writeChan)+import Control.Concurrent.STM.TMVar(TMVar,putTMVar,takeTMVar)+-- import Control.Concurrent.STM.TChan(TChan,writeTChan)+import Control.Concurrent.MVar(MVar,newEmptyMVar,takeMVar,tryTakeMVar,putMVar)+import qualified Control.Concurrent.STM as S (STM,orElse,retry,catchSTM,atomically,check,always,alwaysSucceeds)+import Control.Concurrent.STM.TVar(TVar,newTVarIO,readTVar,writeTVar)+import GHC.Conc(unsafeIOToSTM)+import Data.IORef(newIORef,readIORef,writeIORef)+ +--------------------------------------------------------------------------------+++instance MonadAdvSTM AdvSTM where+ onCommit ioaction = do+ commitVar <- AdvSTM $ asks commitTVar+ commitFun <- liftAdv $ readTVar commitVar+ liftAdv $ writeTVar commitVar $ commitFun . (ioaction >>)++ unsafeOnRetry ioaction = do+ retryVar <- AdvSTM $ asks retryMVar+ liftAdv . unsafeIOToSTM $ do+ may'retryFun <- tryTakeMVar retryVar+ let retryFun = maybe (ioaction >>) (. (ioaction >>)) may'retryFun+ putMVar retryVar $! retryFun++ orElse = mplus++ retry = liftAdv S.retry ++ check = liftAdv . S.check+ ++ alwaysSucceeds inv = unlift inv >>= liftAdv . S.alwaysSucceeds ++ always inv = unlift inv >>= liftAdv . S.always ++ runAtomic = atomically++ catchSTM action handler = do+ action' <- unlift action+ handler' <- unlift1 handler+ let handler'' = \e -> case fromException e of+ Nothing -> throw e+ Just e' -> handler' e'+ liftAdv $ S.catchSTM action' (handler'') ++ liftAdv = AdvSTM . lift ++-- | Adds the IO action to the retry queue and then retries the transaction+unsafeRetryWith :: (Monad m, MonadAdvSTM m) => IO () -> m b+unsafeRetryWith io = unsafeOnRetry io >> retry+ ++ +--------------------------------------------------------------------------------++++atomically :: AdvSTM a -> IO a+atomically (AdvSTM action) = do+ let debugging = False -- Switching this to True occasionally causes deadlocks (b/c unsafeIO)!+ debugTVar <- newTVarIO debugging+ tid <- myThreadId -- ThreadId for controlling TVar access+ debug "***************************************************************" 0 debugging+ debug (show (tid,"Starting transaction...")) 1000000 debugging+ -- Building the Reader monad environment+ commitVar <- newTVarIO id -- IO actions to be run if the transaction commits+ retryVar <- newEmptyMVar -- full if there's something todo before retrying+ commitListeners <- newTVarIO [] + let env = Env { commitTVar = commitVar :: TVar (IO () -> IO ())+ , retryMVar = retryVar :: MVar (IO () -> IO ())+ , transThreadId = tid :: ThreadId+ , listeners = commitListeners :: TVar [(TMVar (),TVarValue)] + , debugModeVar = debugTVar+ } + -- Setting up communication for the retry-helper thread: + retryChanVar <- newIORef (Nothing :: Maybe (Chan (Maybe (IO ())))) + retryEndVar <- newEmptyMVar -- Termination signal for the retry-helper thread++ let check'retry = do + unsafeIOToSTM $ do+ may'todo <- tryTakeMVar retryVar+ case may'todo of+ Nothing -> return ()+ Just retryFun -> do+ may'chan <- readIORef retryChanVar+ chan <- case may'chan of+ Nothing -> do+ chan <- newChan+ writeIORef retryChanVar (Just chan)+ spawn'retry'thread (readChan chan) (putMVar retryEndVar ())+ return chan+ Just chan -> return chan+ writeChan chan $ Just (retryFun (return()))+ debugSTM (show (tid,"Calling retry now...")) 0 debugging+ debugSTM (show (tid,"*********************")) 1000000 debugging+ S.retry++ let wait'retry'finished = do+ may'chan <- readIORef retryChanVar+ case may'chan of+ Nothing -> return ()+ Just chan -> do+ -- Write an "EOF" on the channel and block until the helper thread is done:+ writeChan chan Nothing+ takeMVar retryEndVar++ let wrappedAction = runReaderT action env `S.orElse` (check'retry)++ result <- S.atomically $ do + debugSTM (show (tid,"wrappedAction: Running S.STM action...")) 0 debugging+ result <- wrappedAction+ debugSTM (show (tid,"wrappedAction: Finished S.STM action...")) 0 debugging+ ls <- readTVar commitListeners + -- Notify the TPVars that we're entering onCommit mode:+ debugSTM (show (tid,"wrappedAction: Notifying TVars that we're about to run onCommit...")) 0 debugging+ mapM_ (\(l,_) -> + takeTMVar l -- tell the TVar that we're going into onCommit mode+ ) ls+ return result++ let rollbackOnCommit = do+ debug "rollbackOnCommit: rolling back modified TVar values!" 0 debugging+ wait'retry'finished+ S.atomically $ do+ ls <- readTVar commitListeners+ mapM_ (\(l,TVarValue (oldValTVar,oldVal)) -> do + writeTVar oldValTVar oldVal -- smthg went wrong, restore the old value + putTMVar l () -- ...and unblock the TVar+ ) ls+ + -- Now try to run the onCommit IO actions:+ commitFun <- S.atomically $ readTVar commitVar+ commitFun (return ()) `catch` (\(e::SomeException) -> rollbackOnCommit >> throw e)++ -- Everything's ok, so notify and unblock the TVars:+ debug (show (tid,"*********************")) 1000000 debugging+ debug (show (tid,"Notifying TPVars that we're done:")) 0 debugging+ S.atomically $ do + ls <- readTVar commitListeners + mapM_ (\(l,_) -> do + putTMVar l () + ) ls+ wait'retry'finished+ debug (show (tid,"Transaction done; retry thread finished")) 1000000 debugging+ debug "************************************************************" 0 debugging+ return result++ where+ -- Helper thread for the retry IO-actions+ spawn'retry'thread :: IO (Maybe (IO ())) -> IO () -> IO ThreadId+ spawn'retry'thread nextJob atEndAction = forkIO $ loop+ where loop = do+ may'job <- nextJob+ case may'job of+ Nothing -> atEndAction+ Just job -> do + res <- try job + case res of+ Left (e::SomeException) -> throw e+ Right _ -> loop+ +--------------------------------------------------------------------------------++unsafeIOToAdvSTM :: IO a -> AdvSTM a+unsafeIOToAdvSTM = liftAdv . unsafeIOToSTM++--------------------------------------------------------------------------------++-- | Switches the debug mode on or off+-- /WARNING:/ Can lead to deadlocks! +debugMode :: Bool -> AdvSTM ()+debugMode switch = do+ debVar <- AdvSTM $ asks debugModeVar + liftAdv $ writeTVar debVar switch++-- | Uses unsafeIOToSTM to output the Thread Id and a message and delays +-- for a given number of ms+-- /WARNING:/ Can lead to deadlocks! +debugAdvSTM :: String -> Int -> AdvSTM ()+debugAdvSTM msg delay = do + debVar <- AdvSTM $ asks debugModeVar+ debugging <- liftAdv $ readTVar debVar+ tid <- AdvSTM $ asks transThreadId + liftAdv $ debugSTM (show (tid,msg)) delay debugging ++--------------------------------------------------------------------------------+-- The following functions are not exported as they aren't needed by +-- the enduser ++runWith :: Env -> AdvSTM t -> S.STM t+runWith env (AdvSTM action) = runReaderT action env+ + +unlifter :: AdvSTM (AdvSTM a -> S.STM a)+unlifter = do+ env <- AdvSTM ask+ return (runWith env)+ +unlift :: AdvSTM a -> AdvSTM (S.STM a)+unlift f = do+ u <- unlifter+ return (u f)+ +unlift1 :: (t -> AdvSTM a) -> AdvSTM (t -> S.STM a)+unlift1 f = do+ u <- unlifter+ return (\x -> u (f x))+ +-- WARNING: Can lead to deadlocks! +debugSTM :: String -> Int -> Bool -> S.STM ()+debugSTM msg delay debugging = + when (debugging) $ unsafeIOToSTM $ putStrLn msg >> threadDelay delay++-- WARNING: Can lead to deadlocks! +debug :: String -> Int -> Bool -> IO ()+debug msg delay debugging = when (debugging) $ putStrLn msg >> threadDelay delay+++
+ Control/Concurrent/AdvSTM/TArray.hs view
@@ -0,0 +1,46 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.AdvSTM.TArray+-- Copyright : (c) Peter Robinson 2009, The University of Glasgow 2004+-- License : BSD-style (see the file LICENSE)+-- +-- Maintainer : Peter Robinson <thaldyron@gmail.com>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- Corresponds to "Control.Concurrent.STM.TArray" +--+-----------------------------------------------------------------------------++++module Control.Concurrent.AdvSTM.TArray( TArray )+where+import Control.Concurrent.AdvSTM+import Control.Concurrent.AdvSTM.TVar+++import Control.Monad (replicateM)+import Data.Array (Array, bounds)+import Data.Array.Base (listArray, arrEleBottom, unsafeAt, MArray(..), IArray(numElements))+import Data.Ix (rangeSize)+++-- |TArray is a transactional array, supporting the usual 'MArray'+-- interface for mutable arrays.+++newtype TArray i e = TArray (Array i (TVar e))++instance MArray TArray e AdvSTM where+ getBounds (TArray a) = return (bounds a)+ newArray b e = do+ a <- replicateM (rangeSize b) (newTVar e)+ return $ TArray (listArray b a)+ newArray_ b = do+ a <- replicateM (rangeSize b) (newTVar arrEleBottom)+ return $ TArray (listArray b a)+ unsafeRead (TArray a) i = readTVar $ unsafeAt a i+ unsafeWrite (TArray a) i e = writeTVar (unsafeAt a i) e+ getNumElements (TArray a) = return (numElements a)+
+ Control/Concurrent/AdvSTM/TChan.hs view
@@ -0,0 +1,97 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.AdvSTM.TChan+-- Copyright : (c) Peter Robinson 2009, The University of Glasgow 2004+-- License : BSD-style (see the file LICENSE)+-- +-- Maintainer : Peter Robinson <robinson@ecs.tuwien.ac.at>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- Corresponds to "Control.Concurrent.STM.TChan"+--+-----------------------------------------------------------------------------+module Control.Concurrent.AdvSTM.TChan( TChan + , newTChan+ , newTChanIO+ , readTChan+ , writeTChan+ , dupTChan+ , unGetTChan+ , isEmptyTChan+ )+where+import Control.Concurrent.AdvSTM(AdvSTM,retry)+import Control.Concurrent.AdvSTM.TVar(TVar,readTVar,writeTVar,newTVar,newTVarIO)++-- | 'TChan' is an abstract type representing an unbounded FIFO channel.+data TChan a = TChan (TVar (TVarList a)) (TVar (TVarList a))++type TVarList a = TVar (TList a)+data TList a = TNil | TCons a (TVarList a)++-- |Build and returns a new instance of 'TChan'+newTChan :: AdvSTM (TChan a)+newTChan = do+ hole <- newTVar TNil+ read <- newTVar hole+ write <- newTVar hole+ return (TChan read write)++-- |@IO@ version of 'newTChan'. This is useful for creating top-level+-- 'TChan's using 'System.IO.Unsafe.unsafePerformIO', because using+-- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't+-- possible.+newTChanIO :: IO (TChan a)+newTChanIO = do+ hole <- newTVarIO TNil+ read <- newTVarIO hole+ write <- newTVarIO hole+ return (TChan read write)++-- |Write a value to a 'TChan'.+writeTChan :: TChan a -> a -> AdvSTM ()+writeTChan (TChan _read write) a = do+ listend <- readTVar write -- listend == TVar pointing to TNil+ new_listend <- newTVar TNil+ writeTVar listend (TCons a new_listend)+ writeTVar write new_listend++-- |Read the next value from the 'TChan'.+readTChan :: TChan a -> AdvSTM a+readTChan (TChan read _write) = do+ listhead <- readTVar read+ head <- readTVar listhead+ case head of+ TNil -> retry+ TCons a tail -> do+ writeTVar read tail+ return a++-- |Duplicate a 'TChan': the duplicate channel begins empty, but data written to+-- either channel from then on will be available from both. Hence this creates+-- a kind of broadcast channel, where data written by anyone is seen by+-- everyone else.+dupTChan :: TChan a -> AdvSTM (TChan a)+dupTChan (TChan read write) = do+ hole <- readTVar write + new_read <- newTVar hole+ return (TChan new_read write)++-- |Put a data item back onto a channel, where it will be the next item read.+unGetTChan :: TChan a -> a -> AdvSTM ()+unGetTChan (TChan read _write) a = do+ listhead <- readTVar read+ newhead <- newTVar (TCons a listhead)+ writeTVar read newhead++-- |Returns 'True' if the supplied 'TChan' is empty.+isEmptyTChan :: TChan a -> AdvSTM Bool+isEmptyTChan (TChan read write) = do+ listhead <- readTVar read+ head <- readTVar listhead+ case head of+ TNil -> return True+ TCons _ _ -> return False++
+ Control/Concurrent/AdvSTM/TMVar.hs view
@@ -0,0 +1,143 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.AdvSTM.TMVar+-- Copyright : (c) Peter Robinson 2009, (c) The University of Glasgow 2004+-- License : BSD3+-- +-- Maintainer : Peter Robinson <robinson@ecs.tuwien.ac.at>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- TMVar: Transactional MVars, for use in the AdvAdvSTM monad+--+-- Corresponds to "Control.Concurrent.STM.TMVar" +--+-----------------------------------------------------------------------------++module Control.Concurrent.AdvSTM.TMVar (+ -- * TVars+ TMVar,+ newTMVar,+ newEmptyTMVar,+ newTMVarIO,+ newEmptyTMVarIO,+ takeTMVar,+ putTMVar,+ readTMVar, + swapTMVar,+ tryTakeTMVar,+ tryPutTMVar,+ isEmptyTMVar+ ) where++import Control.Concurrent.AdvSTM.TVar(TVar,readTVar,writeTVar,newTVar,newTVarIO)+import Control.Concurrent.AdvSTM(AdvSTM,retry)++newtype TMVar a = TMVar (TVar (Maybe a))+{- ^+A 'TMVar' is a synchronising variable, used+for communication between concurrent threads. It can be thought of+as a box, which may be empty or full.+-}++-- |Create a 'TMVar' which contains the supplied value.+newTMVar :: a -> AdvSTM (TMVar a)+newTMVar a = do+ t <- newTVar (Just a)+ return (TMVar t)++-- |@IO@ version of 'newTMVar'. This is useful for creating top-level+-- 'TMVar's using 'System.IO.Unsafe.unsafePerformIO', because using+-- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't+-- possible.+newTMVarIO :: a -> IO (TMVar a)+newTMVarIO a = do+ t <- newTVarIO (Just a)+ return (TMVar t)++-- |Create a 'TMVar' which is initially empty.+newEmptyTMVar :: AdvSTM (TMVar a)+newEmptyTMVar = do+ t <- newTVar Nothing+ return (TMVar t)++-- |@IO@ version of 'newEmptyTMVar'. This is useful for creating top-level+-- 'TMVar's using 'System.IO.Unsafe.unsafePerformIO', because using+-- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't+-- possible.+newEmptyTMVarIO :: IO (TMVar a)+newEmptyTMVarIO = do+ t <- newTVarIO Nothing+ return (TMVar t)++-- |Return the contents of the 'TMVar'. If the 'TMVar' is currently+-- empty, the transaction will 'retry'. After a 'takeTMVar', +-- the 'TMVar' is left empty.+takeTMVar :: TMVar a -> AdvSTM a+takeTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> retry+ Just a -> do writeTVar t Nothing; return a++-- | A version of 'takeTMVar' that does not 'retry'. The 'tryTakeTMVar'+-- function returns 'Nothing' if the 'TMVar' was empty, or @'Just' a@ if+-- the 'TMVar' was full with contents @a@. After 'tryTakeTMVar', the+-- 'TMVar' is left empty.+tryTakeTMVar :: TMVar a -> AdvSTM (Maybe a)+tryTakeTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> return Nothing+ Just a -> do writeTVar t Nothing; return (Just a)++-- |Put a value into a 'TMVar'. If the 'TMVar' is currently full,+-- 'putTMVar' will 'retry'.+putTMVar :: TMVar a -> a -> AdvSTM ()+putTMVar (TMVar t) a = do+ m <- readTVar t+ case m of+ Nothing -> do writeTVar t (Just a); return ()+ Just _ -> retry++-- | A version of 'putTMVar' that does not 'retry'. The 'tryPutTMVar'+-- function attempts to put the value @a@ into the 'TMVar', returning+-- 'True' if it was successful, or 'False' otherwise.+tryPutTMVar :: TMVar a -> a -> AdvSTM Bool+tryPutTMVar (TMVar t) a = do+ m <- readTVar t+ case m of+ Nothing -> do writeTVar t (Just a); return True+ Just _ -> return False++{-|+ This is a combination of 'takeTMVar' and 'putTMVar'; ie. it takes the value+ from the 'TMVar', puts it back, and also returns it.+-}+readTMVar :: TMVar a -> AdvSTM a+readTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> retry+ Just a -> return a++-- |Swap the contents of a 'TMVar' for a new value.+swapTMVar :: TMVar a -> a -> AdvSTM a+swapTMVar (TMVar t) new = do+ m <- readTVar t+ case m of+ Nothing -> retry+ Just old -> do writeTVar t (Just new); return old++-- |Check whether a given 'TMVar' is empty.+--+-- Notice that the boolean value returned is just a snapshot of+-- the state of the 'TMVar'. By the time you get to react on its result,+-- the 'TMVar' may have been filled (or emptied) - so be extremely+-- careful when using this operation. Use 'tryTakeTMVar' instead if possible.+isEmptyTMVar :: TMVar a -> AdvSTM Bool+isEmptyTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> return True+ Just _ -> return False
+ Control/Concurrent/AdvSTM/TVar.hs view
@@ -0,0 +1,112 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.AdvSTM.TVar+-- Copyright : (c) Peter Robinson 2009+-- License : BSD-style (see the file LICENSE)+-- +-- Maintainer : Peter Robinson <robinson@ecs.tuwien.ac.at>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- +--+-----------------------------------------------------------------------------++module Control.Concurrent.AdvSTM.TVar( -- * TVars+ TVar + , newTVar+ , newTVarIO+ , readTVar+ , writeTVar+ )+where+import qualified Control.Concurrent.STM.TVar as OldTVar+import qualified Control.Concurrent.STM as STM+import qualified Control.Concurrent.STM.TMVar as OldTMVar+import qualified Control.Concurrent.STM.TChan as OldTChan+import Control.Exception(throw,Deadlock(Deadlock))+import Control.Concurrent.AdvSTM(liftAdv,orElse,retry)+import Control.Monad.AdvSTM.Def(AdvSTM(AdvSTM),transThreadId,listeners,TVarValue(TVarValue))+import Control.Concurrent(ThreadId)+import Control.Monad(liftM,ap,unless)+import Control.Monad.Reader(asks)+import Data.Maybe(isJust,Maybe,fromJust)+import qualified Data.Set as S++--------------------------------------------------------------------------------++data TVar a = TVar + { valueTVar :: OldTVar.TVar a + , onCommitLock :: OldTMVar.TMVar () + , currentTid :: OldTVar.TVar (Maybe ThreadId)+ }++--------------------------------------------------------------------------------++-- | See 'OldTVar.newTVar'+newTVar :: a -> AdvSTM (TVar a)+newTVar a = TVar `liftM` (liftAdv $ OldTVar.newTVar a) + `ap` (liftAdv $ OldTMVar.newTMVar ()) + `ap` (liftAdv $ OldTVar.newTVar Nothing)++-- | See 'OldTVar.newTVarIO'+newTVarIO :: a -> IO (TVar a)+newTVarIO a = TVar `liftM` OldTVar.newTVarIO a+ `ap` OldTMVar.newTMVarIO ()+ `ap` OldTVar.newTVarIO Nothing++--------------------------------------------------------------------------------++-- | Writes a value to a TVar. Blocks until the onCommit IO-action(s) are+-- complete. See 'onCommit' for details.+writeTVar :: TVar a -> a -> AdvSTM ()+writeTVar tvar a = do + commitLock <- liftAdv $ OldTMVar.tryTakeTMVar (onCommitLock tvar)+ -- Get ThreadID of current transaction:+ curTid <- AdvSTM $ asks transThreadId + storedTid <- liftAdv $ OldTVar.readTVar (currentTid tvar) + case commitLock of+ Nothing -> do+ if isJust storedTid && fromJust storedTid == curTid+ then throw Deadlock -- No transaction during onCommit-phase!+ else retry+ Just _ -> do+ unless (isJust storedTid && (fromJust storedTid == curTid)) $ do+ -- First write access, update the ThreadId:+ liftAdv $ OldTVar.writeTVar (currentTid tvar) $ Just curTid+ -- Add this TVar to the onCommit-listener list:+ lsTVar <- AdvSTM $ asks listeners+ ls <- liftAdv $ OldTVar.readTVar lsTVar+ -- Remember the old value for rollback:+ oldval <- liftAdv $ OldTVar.readTVar (valueTVar tvar)+ liftAdv $ OldTVar.writeTVar lsTVar $+ (onCommitLock tvar,TVarValue (valueTVar tvar,oldval)) : ls+ + liftAdv $ OldTVar.writeTVar (valueTVar tvar) a + liftAdv $ OldTMVar.putTMVar (onCommitLock tvar) ()+++--------------------------------------------------------------------------------++-- | Reads a value from a TVar. Blocks until the IO onCommit action(s) of +-- the corresponding transaction are complete.+-- See 'onCommit' for a more detailed description of this behaviour.+readTVar :: TVar a -> AdvSTM a+readTVar tvar = do + commitLock <- liftAdv $ OldTMVar.tryTakeTMVar (onCommitLock tvar)+ case commitLock of+ Nothing -> do+ storedTid <- liftAdv $ OldTVar.readTVar (currentTid tvar) + curTid <- AdvSTM $ asks transThreadId + if isJust storedTid && fromJust storedTid == curTid+ then throw Deadlock+ else retry+ Just _ -> do+ result <- liftAdv $ OldTVar.readTVar $ valueTVar tvar+ liftAdv $ OldTMVar.putTMVar (onCommitLock tvar) ()+ return result++--------------------------------------------------------------------------------+++
+ Control/Monad/AdvSTM.hs view
@@ -0,0 +1,82 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Monad.AdvSTM+-- Copyright : (c) HaskellWiki 2006-2007, Peter Robinson 2008+-- License : BSD3+-- +-- Maintainer : Peter Robinson <robinson@ecs.tuwien.ac.at>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- Provides the type class MonadAdvSTM and the AdvSTM monad.+-- Parts of this implementation were taken from the HaskellWiki Page of+-- MonadAdvSTM (see package description).+-----------------------------------------------------------------------------+ ++module Control.Monad.AdvSTM( MonadAdvSTM(..)+ , AdvSTM+ )+where+import Control.Monad.AdvSTM.Def+import Control.Monad.AdvSTM.Class++import Control.Exception(Exception)+import qualified Control.Concurrent.STM as S+import Control.Concurrent.STM.TVar(TVar) +import Control.Concurrent.STM.TMVar(TMVar) +import Control.Concurrent(MVar,ThreadId)+import Control.Monad(Monad,MonadPlus)+import Control.Monad.Reader(MonadReader,ReaderT)++{-+-- | A type class for extended-STM monads. See 'AdvSTM' for a concrete+-- instantiation+class Monad m => MonadAdvSTM m where+ -- | Takes an IO action that will be executed if the STM transaction commits. + -- /Beware:/ If the IO action throws an exception, the effects of the STM + -- action will still be visible.+ -- + -- /Atomicity and Race Conditions:/ + --+ -- * 'TVar', 'TChan', 'TArray' or 'TMVar': There is a race hole between the TM transaction + -- and the execution of the IO action, i.e., other+ -- transactions might commit before the IO action is run. + --+ -- * 'TTVar': When a 'TTVar' was modified in the TM action, it cannot be accessed by any + -- other thread before the onCommit IO action was run. Forking+ -- another thread inside of the onCommit IO action breaks this atomicity+ -- guarantee.+ onCommit :: IO a -> m ()++ -- | Adds the IO action to the retry-queue. If the transaction retries,+ -- a new thread is forked that runs the retry actions. When this thread is+ -- done, the transaction retries.+ onRetry :: IO a -> m ()++ -- | See 'S.orElse'+ orElse :: m a -> m a -> m a++ -- | See 'S.retry'+ retry :: m a++ -- | Runs a transaction atomically in the 'IO' monad. + runAtomic :: m a -> IO a++ -- | See 'S.catchSTM'+ catchSTM :: Exception e => m a -> (e -> m a) -> m a++ -- | Lifts STM actions to 'MonadAdvSTM'.+ liftAdv :: S.STM a -> m a+-} +--------------------------------------------------------------------------------+{-+-- | Drop-in replacement for the STM monad+newtype AdvSTM a = AdvSTM (ReaderT Env S.STM a)+ deriving ( Functor+ , Monad+ , MonadPlus+-- , MonadReader Env+ )++-}
+ Control/Monad/AdvSTM/Class.hs view
@@ -0,0 +1,85 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Monad.AdvSTM.Class+-- Copyright : (c) HaskellWiki 2006-2007, Peter Robinson 2008+-- License : BSD3+-- +-- Maintainer : Peter Robinson <robinson@ecs.tuwien.ac.at>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- Provides the type class MonadAdvSTM +-- Parts of this implementation were taken from the HaskellWiki Page of+-- MonadAdvSTM (see package description).+-----------------------------------------------------------------------------++module Control.Monad.AdvSTM.Class( MonadAdvSTM(..))+where++import Control.Exception(Exception,Deadlock)+import qualified Control.Concurrent.STM as S+import Control.Monad(Monad)+import Control.Monad.AdvSTM.Def(AdvSTM)++++-- | A type class for extended-STM monads. For a concrete instantiation see+-- 'AdvSTM'+class Monad m => MonadAdvSTM m where++ -- | Takes an IO action that will be executed iff the transaction commits. + -- + -- * When a TVar was modified in a transaction and this transaction commits,+ -- this update remains invisible to other threads until the corresponding + -- onCommit action was run. + --+ -- * If the onCommit action throws an exception, the original value of + -- the TVars will be restored.+ --+ -- * Accessing a modified TVar within the onCommit action will cause a+ -- 'Deadlock' exception to be thrown. + --+ -- As a general rule, 'onCommit' should + -- only be used for \"real\" (i.e. without atomic blocks) IO actions and is certainly+ -- not the right place to fiddle with TVars. For example, if you wanted to+ -- write a TVar value to a file on commit, you could write:+ -- + -- > tvar <- newTVarIO "bla"+ -- > atomically $ do + -- > x <- readTVar tvar + -- > onCommit (writeFile "myfile" x)+ -- + --+ onCommit :: IO () -> m ()++ -- | Adds an IO action to the retry job-queue. If the transaction retries,+ -- a new helper thread is forked that runs the retry actions, and, after the helper + -- thread is done, the transaction retries.+ -- /Warning:/ Uses unsafeIOToSTM to fork a helper thread that runs the retry + -- actions!+ unsafeOnRetry :: IO () -> m ()++ -- | See 'S.orElse'+ orElse :: m a -> m a -> m a++ -- | See 'S.retry'. Skips any IO actions added by unsafeOnRetry.+ retry :: m a++ -- | See 'S.check'+ check :: Bool -> m ()++ -- | See 'S.alwaysSucceeds'+ alwaysSucceeds :: m a -> m ()++ -- | See 'S.always'+ always :: m Bool -> m ()++ -- | Runs a transaction atomically in the 'IO' monad. + runAtomic :: m a -> IO a++ -- | See 'S.catchSTM'+ catchSTM :: Exception e => m a -> (e -> m a) -> m a++ -- | Lifts STM actions to 'MonadAdvSTM'.+ liftAdv :: S.STM a -> m a+
+ Control/Monad/AdvSTM/Def.hs view
@@ -0,0 +1,47 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Monad.AdvSTM.Def+-- Copyright : (c) HaskellWiki 2006-2007, Peter Robinson 2008+-- License : BSD3+-- +-- Maintainer : Peter Robinson <robinson@ecs.tuwien.ac.at>+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+--+-- This is an internal module.+-----------------------------------------------------------------------------+ +module Control.Monad.AdvSTM.Def(AdvSTM(..),Env(..),TVarValue(..))+where++import qualified Control.Concurrent.STM as S+import Control.Concurrent.STM.TVar(TVar) +import Control.Concurrent.STM.TMVar(TMVar) +import Control.Concurrent(MVar,ThreadId)+import Control.Monad(Monad,MonadPlus)+import Control.Monad.Reader(ReaderT)++-- | Drop-in replacement for the STM monad+newtype AdvSTM a = AdvSTM (ReaderT Env S.STM a)+ deriving ( Functor+ , Monad+ , MonadPlus+ )++++-- | The environment used for the Reader Monad+data Env = Env { commitTVar :: TVar (IO () -> IO ()) -- the commit action(s)+ , retryMVar :: MVar (IO () -> IO ()) -- the retry action(s)+ , transThreadId :: ThreadId -- the current ThreadId+ , listeners :: TVar [(TMVar (),TVarValue)] -- ,TVar (Maybe ThreadId),TChan (Maybe ThreadId))]+ -- Contains communication facilities for modified TVars: + -- [(tVar-Lock,Old-tVar-Value,Maybe (current ThreadId)+ -- ,death-notice channel)]+ , debugModeVar :: TVar Bool+-- , isInRetryMode :: TVar Bool+ }++data TVarValue = forall a. TVarValue ((TVar a),a)+
+ LICENSE view
@@ -0,0 +1,90 @@+This library is derived from code from several sources: ++ * Code from the STM library which is distributable under + the Glasgow Haskell Compiler license (see below)++ * Code from the Haskell Wiki which is licensed under the+ simple permissive license (see below)++The full text of these licenses is reproduced below. All of the+licenses are BSD-style or compatible.++--------------------------------------------------------------------------------++Copyright (c) 2009, Peter Robinson+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright notice,+ * this list of conditions and the following disclaimer.+ * Redistributions in binary form must reproduce the above copyright notice,+ * this list of conditions and the following disclaimer in the documentation+ * and/or other materials provided with the distribution.+ * The names of its contributors+ * may not be used to endorse or promote products derived from this software+ * without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.++--------------------------------------------------------------------------------++The Glasgow Haskell Compiler License++Copyright 2004, The University Court of the University of Glasgow.+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++- Redistributions of source code must retain the above copyright notice,+this list of conditions and the following disclaimer.++- Redistributions in binary form must reproduce the above copyright notice,+this list of conditions and the following disclaimer in the documentation+and/or other materials provided with the distribution.++- Neither name of the University nor the names of its contributors may be+used to endorse or promote products derived from this software without+specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF+GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE+UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH+DAMAGE.+++--------------------------------------------------------------------------------++Permission is hereby granted, free of charge, to any person obtaining this work+(the "Work"), to deal in the Work without restriction, including without+limitation the rights to use, copy, modify, merge, publish, distribute,+sublicense, and/or sell copies of the Work, and to permit persons to whom the+Work is furnished to do so.++THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS IN THE WORK.++
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ stm-io-hooks.cabal view
@@ -0,0 +1,49 @@+Name: stm-io-hooks+Synopsis: An STM monad with IO hooks+Description:+ This library provides an STM monad with commit and retry IO hooks. + A retry-action is run (once) if the transaction retries, while commit-actions are + executed iff the transaction commits. The library also gives some atomicity+ guarantees for commit-actions, see documentation for details. + .+ Note: Part of this library uses code from+ the Haskell Wiki (see <http://haskell.org/haskellwiki/?title=New_monads/MonadAdvSTM>).+Category: Concurrency+Author: Peter Robinson 2009, Chris Kuklewicz 2006+Maintainer: Peter Robinson <robinson@ecs.tuwien.ac.at>+License: BSD3+License-file: LICENSE+Homepage: http://darcs.monoid.at/stm-io-hooks+Version: 0.0.1++Build-type: Simple +Cabal-Version: >= 1.2.3++library+ Exposed-Modules: Control.Monad.AdvSTM+ ,Control.Concurrent.AdvSTM+ ,Control.Concurrent.AdvSTM.TMVar + ,Control.Concurrent.AdvSTM.TVar+ ,Control.Concurrent.AdvSTM.TArray+ ,Control.Concurrent.AdvSTM.TChan++ Other-Modules: Control.Monad.AdvSTM.Def+ ,Control.Monad.AdvSTM.Class+ -- TODO: add Sample module!+ + build-depends: base >= 4.0.0.0+ ,stm >= 2.1.1.2+ ,array+ ,containers >= 0.2.0.0+ ,mtl >= 1.1.0.2++ extensions: MultiParamTypeClasses+ ,FunctionalDependencies+ ,FlexibleInstances+ ,GeneralizedNewtypeDeriving+ ,ScopedTypeVariables+ ,DeriveDataTypeable+ ,RankNTypes+ ,ExistentialQuantification+ +