SafeSemaphore 0.7.0 → 0.9.0
raw patch · 11 files changed
+1411/−431 lines, 11 filesdep +stmdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: stm
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Control.Concurrent.MSemN2: data MSemN i
+ Control.Concurrent.MSemN2: instance Eq (MSemN i)
+ Control.Concurrent.MSemN2: instance Eq i => Eq (MS i)
+ Control.Concurrent.MSemN2: instance Typeable1 MS
+ Control.Concurrent.MSemN2: instance Typeable1 MSemN
+ Control.Concurrent.MSemN2: new :: Integral i => i -> IO (MSemN i)
+ Control.Concurrent.MSemN2: peekAvail :: Integral i => MSemN i -> IO i
+ Control.Concurrent.MSemN2: signal :: Integral i => MSemN i -> i -> IO ()
+ Control.Concurrent.MSemN2: signalF :: Integral i => MSemN i -> (i -> (i, b)) -> IO (i, b)
+ Control.Concurrent.MSemN2: wait :: Integral i => MSemN i -> i -> IO ()
+ Control.Concurrent.MSemN2: waitF :: Integral i => MSemN i -> (i -> (i, b)) -> IO (i, b)
+ Control.Concurrent.MSemN2: with :: Integral i => MSemN i -> i -> IO a -> IO a
+ Control.Concurrent.MSemN2: withF :: Integral i => MSemN i -> (i -> (i, b)) -> ((i, b) -> IO a) -> IO a
+ Control.Concurrent.SSem: data SSem
+ Control.Concurrent.SSem: getValue :: SSem -> IO Int
+ Control.Concurrent.SSem: new :: Int -> IO SSem
+ Control.Concurrent.SSem: signal :: SSem -> IO ()
+ Control.Concurrent.SSem: signalN :: SSem -> Int -> IO ()
+ Control.Concurrent.SSem: tryWait :: SSem -> IO (Maybe Int)
+ Control.Concurrent.SSem: tryWaitN :: SSem -> Int -> IO (Maybe Int)
+ Control.Concurrent.SSem: wait :: SSem -> IO ()
+ Control.Concurrent.SSem: waitN :: SSem -> Int -> IO ()
+ Control.Concurrent.SSem: withSem :: SSem -> IO a -> IO a
+ Control.Concurrent.SSem: withSemN :: SSem -> Int -> IO a -> IO a
+ Control.Concurrent.STM.SSem: data SSem
+ Control.Concurrent.STM.SSem: getValue :: SSem -> STM Int
+ Control.Concurrent.STM.SSem: new :: Int -> STM SSem
+ Control.Concurrent.STM.SSem: signal :: SSem -> STM ()
+ Control.Concurrent.STM.SSem: signalN :: SSem -> Int -> STM ()
+ Control.Concurrent.STM.SSem: tryWait :: SSem -> STM (Maybe Int)
+ Control.Concurrent.STM.SSem: tryWaitN :: SSem -> Int -> STM (Maybe Int)
+ Control.Concurrent.STM.SSem: wait :: SSem -> STM ()
+ Control.Concurrent.STM.SSem: waitN :: SSem -> Int -> STM ()
- Control.Concurrent.MSemN: peekAvail :: Integral i => (MSemN i) -> IO i
+ Control.Concurrent.MSemN: peekAvail :: Integral i => MSemN i -> IO i
- Control.Concurrent.MSemN: signal :: Integral i => (MSemN i) -> i -> IO ()
+ Control.Concurrent.MSemN: signal :: Integral i => MSemN i -> i -> IO ()
- Control.Concurrent.MSemN: signalF :: Integral i => (MSemN i) -> (i -> (i, b)) -> IO (i, b)
+ Control.Concurrent.MSemN: signalF :: Integral i => MSemN i -> (i -> (i, b)) -> IO (i, b)
- Control.Concurrent.MSemN: wait :: Integral i => (MSemN i) -> i -> IO ()
+ Control.Concurrent.MSemN: wait :: Integral i => MSemN i -> i -> IO ()
- Control.Concurrent.MSemN: waitF :: Integral i => (MSemN i) -> (i -> (i, b)) -> IO (i, b)
+ Control.Concurrent.MSemN: waitF :: Integral i => MSemN i -> (i -> (i, b)) -> IO (i, b)
- Control.Concurrent.MSemN: with :: Integral i => (MSemN i) -> i -> IO a -> IO a
+ Control.Concurrent.MSemN: with :: Integral i => MSemN i -> i -> IO a -> IO a
- Control.Concurrent.MSemN: withF :: Integral i => (MSemN i) -> (i -> (i, b)) -> ((i, b) -> IO a) -> IO a
+ Control.Concurrent.MSemN: withF :: Integral i => MSemN i -> (i -> (i, b)) -> ((i, b) -> IO a) -> IO a
Files
- SafeSemaphore.cabal +10/−4
- src/Control/Concurrent/FairRWLock.hs +86/−72
- src/Control/Concurrent/MSem.hs +0/−132
- src/Control/Concurrent/MSem.lhs +306/−0
- src/Control/Concurrent/MSemN.hs +0/−223
- src/Control/Concurrent/MSemN.lhs +319/−0
- src/Control/Concurrent/MSemN2.hs +262/−0
- src/Control/Concurrent/SSem.hs +77/−0
- src/Control/Concurrent/STM/SSem.hs +75/−0
- src/Control/Concurrent/STM/SSemInternals.hs +27/−0
- tests/TestKillSem.hs +249/−0
SafeSemaphore.cabal view
@@ -1,8 +1,10 @@ Name: SafeSemaphore-Version: 0.7.0+Version: 0.9.0 Synopsis: Much safer replacement for QSemN, QSem, and SampleVar-Description: This provides a much safer semaphore than the QSem in base. Performance has not been compared. In the source is a tests/TestKillSem.hs executable (run by cabal test) that shows the problem with QSem.-Homepage: http://hackage.haskell.org/package/SafeSemaphore+Description: This provides a much safer semaphore than the QSem, QSemN, SampleVar in base.+ Those base modules are not exception safe and can be broken by killThread.+ See <https://github.com/ChrisKuklewicz/SafeSemaphore> for more details.+Homepage: https://github.com/ChrisKuklewicz/SafeSemaphore License: BSD3 License-file: LICENSE Author: Chris Kuklewicz <haskell@list.mightyreason.com>@@ -16,9 +18,13 @@ ghc-options: -Wall -O2 -funbox-strict-fields Exposed-modules: Control.Concurrent.MSem Control.Concurrent.MSemN+ Control.Concurrent.MSemN2 Control.Concurrent.MSampleVar Control.Concurrent.FairRWLock- Build-depends: base < 5, containers+ Control.Concurrent.SSem+ Control.Concurrent.STM.SSem+ Other-modules: Control.Concurrent.STM.SSemInternals+ Build-depends: base < 5, containers, stm Test-Suite TestSafeSemaphore type: exitcode-stdio-1.0
src/Control/Concurrent/FairRWLock.hs view
@@ -1,51 +1,50 @@ {-# LANGUAGE DeriveDataTypeable, PatternGuards #-}-{-| Provides a fair RWLock, similar to one from Java.-+{-| Provides a fair RWLock, similar to one from Java, which is itself documented at <http://download.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html> - There are complicated policy choices that have to be made. This policy choices here are different- from the ones for the RWLock in concurrent-extras.-- The preferred way to use this API is sticking to 'new', 'withRead', and 'withWrite'.+ There are complicated policy choices that have to be made. The policy choices here are different+from the ones for the RWLock in concurrent-extras. - The readers and writers are always identified by their ThreadId. Each thread that calls- acquireRead must later call releaseRead from the same thread. Each thread that calls acquireWrite- must later call releaseWrite from the same thread.+ The 'FairRWLock' may be in a free unlocked state, it may be in a read locked state, or it may be a+write locked state. Many running threads may hold the read lock and execute concurrently. Only one+running thread may hold the write lock. The scheduling is a fair FIFO queue that avoids starvation. - The main way to misuse a FairRWLock is to call a release without having called an acquire. This is- reported in the (Left error) outcomes from releaseRead and releaseWrite. If the FairRWLock has a- bug and finds itself in an impossible state then it will throw an error.+ When in the read lock state the first 'acquireWrite' will block, and subsequent 'acquireRead' and+'acquireWrite' will queue in order. When in the write locked state all other threads trying to+'acquireWrite' or 'acquireRead' will queue in order. - The FairRWLock may be in a free unlocked state, it may be in a read locked state, and it may be a- write locked state. Many running threads may hold the read lock and execute concurrently. Only- one running thread may hold the write lock. The scheduling is a fair FIFO queue that avoids- starvation.+ 'FairRWLock' allows recursive write locks, and it allows recursive read locks, and it allows the+write lock holding thread to acquire read locks. When the current writer also holds read locks and+then releases its last write lock it will immediately convert to the read locked state (and other+waiting readers may join it). When a reader acquires a write lock it will (1) release all its read+locks, (2) wait to acquire the write lock, (3) retake the same number of read locks released in (1). - When in the read lock state the first acquireWrite will block, and subsequent acquireRead and- acquireWrite will queue in order. When in the write locked state all other threads trying to- acquireWrite or acquireRead will queue in order.+ The preferred way to use this API is sticking to 'new', 'withRead', and 'withWrite'. - FairRWLock allows recursive write locks, and it allows recursive read locks, and it allows the- write lock holding thread to acquire read locks. When the current writer also holds read locks and- then releases its last write lock it will immediately convert to the read locked state (and other- waiting readers may join it). When a reader acquires a write lock it will (1) release all its read- locks, (2) wait to acquire the write lock, (3) retake the same number of read locks released in- (1).+ No sequence of calling acquire on a single RWLock should lead to deadlock. Exceptions, espcially+from 'killThread', do not break 'withRead' or 'withWrite'. The 'withRead' and 'withWrite' ensure+all locks get released when exiting due to an exception. - No sequence of calling acquire on a single RWLock should lead to deadlock.+ The readers and writers are always identified by their 'ThreadId'. Each thread that calls+'acquireRead' must later call 'releaseRead' from the same thread. Each thread that calls+'acquireWrite' must later call 'releaseWrite' from the same thread. The main way to misuse a+FairRWLock is to call a release without having called an acquire. This is reported in the (Left+error) outcomes from 'releaseRead' and 'releaseWrite'. Only if the 'FairRWLock' has a bug and finds+itself in an impossible state then it will throw an error. -} module Control.Concurrent.FairRWLock ( RWLock, RWLockException(..), RWLockExceptionKind(..),FRW(..),LockKind(..),TMap,TSet- , new, peekLock, checkLock+ , new+ , withRead, withWrite , acquireRead, acquireWrite , releaseRead, releaseWrite- , withRead, withWrite+ , peekLock, checkLock ) where import Control.Applicative(liftA2) import Control.Concurrent-import Control.Exception+import Control.Exception(Exception,bracket_,onException,evaluate,uninterruptibleMask_,mask_,throw) import Control.Monad((>=>),join,forM_) import Data.Sequence((<|),(|>),(><),Seq,ViewL(..),ViewR(..)) import qualified Data.Sequence as Seq(empty,viewl,viewr,breakl,spanl)@@ -90,16 +89,16 @@ data RWLockException = RWLockException ThreadId RWLockExceptionKind String deriving (Show,Typeable) --- | Operation in which error arose+-- | Operation in which error arose, data RWLockExceptionKind = RWLock'acquireWrite | RWLock'releaseWrite | RWLock'acquireRead | RWLock'releaseRead deriving (Show,Typeable) instance Exception RWLockException --- | Observable state of holders of lock. The W returns a pair of Ints where the first is number of+-- | Observable state of holder(s) of lock(s). The W returns a pair of Ints where the first is number of -- read locks (at least 0) and the second is the number of write locks held (at least 1). The R--- returns a map from thread id to the positive number of read locks held.+-- returns a map from thread id to the number of read locks held (at least 1). data FRW = F | R TMap | W (ThreadId,(Int,Int)) deriving (Show) -- | Create a new RWLock which starts in a free and unlocked state.@@ -160,23 +159,27 @@ -- -- This can block but cannot be interrupted. releaseRead :: RWLock -> IO (Either RWLockException ())-releaseRead (RWL rwlVar) = uninterruptibleMask_ $ do+releaseRead (RWL rwlVar) = mask_ $ do me <- myThreadId- releaseRead' False me rwlVar -- False to indicate called from releaseRead+ releaseRead' False me rwlVar -- False to indicate call is from releaseRead --- Eleven non-impossible cases, plus one impossible case--- Lock is Free, error or impossible--- I have write lock, I have no read lock, error or impossible--- , I have at least one read lock, just decrement the counter--- Someone else has write lock, abandoning my acquireWrite--- , releaseWrite called in error--- Read lock held, I have 1 read lock, no other readers, change to FreeLock--- , change to next Writer--- , remove and leave to other readers--- , I have more than one read lock, just decrement the counter--- , I have no read lock, abandoning with no queue is IMPOSSIBLE--- , abandoning from queue past next writer--- , releaseRead called in error+-- The (abandon :: Bool) is False if called from releaseRead (from user API).+-- The (abandon :: Bool) is True if called as handler when acquireRead[Priority] interrupted by exception (internal use).+-- +-- There are 14 cases.+-- Four ERROR cases from misuse of releaseRead, Three IMPOSSIBLE cases (from interruptions), Seven normal cases:+-- Lock is Free, ERROR if releaseRead or IMPOSSIBLE if interrupted -- 1 and 2+-- I have write lock, I have no read lock, ERROR if releaseRead or IMPOSSIBLE if interrupted -- 3 and 4+-- , I have at least one read lock, just decrement the counter -- 5+-- Someone else has write lock, abandoning my acquireWrite -- 6+-- , releaseRead called in ERROR -- 7+-- Read lock held, I have 1 read lock, no other readers, change to FreeLock -- 8+-- , change to next Writer -- 9+-- , remove and leave to other readers -- 10+-- , I have more than one read lock, just decrement the counter -- 11+-- , I have no read lock, abandoning with no queue is IMPOSSIBLE -- 12+-- , abandoning from queue past next writer -- 13+-- , releaseRead called in ERROR -- 14 releaseRead' :: Bool -> ThreadId -> MVar LockUser -> IO (Either RWLockException ()) releaseRead' abandon me rwlVar = uninterruptibleMask_ . modifyMVar rwlVar $ \ rwd -> do let impossible :: Show x => String -> x -> IO a@@ -202,24 +205,29 @@ evaluate $ if Set.null rcs' then pre >< post else pre >< ((ReaderKind rcs',mblock) <| post) case rwd of- FreeLock | abandon ->+ FreeLock | abandon -> {- 1 -} impossible "acquireRead interrupted with unlocked RWLock" me- | otherwise ->++ | otherwise -> {- 2 -} err "cannot releaseRead lock from unlocked RWLock" me w@(Writer { writerID=it, readerCount=rc, queue=q }) | it==me -> do case rc of- 0 | abandon -> impossible "acquireRead interrupted with write lock but not read lock" (me,it)- | otherwise -> err "releaseRead when holding write lock but not read lock" (me,it)- _ -> do+ 0 | abandon -> {- 3 -}+ impossible "acquireRead interrupted with write lock but not read lock" (me,it)++ | otherwise -> {- 4 -}+ err "releaseRead when holding write lock but not read lock" (me,it)++ _ -> do {- 5 -} rc' <- evaluate $ pred rc ret (w { readerCount=rc' }) - {-ditto-} | abandon -> do+ {-ditto-} | abandon -> do {- 6 -} q' <- dropReader q ret (w { queue=q' }) - {-ditto-} | otherwise ->+ {-ditto-} | otherwise -> {- 7 -} err "releaseRead called when not read locked " me r@(Readers { readerCounts=rcs,queueR=qR }) ->@@ -228,27 +236,30 @@ let rcs' = Map.delete me rcs if Map.null rcs' then case qR of- Nothing ->+ Nothing -> {- 8 -} ret FreeLock- Just ((wid,mblock),q) -> do++ Just ((wid,mblock),q) -> do {- 9 -} putMVar mblock () ret (Writer { writerID=wid, writerCount=1, readerCount=0, queue=q })- else ret (r { readerCounts=rcs' }) - Just rc -> do+ else ret (r { readerCounts=rcs' }) {- 10 -}++ Just rc -> do {- 11 -} rc' <- evaluate $ pred rc rcs' <- evaluate $ Map.insert me rc' rcs ret (r { readerCounts=rcs' }) Nothing | abandon -> case qR of- Nothing ->+ Nothing -> {- 12 -} impossible "acquireRead interrupted not holding lock and with no queue" (me,rcs)- Just (w,q) -> do++ Just (w,q) -> {- 13 -} do q' <- dropReader q ret (r { queueR = Just (w,q') }) - {-ditto-} | otherwise -> + {-ditto-} | otherwise -> {- 14 -} err "releaseRead called with read lock held by others" (me,rcs) -- | A thread that calls acquireWrite must later call releaseWrite once for each call to acquireWrite.@@ -258,9 +269,9 @@ -- -- This can block but cannot be interrupted. releaseWrite :: RWLock -> IO (Either RWLockException ())-releaseWrite (RWL rwlVar) = uninterruptibleMask_ $ do+releaseWrite (RWL rwlVar) = mask_ $ do me <- myThreadId- releaseWrite' False me rwlVar -- False to indicate called from releaseWrite+ releaseWrite' False me rwlVar -- False to indicate call is from releaseWrite -- Nine non-impossible cases, plus one impossible case -- Lock is Free@@ -298,6 +309,7 @@ case rwd of FreeLock | abandon -> impossible "acquireWrite interrupted with unlocked RWLock" me+ | otherwise -> err "cannot releaseWrite lock from unlocked RWLock" me @@ -366,13 +378,13 @@ isReader (ReaderKind {},_) = True isReader _ = False --- Six cases:+-- Six cases below: -- Lock is Free -- I already have write lock--- Someone else has write lock, mblock+-- Someone else has write lock, leads to mblock -- I alread have read lock -- Someone else has read lock, no pending write lock--- Someone else has read lock, there is a pending write lock, mblock+-- Someone else has read lock, there is a pending write lock, leads to mblock -- | Any thread may call acquireRead (even ones holding write locks). This read lock may be -- acquired multiple times, requiring an identical number of releaseRead calls.@@ -397,7 +409,8 @@ rc' <- evaluate $ succ rc return ( w { readerCount=rc' } , return () )- | otherwise -> do++ {- ditto -} | otherwise -> do (q',mblock) <- enterQueueR q me return ( w { queue = q' } , safeBlock mblock )@@ -434,6 +447,7 @@ addMe rcs | Set.member me rcs = error (imp "enterQueueR.addMe when already in set" me) | otherwise = return (Set.insert me rcs) +-- Five cases. -- This is not exported. This has uninterruptibleMask_. It is used to restore read locks released -- during acquireWrite when acquireWrite is called while holding read locks. If this acquireWrite -- upgrade is going well then this thread holds the Writer lock and acquireReadPriority is identical@@ -486,13 +500,13 @@ addMe rcs | Set.member me rcs = error (imp "enterQueueL.addMe when already in set" me) | otherwise = return (Set.insert me rcs) --- Six cases:+-- Six cases below: -- Lock is Free -- I already have write lock--- Someone else has write lock+-- Someone else has write lock, leads to waiting -- I already have read lock--- Someone else has read lock, there is no pending write lock--- Someone else has read lock, there is a pending write lock+-- Someone else has read lock, there is no pending write lock, wait+-- Someone else has read lock, there is a pending write lock, wait -- | Any thread may call acquireWrite (even ones holding read locks, but see below for interrupted -- behavior). This write lock may be acquired multiple times, requiring an identical number of@@ -565,7 +579,7 @@ subtle bug #1: -When converting from a read lock holding 'rc' read locks to a also holding a write lock, I first wrote:+When converting from a read lock holding rc > 0 read locks to also holding a write lock, I first wrote: replicateM_ rc (releaseRead rwl >>= either throw return) acquireWrite rwl
− src/Control/Concurrent/MSem.hs
@@ -1,132 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--- | --- Module : Control.Concurrent.MSem--- Copyright : (c) Chris Kuklewicz 2011--- License : 3 clause BSD-style (see the file LICENSE)--- --- Maintainer : haskell@list.mightyreason.com--- Stability : experimental--- Portability : non-portable (concurrency)------ A semaphore in which operations may 'wait' for or 'signal' single units of value. This modules--- is intended to improve on "Control.Concurrent.QSem".--- --- This semaphore gracefully handles threads which die while blocked waiting. The fairness--- guarantee is that blocked threads are FIFO.------ If 'with' is used to guard a critical section then no quantity of the semaphore will be lost if--- the activity throws an exception. 'new' can initialize the semaphore to negative, zero, or--- positive quantity. 'wait' always leaves the 'MSem' with non-negative quantity.------ The functions below are generic in (Integral i) with specialization to Int and Integer.------ Overflow warning: These operations do not check for overflow errors. If the Integral type is too--- small to accept the new total then the behavior of these operations is undefined. Using (MSem--- Integer) prevents the possibility of an overflow error.-module Control.Concurrent.MSem- (MSem- ,new- ,with- ,wait- ,signal- ,peekAvail- ) where--import Control.Monad(join)-import Control.Concurrent.MVar(MVar,withMVar,modifyMVar,modifyMVar_,newMVar,newEmptyMVar,putMVar,takeMVar,tryTakeMVar,tryPutMVar)-import Control.Exception(bracket_,uninterruptibleMask_,mask_)-import Data.Typeable(Typeable)--{- design notes are in MSemN.hs -}---- | A 'MSem' is a semaphore in which the available quantity can be added and removed in single--- units, and which can start with positive, zero, or negative value.-data MSem i = MSem { mSem :: !(MVar i) -- ^ Used to lock access to state of semaphore quantity. Never updated.- , queueWait :: !(MVar ()) -- ^ Used as FIFO queue for waiter, held by head of queue. Never updated.- , headWait :: !(MVar ()) -- ^ The head of the waiter queue blocks on headWait. Never updated.- }- deriving (Eq,Typeable)---- |'new' allows positive, zero, and negative initial values. The initial value is forced here to--- better localize errors.------ The only way to acheive a negative value with MSem is to start negative with 'new'. Once the quantity-new :: Integral i => i -> IO (MSem i)-{-# SPECIALIZE new :: Int -> IO (MSem Int) #-}-{-# SPECIALIZE new :: Integer -> IO (MSem Integer) #-}-new initial = do- newMS <- newMVar $! initial- newQueueWait <- newMVar ()- newHeadWait <- newEmptyMVar- return (MSem { mSem = newMS- , queueWait = newQueueWait- , headWait = newHeadWait })---- | 'with' takes a unit of value from the semaphore to hold while performing the provided--- operation. 'with' ensures the quantity of the sempahore cannot be lost if there are exceptions.------ 'with' uses 'bracket_' to ensure 'wait' and 'signal' get called correctly.-with :: Integral i => MSem i -> IO a -> IO a-{-# SPECIALIZE with :: MSem Int -> IO a -> IO a #-}-{-# SPECIALIZE with :: MSem Integer -> IO a -> IO a #-}-with m = bracket_ (wait m) (signal m)---- |'wait' will take one unit of value from the sempahore, but will block if the quantity available--- is not positive.------ If 'wait' returns without interruption then it left the 'MSem' with a remaining quantity that was--- greater than or equal to zero. If 'wait' is interrupted then no quantity is lost. If 'wait'--- returns without interruption then it is known that each earlier waiter has definitely either been--- interrupted or has retured without interruption.-wait :: Integral i => MSem i -> IO ()-{-# SPECIALIZE wait :: MSem Int -> IO () #-}-{-# SPECIALIZE wait :: MSem Integer -> IO () #-}-wait m = mask_ . withMVar (queueWait m) $ \ () -> do- join . modifyMVar (mSem m) $ \ ms -> do- mayGrab <- tryTakeMVar (headWait m)- case mayGrab of- Just () -> return (ms,return ())- Nothing -> if 1 <= ms- then let ms' = pred ms -- ms' is never negative- in seq ms' $ return (ms', return ())- else return (ms, takeMVar (headWait m))- -- mask_ is needed above because we may have just decremented 'avail' and we must finished 'wait'- -- without being interrupted so that a 'bracket' can ensure a matching 'signal' can be ensured.- --- -- join (takeMVar ..) actually may or may not block, a 'signal' could have already arrived or this- -- thread might have an pending throwTo/killThread exception.---- | 'signal' adds one unit to the sempahore.------ 'signal' may block, but it cannot be interrupted, which allows it to dependably restore value to--- the 'MSem'. All 'signal', 'peekAvail', and the head waiter may momentarily block in a fair FIFO--- manner.-signal :: Integral i => MSem i -> IO ()-{-# SPECIALIZE signal :: MSem Int -> IO () #-}-{-# SPECIALIZE signal :: MSem Integer -> IO () #-}-signal m = uninterruptibleMask_ . modifyMVar_ (mSem m) $ \ ms -> do- -- mask_ might be as good as uninterruptibleMask_ since nothing below can block- if ms < 0- then return $! succ ms- else do- didPlace <- tryPutMVar (headWait m) () -- ms is never negative- if didPlace- then return ms- else return $! succ ms---- | 'peekAvail' skips the queue of any blocked 'wait' threads, but may momentarily block on--- 'signal', other 'peekAvail', and the head waiter. This returns the amount of value available to--- be taken. Using this value without producing unwanted race conditions is left up to the--- programmer.------ Note that "Control.Concurrent.MSemN" offers a more powerful API for making decisions based on the--- available amount.-peekAvail :: Integral i => MSem i -> IO i-{-# SPECIALIZE peekAvail :: MSem Int -> IO Int #-}-{-# SPECIALIZE peekAvail :: MSem Integer -> IO Integer #-}-peekAvail m = mask_ $ withMVar (mSem m) $ \ ms -> do- extraFlag <- tryTakeMVar (headWait m)- case extraFlag of- Nothing -> return ms- Just () -> do putMVar (headWait m) () -- cannot block- return $! succ ms
+ src/Control/Concurrent/MSem.lhs view
@@ -0,0 +1,306 @@+> {-# LANGUAGE DeriveDataTypeable #-}+> -- |+> -- Module : Control.Concurrent.MSem+> -- Copyright : (c) Chris Kuklewicz 2011+> -- License : 3 clause BSD-style (see the file LICENSE)+> --+> -- Maintainer : haskell@list.mightyreason.com+> -- Stability : experimental+> -- Portability : non-portable (concurrency)+> --+> -- This is a literate haskell version of Control.Concurrent.MSem for increased clarity.+> --+> -- A semaphore in which operations may 'wait' for or 'signal' single units of value. This modules+> -- is intended to improve on "Control.Concurrent.QSem".+> --+> -- This semaphore gracefully handles threads which die while blocked waiting. The fairness+> -- guarantee is that blocked threads are servied in a FIFO order.+> --+> -- If 'with' is used to guard a critical section then no quantity of the semaphore will be lost if+> -- the activity throws an exception or if this thread is killed by the rest of the program.+> --+> -- 'new' can initialize the semaphore to negative, zero, or positive quantity.+> -- 'wait' always leaves the 'MSem' with non-negative quantity.+> -- 'signal' alawys adds one to the quantity.+> --+> -- The functions below are generic in (Integral i) with specialization to Int, Word, and Integer.+> --+> -- Overflow warning: These operations do not check for overflow errors. If the Integral type is too+> -- small to accept the new total then the behavior of 'signal' is undefined. Using (MSem+> -- Integer) prevents the possibility of an overflow error. [ A version of 'signal' that checks the upper+> -- bound could be added, but how would it report failure and how would you use this sanely? ]+> -- +>+> module Control.Concurrent.MSem+> (MSem -- do not export the constructor, kept abstract+> , new -- :: Integral i => i -> IO (MSem i)+> , with -- :: Integral i => MSem i -> IO a -> IO a+> , wait -- :: Integral i => MSem i -> IO ()+> , signal -- :: Integral i => MSem i -> IO ()+> , peekAvail -- :: Integral i => MSem i -> IO i+> ) where++The above export list shows the API.++The amount of value in the orignal QSem is always of type Int. This module+generalizes the type to any Integral, where comparison (<) to 'fromIntegral 0'+and 'pred' and 'succ' are employed.++The 'new', 'wait', and 'signal' operations mimic the QSem API. The peekAvail+query is also provided, primarily for monitoring or debugging purposes. The+with combinator is used to safely and conveniently bracket operations.++> import Prelude( Integral,Eq,IO,Int,Integer,Maybe(Just,Nothing)+> , seq,pred,succ,return+> , (.),(<),($),($!) )+> import Control.Concurrent.MVar( MVar+> , withMVar,modifyMVar,modifyMVar_,tryPutMVar+> , newMVar,newEmptyMVar,putMVar,takeMVar,tryTakeMVar)+> import Control.Exception(bracket_,uninterruptibleMask_,mask_)+> import Control.Monad(join)+> import Data.Typeable(Typeable)+> import Data.Word(Word)++The import list shows that most of the power of MVar's will be exploited, and+that the rather dangerous uninterruptibleMask_ will be employed (in 'signal').++A new semaphore is created with a specified avaiable quantity. The mutable+available quantity will be called the value of the semaphore for brevity's+sake.++The use of a semaphore involves multiple threads executing 'wait' and 'signal'+commands. This stream of wait and 'signal' commands will be executed as if+they arrive in some sequential, non-overlapping, order which is an interleaving+of the commands from each thread.++From the local perspective of a single thread the semantics are simple to+specify. The 'signal' command will find the MSem to have a value and mutate+this to add one to the value. The 'wait' command will find the MSem to have a+value and if this is greater than zero it will mutate this to be one less and+finish, otherwise the value is negative or zero and the execution of the 'wait'+thread will block. Eventually another thread executes 'signal' and raises the+value to be positive, at this point the blocked 'wait' thread will reduce the+value by one and finish executing the 'wait' command.++From a broader perspective there is a question of precedence and starvation.+If there is a blocked wait thread and a second 'wait' command starts to execute+then will the second thread "find the MSem to have a value" before or after the+orignal blocked thread has finished? If there are several blocked 'wait'+threads and a 'signal' arrives then which blocked thread has priority to take+the quatity and finish waiting? Are there any fairness guarantees or might a+blocked thread never get priority over its bretheren leading to starvation?++I have designed this module to provide a fair semaphore: multiple 'wait'+threads are serviced in FIFO order. All 'signal' operations, while they may+block, are individually quick.++There are precisely three components, all MVars alloced by 'new': queueWait,+quantityStore, and headWait.++1) The 'wait' operations are forced into a FIFO queue by taking an (MVar ())+called queueWait during their operation. The thread holding this token is the+"head" waiter.++2) The 'signal' operations are forced into a FIFO queue by taking the MVar+called quantityStore which holds an integral value.++3) The logical value stored in the semaphore might be represented by one of two+different states of the semaphore data structure, depending on whether+'headWait :: MVar ()' is empty or full. In this module a full headWait+reprents a single unit of value stored in the semaphore.++> -- | A 'MSem' is a semaphore in which the available quantity can be added and removed in single+> -- units, and which can start with positive, zero, or negative value.+> data MSem i = MSem { quantityStore :: !(MVar i) -- ^ Used to lock access to state of semaphore quantity. Never updated.+> , queueWait :: !(MVar ()) -- ^ Used as FIFO queue for waiter, held by head of queue. Never updated.+> , headWait :: !(MVar ()) -- ^ The head of the waiter queue blocks on headWait. Never updated.+> }+> deriving (Eq,Typeable)+>+> -- |'new' allows positive, zero, and negative initial values. The initial value is forced here to+> -- better localize errors.+> --+> -- The only way to achieve a negative value with MSem is to start negative with 'new'. Once a negative quantity becomes non-negative+> -- by use of 'signal' it will never later be negative.+> new :: Integral i => i -> IO (MSem i)+> {-# SPECIALIZE new :: Int -> IO (MSem Int) #-}+> {-# SPECIALIZE new :: Word -> IO (MSem Word) #-}+> {-# SPECIALIZE new :: Integer -> IO (MSem Integer) #-}+> new initial = do+> newQuantityStore <- newMVar $! initial+> newQueueWait <- newMVar ()+> newHeadWait <- newEmptyMVar+> return (MSem { quantityStore = newQuantityStore+> , queueWait = newQueueWait+> , headWait = newHeadWait })+>++Note that the only MVars that get allocated are all by these three commands in+'new'. The other commands change the stored values but do not allocate new+mutable storage. None of these three MVars can be simply replaced by an IORef+because the possibility of blocking on each of them is used in the design. A+design with two MVar is possible but I think it would have more contention+between threads and be more complex to ensure thread safety.++There are four operations on the semaphore leading to two possible states for+headWait:++1) If the most recent operation to finish was 'new' then headWait is definitely+empty and the value of the MSem is the quantity in quantityStore.++2) If the most recent operation to finish was 'wait' then headWait is+definitely empty and the value of the MSem is the quantity in quantityStore.++3) If the most recent operation to finish was a 'signal' and the new value is+positive then headWait is definitely full and the value of the MSem is the+quantity in quantityStore PLUS ONE.++4) If the most recent operation to finish was a 'signal' and the new value is+non-positive then headWait is definitely empty and the value of the MSem is the+quantity in quantityStore.++If the "head" 'wait' thread finds a non-positive value then it will need to+sleep until being awakened by a future 'signal'. This sleeping is accomplished+by the head waiter taking an empty headWait.++All uses of the semaphore API to guard execution of an action should use 'with'+to simplify ensuring exceptions are safely handled. Other uses should use+still try and use combinators in Control.Exception to ensure that no 'signal'+commands get lost so that no quantity of the semaphore leaks when exceptions+occur.++> -- | 'with' takes a unit of value from the semaphore to hold while performing the provided+> -- operation. 'with' ensures the quantity of the sempahore cannot be lost if there are exceptions or+> -- if killThread is used.+> --+> -- 'with' uses 'bracket_' to ensure 'wait' and 'signal' get called correctly.+> with :: Integral i => MSem i -> IO a -> IO a+> {-# SPECIALIZE with :: MSem Int -> IO a -> IO a #-}+> {-# SPECIALIZE with :: MSem Word -> IO a -> IO a #-}+> {-# SPECIALIZE with :: MSem Integer -> IO a -> IO a #-}+> with m = bracket_ (wait m) (signal m)++> -- |'wait' will take one unit of value from the sempahore, but will block if the quantity available+> -- is not positive.+> --+> -- If 'wait' returns normally (not interrupted) then it left the 'MSem' with a remaining quantity that was+> -- greater than or equal to zero. If 'wait' is interrupted then no quantity is lost. If 'wait'+> -- returns without interruption then it is known that each earlier waiter has definitely either been+> -- interrupted or has retured without interruption (the FIFO guarantee).+> wait :: Integral i => MSem i -> IO ()+> {-# SPECIALIZE wait :: MSem Int -> IO () #-}+> {-# SPECIALIZE wait :: MSem Word -> IO () #-}+> {-# SPECIALIZE wait :: MSem Integer -> IO () #-}+> wait m = mask_ . withMVar (queueWait m) $ \ () -> do+> join . modifyMVar (quantityStore m) $ \ quantity -> do+> mayGrab <- tryTakeMVar (headWait m) -- First try optimistic grab on (headWait w)+> case mayGrab of+> Just () -> return (quantity,return ()) -- Took unit of value, done+> Nothing -> if 0 < quantity -- Did not take unit of value, check quantity+> then let quantity' = pred quantity -- quantity' is never negative+> in seq quantity' $ return (quantity', return ())+> else return (quantity, takeMVar (headWait m)) -- go to sleep++The needed invariant is that 'wait' takes a unit of value iff it returns+normally (i.e. it is not interrupted). The 'mask_' is needed above because we+may decrement 'headWait' with 'tryTakeMVar' and must then finished the+'withMVar' without being interrupted. Under the 'mask_' the 'wait' might block+and then be interruptable at one or more of++1) 'withMVar (queueWait m)' : the 'wait' dies before becoming head waiter while+blocked by previous 'wait'.++2) 'modifyMVar (quantityStore m)' : the 'wait' dies as head waiter while+blocked by previous 'signal'.++3) 'takeMVar (headWait m)' from 'join' : the 'wait' dies as head waiter while+sleeping on 'headWait'.++All three of those are safe places to die. The unsafe possibilities would be+to die after a 'tryTakeMVar (headWait m)' returns 'Just ()' or after+'modifyMVar' puts the decremented quantity into (quantityStore m). These are+prevented by the 'mask_'.++Note that the head waiter must also get to the front of the FIFO queue of+signals to get the value of 'quantityStore'. Only the head waiter competes+with the 'signal' & peek threads for obtaining 'quantityStore'.++> -- | 'signal' adds one unit to the sempahore. Overflow is not checked.+> --+> -- 'signal' may block, but it cannot be interrupted, which allows it to dependably restore value to+> -- the 'MSem'. All 'signal', 'peekAvail', and the head waiter may momentarily block in a fair FIFO+> -- manner.+> signal :: Integral i => MSem i -> IO ()+> {-# SPECIALIZE signal :: MSem Int -> IO () #-}+> {-# SPECIALIZE signal :: MSem Word -> IO () #-}+> {-# SPECIALIZE signal :: MSem Integer -> IO () #-}+> signal m = uninterruptibleMask_ . modifyMVar_ (quantityStore m) $ \ quantity -> do+> if quantity < 0+> then return $! succ quantity+> else do+> didPlace <- tryPutMVar (headWait m) () -- quantity is never negative+> if didPlace+> then return quantity+> else return $! succ quantity++The 'signal' operation first has the FIFO grab of (quantityStore m). If+'tryPutMVar' returns True then a currently sleeping head waiter will be woken+up.++The 'modifyMVar_' will block until prior 'signal' and 'peek' threads and+perhaps a prior head 'wait' finish. This is the only point that may block.+Thus 'uninterruptibleMask_' only differs from 'mask_' in that once 'signal'+starts executing it cannot be interrupted before returning the unit of value to+the MSem. All the operations 'signal' would be waiting for are quick and are+themselves non-blocking, so the uninterruptible operation here should finish+without arbitrary delay.++Consider 'with m act = bracket_ (wait m) (signal m) act', refer to+http://www.haskell.org/ghc/docs/latest/html/libraries/base/src/Control-Exception-Base.html#bracket_+for the details. Specifically a killThread arrives at one of these points:++1) during (wait m) the exception is masked by both 'bracket' and 'wait' so this+occurs at one of the blocking points mentioned above. This does not affect the+MSe, and aborts the 'bracket_' without calling act or (signal m).++2) during (restore act) the `onException` in the definition of 'bracket' will+shift control to (signal m).++3) during (signal m) regardless of how act exited. Here we know (wait m)+exited normally and thus took a unit of value from the MSem. The mask_ of+'bracket' ensures that the uninterruptibleMask_ in 'signal' ensures that the+unit of value is returned to MSem even if 'signal' blocks on 'modifyMVar_+(quantityStore m)'.++4) Outside of any of the above the mask_ in 'bracket' prevents the killThread+from being recognized until one of the above or until the 'bracket' finishes.++If 'signal' did not use 'uninterruptibleMask_' then point (3) could be+interrupted without returning the value to the MSem. Avoiding losing quantity+is the primary design criterion for this semaphore library, and I think it+requires this apparantly safe use of uninterruptibleMask_ to ensure that+'signal' can and will succeed.++> -- | 'peekAvail' skips the queue of any blocked 'wait' threads, but may momentarily block on+> -- 'signal', other 'peekAvail', and the head waiter. This returns the amount of value available to+> -- be taken. Using this value without producing unwanted race conditions is left up to the+> -- programmer.+> --+> -- Note that "Control.Concurrent.MSemN" offers a more powerful API for making decisions based on the+> -- available amount.+> peekAvail :: Integral i => MSem i -> IO i+> {-# SPECIALIZE peekAvail :: MSem Int -> IO Int #-}+> {-# SPECIALIZE peekAvail :: MSem Word -> IO Word #-}+> {-# SPECIALIZE peekAvail :: MSem Integer -> IO Integer #-}+> peekAvail m = mask_ $ withMVar (quantityStore m) $ \ quantity -> do+> extraFlag <- tryTakeMVar (headWait m)+> case extraFlag of+> Nothing -> return quantity+> Just () -> do putMVar (headWait m) () -- cannot block+> return $! succ quantity++The implementaion of peekAvail is slightly complicated by the interplay of+tryTakeMVar and putMVar. Only this thread will be holding the lock on+quantityStore and the putMVar only runs to put a () just taken from headWait.+Thus the putMVar will never block. The 'mask_' ensures that there can be no+external interruption between a tryTakeMVar and putMVar.
− src/Control/Concurrent/MSemN.hs
@@ -1,223 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--- | --- Module : Control.Concurrent.MSemN--- Copyright : (c) Chris Kuklewicz 2011--- License : 3 clause BSD-style (see the file LICENSE)--- --- Maintainer : haskell@list.mightyreason.com--- Stability : experimental--- Portability : non-portable (concurrency)------ Quantity semaphores in which each thread may wait for an arbitrary amount. This modules is--- intended to improve on "Control.Concurrent.QSemN".--- --- This semaphore gracefully handles threads which die while blocked waiting for quantity. The--- fairness guarantee is that blocked threads are FIFO. An early thread waiting for a large--- quantity will prevent a later thread waiting for a small quantity from jumping the queue.------ If 'with' is used to guard a critical section then no quantity of the semaphore will be lost--- if the activity throws an exception.------ The functions below are generic in (Integral i) with specialization to Int and Integer.------ Overflow warning: These operations do not check for overflow errors. If the Integral type is too--- small to accept the new total then the behavior of these operations is undefined. Using (MSem--- Integer) prevents the possibility of an overflow error.-module Control.Concurrent.MSemN- (MSemN- ,new- ,with- ,wait- ,signal- ,withF- ,waitF- ,signalF- ,peekAvail- ) where--import Control.Monad(when)-import Control.Concurrent.MVar(MVar,withMVar,modifyMVar,modifyMVar_,newMVar,newEmptyMVar,putMVar,takeMVar,tryTakeMVar)-import Control.Exception(bracket,uninterruptibleMask_,onException,evaluate,mask_)-import Data.Typeable(Typeable)--{- --The only MVars allocated are the three created be 'new'. Their three roles are-1) to have a FIFO queue of waiters-2) for the head waiter to block on-3) to protect the quantity state of the semaphore and the head waiter--subtle design notes:--with, wait, and signal pattern match the quantity against 0 which has two effect: it avoids locking-in the easy case and it ensures strict evaluation of the quantity before any locks are taken.--Originally withF, waitF, and signal did not strictly evalaute the function they are passed before-locks are taken because there is no real point since the function may throw an error when computing-the size. But then I realized forcing 'f' might run forever with the locks held and I could move-this particular hang outside the locks by first evaluating 'f'.---}---- MS has an invariant that "maybe True (> avail) headWants" is always True.-data MS i = MS { avail :: !i -- ^ This is the quantity available to be taken from the semaphore.- , headWants :: !(Maybe i) -- ^ If there is waiter then this is Just the amount being waited for.- }- deriving (Eq,Typeable)---- | A 'MSemN' is a quantity semaphore, in which the available quantity may be signalled or--- waited for in arbitrary amounts.-data MSemN i = MSemN { mSem :: !(MVar (MS i)) -- ^ Used to lock access to state of semaphore quantity.- , queueWait :: !(MVar ()) -- ^ Used as FIFO queue for waiter, held by head of queue.- , headWait :: !(MVar ()) -- ^ The head of the waiter queue blocks on headWait.- }- deriving (Eq,Typeable)---- |'new' allows positive, zero, and negative initial values. The initial value is forced here to--- better localize errors.-new :: Integral i => i -> IO (MSemN i)-{-# SPECIALIZE new :: Int -> IO (MSemN Int) #-}-{-# SPECIALIZE new :: Integer -> IO (MSemN Integer) #-}-new initial = do- newMS <- newMVar $! (MS { avail = initial- , headWants = Nothing })- newQueueWait <- newMVar ()- newHeadWait <- newEmptyMVar- return (MSemN { mSem = newMS- , queueWait = newQueueWait- , headWait = newHeadWait })---- | 'with' takes a quantity of the semaphore to take and hold while performing the provided--- operation. 'with' ensures the quantity of the sempahore cannot be lost if there are exceptions.--- This uses 'bracket' to ensure 'wait' and 'signal' get called correctly.-with :: Integral i => (MSemN i) -> i -> IO a -> IO a-{-# SPECIALIZE with :: MSemN Int -> Int -> IO a -> IO a #-}-{-# SPECIALIZE with :: MSemN Integer -> Integer -> IO a -> IO a #-}-with _ 0 = id-with m wanted = bracket (wait m wanted) (\() -> signal m wanted) . const---- | 'withF' takes a pure function and an operation. The pure function converts the available--- quantity to a pair of the wanted quantity and a returned value. The operation takes the result--- of the pure function. 'withF' ensures the quantity of the sempahore cannot be lost if there--- are exceptions. This uses 'bracket' to ensure 'waitF' and 'signal' get called correctly.------ Note: A long running pure function will block all other access to the 'MSemN' while it is--- evaluated.-withF :: Integral i => (MSemN i) -> (i -> (i,b)) -> ((i,b) -> IO a) -> IO a-{-# SPECIALIZE withF :: MSemN Int -> (Int -> (Int,b)) -> ((Int,b) -> IO a) -> IO a #-}-{-# SPECIALIZE withF :: MSemN Integer -> (Integer -> (Integer,b)) -> ((Integer,b) -> IO a) -> IO a #-}-withF m f = bracket (waitF m f) (\(wanted,_) -> signal m wanted)---- |'wait' allow positive, zero, and negative wanted values. Waiters may block, and will be handled--- fairly in FIFO order.------ If 'wait' returns without interruption then it left the 'MSemN' with a remaining quantity that was--- greater than or equal to zero. If 'wait' is interrupted then no quantity is lost. If 'wait'--- returns without interruption then it is known that each earlier waiter has definitely either been--- interrupted or has retured without interruption.-wait :: Integral i => (MSemN i) -> i -> IO ()-{-# SPECIALIZE wait :: MSemN Int -> Int -> IO () #-}-{-# SPECIALIZE wait :: MSemN Integer -> Integer -> IO () #-}-wait _ 0 = return ()-wait m wanted = fmap snd $ waitF m (const (wanted,()))---- | 'waitWith' takes the 'MSemN' and a pure function that takes the available quantity and computes the--- amount wanted and a second value. The value wanted is stricly evaluated but the second value is--- returned lazily.------ 'waitF' allow positive, zero, and negative wanted values. Waiters may block, and will be handled--- fairly in FIFO order.------ If 'waitF' returns without interruption then it left the 'MSemN' with a remaining quantity that was--- greater than or equal to zero. If 'waitF' or the provided function are interrupted then no--- quantity is lost. If 'waitF' returns without interruption then it is known that each previous--- waiter has each definitely either been interrupted or has retured without interruption.------ Note: A long running pure function will block all other access to the 'MSemN' while it is--- evaluated.-waitF :: Integral i => (MSemN i) -> (i -> (i,b)) -> IO (i,b)-{-# SPECIALIZE waitF :: MSemN Int -> (Int -> (Int,b)) -> IO (Int,b) #-}-{-# SPECIALIZE waitF :: MSemN Integer -> (Integer -> (Integer,b)) -> IO (Integer,b) #-}-waitF m f = mask_ . withMVar (queueWait m) $ \ () -> do- (out@(wanted,_),mustWait) <- modifyMVar (mSem m) $ \ ms -> do- let outVal@(wantedVal,_) = f (avail ms)- -- assert that headDown is Nothing (from prior 'new' or 'signal' or 'cleanup')- -- wantedVal gets forced by the (<=) condition here:- if wantedVal <= avail ms- then do- let avail'down = avail ms - wantedVal -- avail'down is never negative, barring overflow- ms' <- evaluate ms { avail = avail'down }- return (ms', (outVal,False))- else do- ms' <- evaluate ms { headWants = Just wantedVal }- return (ms', (outVal,True))- -- mask_ is needed above because either (Just wantedVal) may be set here and this means we need to- -- get the `onException` setup without being interrupted, or avail'down was set and we must finish- -- 'waitF' without being interrupted so that a 'bracket' can ensure a matching 'signal' can- -- protect the returned quantity.- when mustWait $ do- let cleanup = uninterruptibleMask_ $ modifyMVar_ (mSem m) $ \ms -> do- mStale <- tryTakeMVar (headWait m)- let avail' = avail ms + maybe 0 (const wanted) mStale- evaluate ms {avail = avail', headWants = Nothing}- takeMVar (headWait m) `onException` cleanup -- may not block if a 'signal' or exception has already arrived.- return out---- |'signal' allows positive, zero, and negative values, thus this is also way to remove quantity--- that skips any threads in the 'wait'/'waitF' queue. If the new total is greater than the next--- value being waited for (if present) then the first waiter is woken. If there are queued waiters--- then the next one will wake after a waiter has proceeded and notice the remaining value; thus a--- single 'signal' may result in several waiters obtaining values. Waking waiting threads is--- asynchronous.------ 'signal' may block, but it cannot be interrupted, which allows it to dependably restore value to--- the 'MSemN'. All 'signal', 'signalF', 'peekAvail', and the head waiter may momentarily block in a--- fair FIFO manner.-signal :: Integral i => (MSemN i) -> i -> IO ()-{-# SPECIALIZE signal :: MSemN Int -> Int -> IO () #-}-{-# SPECIALIZE signal :: MSemN Integer -> Integer -> IO () #-}-signal _ 0 = return ()-signal m size = uninterruptibleMask_ $ fmap snd $ signalF m (const (size,()))---- | Instead of providing a fixed change to the available quantity, 'signalF' applies a provided--- pure function to the available quantity to compute the change and a second value. The--- requested change is stricly evaluated but the second value is returned lazily. If the new total is--- greater than the next value being waited for then the first waiter is woken. If there are queued--- waiters then the next one will wake after a waiter has proceeded and notice the remaining value;--- thus a single 'signalF' may result in several waiters obtaining values. Waking waiting threads--- is asynchronous.------ 'signalF' may block, and it can be safely interrupted. If the provided function throws an error--- or is interrupted then it leaves the 'MSemN' unchanged. All 'signal', 'signalF', 'peekAvail', and--- the head waiter may momentarily block in a fair FIFO manner.------ Note: A long running pure function will block all other access to the 'MSemN' while it is--- evaluated.-signalF :: Integral i => (MSemN i) -> (i -> (i,b)) -> IO (i,b)-{-# SPECIALIZE signalF :: MSemN Int -> (Int -> (Int,b)) -> IO (Int,b) #-}-{-# SPECIALIZE signalF :: MSemN Integer -> (Integer -> (Integer,b)) -> IO (Integer,b) #-}-signalF m f = mask_ . modifyMVar (mSem m) $ \ ms -> do- -- Nothing below blocks, not even the putMVar- let out@(size,_) = f (avail ms)- avail' <- evaluate $ avail ms + size -- this forces 'size'- ms' <- case headWants ms of- Just wanted | wanted <= avail' -> do- putMVar (headWait m) ()- let avail'down = avail' - wanted -- avail'down is never negative, barring overflow- evaluate ms { avail = avail'down, headWants = Nothing }- _ -> evaluate ms { avail = avail' }- return (ms',out)---- | 'peekAvail' skips the queue of any blocked 'wait' and 'waitF' threads, but may momentarily--- block on 'signal', 'signalF', other 'peekAvail', and the head waiter. This returns the amount of--- value available to be taken. Using this value without producing unwanted race conditions is left--- up to the programmer.------ 'peekAvail' is an optimized form of \"signalF m (\x -> (0,x))\".------ A version of 'peekAvail' that joins the FIFO queue of 'wait' and 'waitF' can be acheived by--- \"waitF m (\x -> (0,x))\"-peekAvail :: Integral i => (MSemN i) -> IO i-{-# SPECIALIZE peekAvail :: MSemN Int -> IO Int #-}-{-# SPECIALIZE peekAvail :: MSemN Integer -> IO Integer #-}-peekAvail m = withMVar (mSem m) (return . avail)
+ src/Control/Concurrent/MSemN.lhs view
@@ -0,0 +1,319 @@+> {-# LANGUAGE DeriveDataTypeable #-}+> -- | +> -- Module : Control.Concurrent.MSemN+> -- Copyright : (c) Chris Kuklewicz 2011+> -- License : 3 clause BSD-style (see the file LICENSE)+> -- +> -- Maintainer : haskell@list.mightyreason.com+> -- Stability : experimental+> -- Portability : non-portable (concurrency)+> --+> -- Quantity semaphores in which each thread may wait for an arbitrary amount. This modules is+> -- intended to improve on "Control.Concurrent.QSemN".+> -- +> -- This semaphore gracefully handles threads which die while blocked waiting for quantity. The+> -- fairness guarantee is that blocked threads are FIFO. An early thread waiting for a large+> -- quantity will prevent a later thread waiting for a small quantity from jumping the queue.+> --+> -- If 'with' is used to guard a critical section then no quantity of the semaphore will be lost+> -- if the activity throws an exception.+> --+> -- The functions below are generic in (Integral i) with specialization to Int and Integer.+> --+> -- Overflow warning: These operations do not check for overflow errors. If the Integral type is too+> -- small to accept the new total then the behavior of these operations is undefined. Using (MSem+> -- Integer) prevents the possibility of an overflow error.+>+> module Control.Concurrent.MSemN+> (MSemN+> ,new+> ,with+> ,wait+> ,signal+> ,withF+> ,waitF+> ,signalF+> ,peekAvail+> ) where+> +> import Prelude( Integral,Eq,IO,Int,Integer,Maybe(Just,Nothing),Num((+),(-)),Bool(False,True)+> , return,id,const,fmap,snd,maybe,seq+> , (.),(<=),($),($!) )+> import Control.Concurrent.MVar( MVar+> , withMVar,modifyMVar,modifyMVar_,newMVar+> , newEmptyMVar,tryPutMVar,takeMVar,tryTakeMVar )+> import Control.Exception(bracket,bracket_,uninterruptibleMask_,onException,evaluate,mask_)+> import Control.Monad(when)+> import Data.Typeable(Typeable)+> import Data.Word(Word)+> + +The only MVars allocated are the three created be 'new'. Their three roles are+1) to have a FIFO queue of waiters (queueWait)+2) for the head waiter to block on, if necessary (headWait)+3) to protect the actual state of the semaphore (quantityStore)++> -- MS has an invariant that "maybe True (> avail) headWants" is always True.+> data MS i = MS { avail :: !i -- ^ This is the quantity available to be taken from the semaphore.+> , headWants :: !(Maybe i) -- ^ If there is waiter then this is Just the amount being waited for.+> }+> deriving (Eq,Typeable)++> -- | A 'MSemN' is a quantity semaphore, in which the available quantity may be signalled or+> -- waited for in arbitrary amounts.+> data MSemN i = MSemN { quantityStore :: !(MVar (MS i)) -- ^ Used to lock access to state of semaphore quantity.+> , queueWait :: !(MVar ()) -- ^ Used as FIFO queue for waiter, held by head of queue.+> , headWait :: !(MVar ()) -- ^ The head of the waiter queue blocks on headWait.+> }+> deriving (Eq,Typeable)++The data structure for 'MSemN' is slightly more complicated than the one in 'MSem'. Here the+quantityStore holds not just a value of type 'i' but also a 'Maybe i' called 'headWants'.++'headWants' is Nothing when there are no blocked threads waiting on quantity. 'headWants' is (Just+x) when there is at least one blocked thread and the head of the queue needs positive quantity x to+proceed.++There are two possible lifecycles of a wait request. Like in MSem, all waiters do all work while+holding queueWait. This is what forces the waiters into a FIFO order.++The first is when the waiter gets to head of the queue and finds that the quantityStore has enough+in 'avail' to be satisfied. This waiter subtracts its wanted value from 'avail' and returns.++The second is when the waiter does not find a larger enough value in 'avail' must block. It sets+headWants from Nothing to 'Just wanted' and then releases quantityStore, followed by blocked in+headWait. When a signal arrives that puts the available quantity above the value in 'headWants'+then it puts () into 'headWait' to wake the blocked waiting thread. Here the subtraction of the+value in 'Just wanted' from the available quantity is handled by the signalling thread.++The difficulty is maintaining the desired invariants in the face of exceptions. If a frustrated+waiter dies before the 'takeMVar' on 'headWait' succeeds then the waiter's changes to+'quantityStore' must be undone! This requires the 'uninterruptibleMask_' around the onException+action in 'waitF'.++When the head waiter releases the queueWait MVar, either by succeeding or being interrupted, there+are three invariants:++(wait invariant 1) The headWait MVar must be empty.++(wait invariant 2) The headWants value is Nothing.++This means that when a waiter first acquires the queueWait MVar both the above hold. If the waiter+succeeded then there is a progress invariant:++(wait progress invariant) The value of 'avail' is non-negative when wait succeeds.++When the signal operation release the quantityStore MVar then one of three situations holds:++(signal possibility 1) headWants was Nothing and it and headWait are unchanged, or++(signal possibility 2) headWants was (Just x) and it and headWait are unchanged, or++(signal possibility 3) headWants was (Just x) and is changed to Nothing and headWait has () put into it.++If headWait had () put into it then headWants is Nothing. The only way headWants can change back to+(Just x) is if a new waiter does it. This requires the original waiter to hand over the queueWait+MVar, and we can be certain that (wait invariant 1) means that the () put into headWait is taken out+before this handoff.++Thus when a signal first acquires the quantityStore MVar there is a dynamically maintained invariant:++(signal invariant 1) A signal that finds headWants of (Just x) also finds headWait empty.++Note that a () put into headWait signifies amount: it is worth the quantity x in the (Just x) in+headWants that was just changed to Nothing. After (signal possibility 3) only the receiving waiting+thread knows the amount that this () in headWait represents, and only this thread can fix the MSemN+if an exception occurs. The waitF function below is careful to fix MSemN.++> -- |'new' allows positive, zero, and negative initial values. The initial value is forced here to+> -- better localize errors.+> new :: Integral i => i -> IO (MSemN i)+> {-# SPECIALIZE new :: Int -> IO (MSemN Int) #-}+> {-# SPECIALIZE new :: Word -> IO (MSemN Word) #-}+> {-# SPECIALIZE new :: Integer -> IO (MSemN Integer) #-}+> new initial = do+> newMS <- newMVar $! (MS { avail = initial -- this forces initial+> , headWants = Nothing })+> newQueueWait <- newMVar ()+> newHeadWait <- newEmptyMVar+> return (MSemN { quantityStore = newMS+> , queueWait = newQueueWait+> , headWait = newHeadWait })+> +> -- | 'with' takes a quantity of the semaphore to take and hold while performing the provided+> -- operation. 'with' ensures the quantity of the sempahore cannot be lost if there are exceptions.+> -- This uses 'bracket' to ensure 'wait' and 'signal' get called correctly.+> with :: Integral i => MSemN i -> i -> IO a -> IO a+> {-# SPECIALIZE with :: MSemN Int -> Int -> IO a -> IO a #-}+> {-# SPECIALIZE with :: MSemN Word -> Word -> IO a -> IO a #-}+> {-# SPECIALIZE with :: MSemN Integer -> Integer -> IO a -> IO a #-}+> with m wanted = seq wanted $ bracket_ (wait m wanted) (signal m wanted)+> +> -- | 'withF' takes a pure function and an operation. The pure function converts the available+> -- quantity to a pair of the wanted quantity and a returned value. The operation takes the result+> -- of the pure function. 'withF' ensures the quantity of the sempahore cannot be lost if there+> -- are exceptions. This uses 'bracket' to ensure 'waitF' and 'signal' get called correctly.+> --+> -- Note: A long running pure function will block all other access to the 'MSemN' while it is+> -- evaluated.+> withF :: Integral i +> => MSemN i+> -> (i -> (i,b))+> -> ((i,b) -> IO a)+> -> IO a+> {-# SPECIALIZE withF :: MSemN Int -> (Int -> (Int,b)) -> ((Int,b) -> IO a) -> IO a #-}+> {-# SPECIALIZE withF :: MSemN Word -> (Word -> (Word,b)) -> ((Word,b) -> IO a) -> IO a #-}+> {-# SPECIALIZE withF :: MSemN Integer -> (Integer -> (Integer,b)) -> ((Integer,b) -> IO a) -> IO a #-}+> withF m f = bracket (waitF m f) (\(wanted,_) -> signal m wanted)+> +> -- |'wait' allow positive, zero, and negative wanted values. Waiters may block, and will be handled+> -- fairly in FIFO order. Waiters will succeed when the wanted value is less than or equal to the+> -- available value. The FIFO order means that a 'wait' for a large quantity that blocks will prevent later+> -- requests from being considered even if the later requests would be for a small quantity that could be fulfilled.+> --+> -- If 'wait' returns without interruption then it left the 'MSemN' with a remaining quantity that was+> -- greater than or equal to zero. If 'wait' is interrupted then no quantity is lost. If 'wait'+> -- returns without interruption then it is known that each earlier waiter has definitely either been+> -- interrupted or has retured without interruption.+> wait :: Integral i => MSemN i -> i -> IO ()+> {-# SPECIALIZE wait :: MSemN Int -> Int -> IO () #-}+> {-# SPECIALIZE wait :: MSemN Word -> Word -> IO () #-}+> {-# SPECIALIZE wait :: MSemN Integer -> Integer -> IO () #-}+> wait m wanted = seq wanted $ fmap snd $ waitF m (const (wanted,()))+> +> -- | 'waitWith' takes the 'MSemN' and a pure function that takes the available quantity and computes the+> -- amount wanted and a second value. The value wanted is stricly evaluated but the second value is+> -- returned lazily.+> --+> -- 'waitF' allow positive, zero, and negative wanted values. Waiters may block, and will be handled+> -- fairly in FIFO order. Waiters will succeed when the wanted value is less than or equal to the+> -- available value. The FIFO order means that a 'waitF' for a large quantity that blocks will prevent later+> -- requests from being considered even if the later requests would be for a small quantity that could be fulfilled.+> --+> -- If 'waitF' returns without interruption then it left the 'MSemN' with a remaining quantity that was+> -- greater than or equal to zero. If 'waitF' or the provided function are interrupted then no+> -- quantity is lost. If 'waitF' returns without interruption then it is known that each previous+> -- waiter has each definitely either been interrupted or has retured without interruption.+> --+> -- Note: A long running pure function will block all other access to the 'MSemN' while it is+> -- evaluated.+> waitF :: Integral i => MSemN i -> (i -> (i,b)) -> IO (i,b)+> {-# SPECIALIZE waitF :: MSemN Int -> (Int -> (Int,b)) -> IO (Int,b) #-}+> {-# SPECIALIZE waitF :: MSemN Word -> (Word -> (Word,b)) -> IO (Word,b) #-}+> {-# SPECIALIZE waitF :: MSemN Integer -> (Integer -> (Integer,b)) -> IO (Integer,b) #-}+> waitF m f = seq f $ mask_ . withMVar (queueWait m) $ \ () -> do+> -- Assume when queueWait taken: (headWait is empty) AND (headWants is Nothing)+> (out@(wanted,_),mustWait) <- modifyMVar (quantityStore m) $ \ ms -> do+> -- Nothing in this scope can block+> let outVal@(wantedVal,_) = f (avail ms)+> -- assert that headDown is Nothing (from prior 'new' or 'signal' or 'cleanup')+> -- wantedVal gets forced by the (<=) condition here:+> if wantedVal <= avail ms+> then do+> let avail'down = avail ms - wantedVal -- avail'down is never negative, barring overflow+> ms' <- evaluate ms { avail = avail'down }+> return (ms', (outVal,False))+> else do+> ms' <- evaluate ms { headWants = Just wantedVal }+> return (ms', (outVal,True))+> -- quantityStore is now released, queueWait is still held, race with signal now possible+> -- Assert: (headWait is empty) AND (mustWait == (headWants is Just)) at release (point X)+> -- Proof: (headWait is empty) was assumed and is unchanged, and+> -- either mustWait is False and assumed (headWants is Nothing) is unchanged,+> -- or mustWait is True and headWants was set to Just wantedVal+> when mustWait $ do+> let cleanup = uninterruptibleMask_ $ modifyMVar_ (quantityStore m) $ \ms -> do+> recovered <- tryTakeMVar (headWait m)+> let total = avail ms + maybe 0 (const wanted) recovered+> evaluate MS {avail = total, headWants = Nothing}+> takeMVar (headWait m) `onException` cleanup -- takeMVar might not block if a 'signal' or exception has already arrived.+> return out+> -- Invariant when queueWait released: (headWait is empty) AND (headWants is Nothing)+> -- Proof: 1) mustWait is false, so (headWants is Just) was false+> -- so (headWait is empty) AND (headWants is Nothing) was true at (point X)+> -- by LEMMA under signalF this is unchanged by signalF; there has been no race condition+> -- 2) mustWait is true, so (headWants is Just) was true+> -- 2a) takeMVar succeeded so headWait became full since (point X)+> -- this implies signal filled headWait and thus signal ended with (headWait is full)+> -- signal invariant ((headWait is empty) OR (headWants is Nothing)) implies (headWants is Nothing) was set+> -- (headWait is empty) by takeMVar and (headWants is Nothing) by implication+> -- 2b) takeMVar was interrupted, then onException ran cleanup, by uninterruptibleMask_ it succeeded+> -- cleanup's tryTakeMVar ensured (headWait is empty), and+> -- cleanup's modifyMVar_ ensured (headWants is Nothing)++> +> -- |'signal' allows positive, zero, and negative values, thus this is also way to remove quantity+> -- that skips any threads in the 'wait'/'waitF' queue. If the new total is greater than the next+> -- value being waited for (if present) then the first waiter is woken. If there are queued waiters+> -- then the next one will wake after a waiter has proceeded and notice the remaining value; thus a+> -- single 'signal' may result in several waiters obtaining values. Waking waiting threads is+> -- asynchronous.+> --+> -- 'signal' may block, but it cannot be interrupted, which allows it to dependably restore value to+> -- the 'MSemN'. All 'signal', 'signalF', 'peekAvail', and the head waiter may momentarily block in a+> -- fair FIFO manner.+> signal :: Integral i => MSemN i -> i -> IO ()+> {-# SPECIALIZE signal :: MSemN Int -> Int -> IO () #-}+> {-# SPECIALIZE signal :: MSemN Word -> Word -> IO () #-}+> {-# SPECIALIZE signal :: MSemN Integer -> Integer -> IO () #-}+> signal _ 0 = return () -- this also forces 'size'+> signal m size = uninterruptibleMask_ $ fmap snd $ signalF m (const (size,()))+> +> -- | Instead of providing a fixed change to the available quantity, 'signalF' applies a provided+> -- pure function to the available quantity to compute the change and a second value. The+> -- requested change is stricly evaluated but the second value is returned lazily. If the new total is+> -- greater than the next value being waited for then the first waiter is woken. If there are queued+> -- waiters then the next one will wake after a waiter has proceeded and notice the remaining value;+> -- thus a single 'signalF' may result in several waiters obtaining values. Waking waiting threads+> -- is asynchronous.+> --+> -- 'signalF' may block, and it can be safely interrupted. If the provided function throws an error+> -- or is interrupted then it leaves the 'MSemN' unchanged. All 'signal', 'signalF', 'peekAvail', and+> -- the head waiter may momentarily block in a fair FIFO manner.+> --+> -- Note: A long running pure function will block all other access to the 'MSemN' while it is+> -- evaluated.+> signalF :: Integral i+> => MSemN i+> -> (i -> (i,b))+> -> IO (i,b)+> {-# SPECIALIZE signalF :: MSemN Int -> (Int -> (Int,b)) -> IO (Int,b) #-}+> {-# SPECIALIZE signalF :: MSemN Word -> (Word -> (Word,b)) -> IO (Word,b) #-}+> {-# SPECIALIZE signalF :: MSemN Integer -> (Integer -> (Integer,b)) -> IO (Integer,b) #-}+> signalF m f = seq f $ mask_ . modifyMVar (quantityStore m) $ \ ms -> do+> -- Assume: ((headWait is empty) OR (headWants is Nothing))+> -- Nothing below can block+> let out@(size,_) = f (avail ms)+> total <- evaluate $ avail ms + size -- this forces 'size'+> ms' <- case headWants ms of+> Just wanted | wanted <= total -> do+> -- Assumption implies headWait is empty, using putMVar below would never block+> didPlace <- tryPutMVar (headWait m) () +> evaluate $ if didPlace+> then MS { avail = total - wanted, headWants = Nothing } -- always this case+> else MS { avail = total, headWants = Nothing } -- impossible case+> _ -> evaluate ms { avail = total }+> return (ms',out)+> -- Invariant: ((headWait is empty) OR (headWants is Nothing))+> -- Proof: 1) originally (headWants is Nothing), headWait and headWants unchanged, invariant still holds+> -- 2) orignal (Just wanted)+> -- 2a) wanted <= total, headWait becomes filled and headWants becomes Nothing, invariant holds+> -- 2b) wanted > total, headWait and headWants unchanged, invariant still holds+>+> -- LEMMA: if (headWait is empty) AND (headWants is Nothing) holds before signalF then it holds after signalF+> -- Proof: When (headWants is Nothing) both headWait and headWants are unchanged (proof case 1 above)++> -- | 'peekAvail' skips the queue of any blocked 'wait' and 'waitF' threads, but may momentarily+> -- block on 'signal', 'signalF', other 'peekAvail', and the head waiter. This returns the amount of+> -- value available to be taken. Using this value without producing unwanted race conditions is left+> -- up to the programmer.+> --+> -- 'peekAvail' is an optimized form of \"signalF m (\x -> (0,x))\".+> --+> -- A version of 'peekAvail' that joins the FIFO queue of 'wait' and 'waitF' can be acheived by+> -- \"waitF m (\x -> (0,x))\"+> peekAvail :: Integral i => MSemN i -> IO i+> {-# SPECIALIZE peekAvail :: MSemN Int -> IO Int #-}+> {-# SPECIALIZE peekAvail :: MSemN Word -> IO Word #-}+> {-# SPECIALIZE peekAvail :: MSemN Integer -> IO Integer #-}+> peekAvail m = withMVar (quantityStore m) (return . avail)
+ src/Control/Concurrent/MSemN2.hs view
@@ -0,0 +1,262 @@+{-# LANGUAGE DeriveDataTypeable #-}+-- | +-- Module : Control.Concurrent.MSemN2+-- Copyright : (c) Chris Kuklewicz 2011+-- License : 3 clause BSD-style (see the file LICENSE)+-- +-- Maintainer : haskell@list.mightyreason.com+-- Stability : experimental+-- Portability : non-portable (concurrency)+--+-- Quantity semaphores in which each thread may wait for an arbitrary amount. This modules is+-- intended to improve on "Control.Concurrent.QSemN".+-- +-- This semaphore gracefully handles threads which die while blocked waiting for quantity. The+-- fairness guarantee is that blocked threads are FIFO. An early thread waiting for a large+-- quantity will prevent a later thread waiting for a small quantity from jumping the queue.+--+-- If 'with' is used to guard a critical section then no quantity of the semaphore will be lost+-- if the activity throws an exception.+--+-- The functions below are generic in (Integral i) with specialization to Int and Integer.+--+-- Overflow warning: These operations do not check for overflow errors. If the Integral type is too+-- small to accept the new total then the behavior of these operations is undefined. Using (MSem+-- Integer) prevents the possibility of an overflow error.+module Control.Concurrent.MSemN2+ (MSemN+ ,new+ ,with+ ,wait+ ,signal+ ,withF+ ,waitF+ ,signalF+ ,peekAvail+ ) where++import Prelude( Integral,Eq,IO,Int,Integer,Maybe(Just,Nothing),Num((+),(-)),Bool(False,True)+ , return,id,const,fmap,snd,seq+ , (.),(<=),($),($!) )+import Control.Concurrent.MVar( MVar+ , withMVar,modifyMVar,newMVar+ , newEmptyMVar,tryPutMVar,takeMVar,tryTakeMVar )+import Control.Exception(bracket,bracket_,uninterruptibleMask_,evaluate,mask_)+import Control.Monad(when,void)+import Data.Maybe(fromMaybe)+import Data.Typeable(Typeable)+import Data.Word(Word)++{- ++The only MVars allocated are the three created be 'new'. Their three roles are+1) to have a FIFO queue of waiters+2) for the head waiter to block on+3) to protect the quantity state of the semaphore and the head waiter++-}++-- MS has an invariant that "maybe True (> avail) headWants" is always True.+data MS i = MS { avail :: !i -- ^ This is the quantity available to be taken from the semaphore.+ , headWants :: !(Maybe i) -- ^ If there is waiter then this is Just the amount being waited for.+ }+ deriving (Eq,Typeable)++-- | A 'MSemN' is a quantity semaphore, in which the available quantity may be signalled or+-- waited for in arbitrary amounts.+data MSemN i = MSemN { quantityStore :: !(MVar (MS i)) -- ^ Used to lock access to state of semaphore quantity.+ , queueWait :: !(MVar ()) -- ^ Used as FIFO queue for waiter, held by head of queue.+ , headWait :: !(MVar i) -- ^ The head of the waiter queue blocks on headWait.+ }+ deriving (Eq,Typeable)++-- |'new' allows positive, zero, and negative initial values. The initial value is forced here to+-- better localize errors.+new :: Integral i => i -> IO (MSemN i)+{-# SPECIALIZE new :: Int -> IO (MSemN Int) #-}+{-# SPECIALIZE new :: Word -> IO (MSemN Word) #-}+{-# SPECIALIZE new :: Integer -> IO (MSemN Integer) #-}+new initial = do+ newMS <- newMVar $! (MS { avail = initial -- this forces 'initial'+ , headWants = Nothing })+ newQueueWait <- newMVar ()+ newHeadWait <- newEmptyMVar+ return (MSemN { quantityStore = newMS+ , queueWait = newQueueWait+ , headWait = newHeadWait })++-- | 'with' takes a quantity of the semaphore to take and hold while performing the provided+-- operation. 'with' ensures the quantity of the sempahore cannot be lost if there are exceptions.+-- This uses 'bracket' to ensure 'wait' and 'signal' get called correctly.+with :: Integral i => MSemN i -> i -> IO a -> IO a+{-# SPECIALIZE with :: MSemN Int -> Int -> IO a -> IO a #-}+{-# SPECIALIZE with :: MSemN Word -> Word -> IO a -> IO a #-}+{-# SPECIALIZE with :: MSemN Integer -> Integer -> IO a -> IO a #-}+with m wanted = seq wanted $ bracket_ (wait m wanted) (uninterruptibleMask_ $ signal m wanted)++-- | 'withF' takes a pure function and an operation. The pure function converts the available+-- quantity to a pair of the wanted quantity and a returned value. The operation takes the result+-- of the pure function. 'withF' ensures the quantity of the sempahore cannot be lost if there+-- are exceptions. This uses 'bracket' to ensure 'waitF' and 'signal' get called correctly.+--+-- Note: A long running pure function will block all other access to the 'MSemN' while it is+-- evaluated.+withF :: Integral i + => MSemN i+ -> (i -> (i,b))+ -> ((i,b) -> IO a)+ -> IO a+{-# SPECIALIZE withF :: MSemN Int -> (Int -> (Int,b)) -> ((Int,b) -> IO a) -> IO a #-}+{-# SPECIALIZE withF :: MSemN Word -> (Word -> (Word,b)) -> ((Word,b) -> IO a) -> IO a #-}+{-# SPECIALIZE withF :: MSemN Integer -> (Integer -> (Integer,b)) -> ((Integer,b) -> IO a) -> IO a #-}+withF m f = bracket (waitF m f) (\(wanted,_) -> uninterruptibleMask_ $ signal m wanted)++-- |'wait' allow positive, zero, and negative wanted values. Waiters may block, and will be handled+-- fairly in FIFO order.+--+-- If 'wait' returns without interruption then it left the 'MSemN' with a remaining quantity that was+-- greater than or equal to zero. If 'wait' is interrupted then no quantity is lost. If 'wait'+-- returns without interruption then it is known that each earlier waiter has definitely either been+-- interrupted or has retured without interruption.+wait :: Integral i => MSemN i -> i -> IO ()+{-# SPECIALIZE wait :: MSemN Int -> Int -> IO () #-}+{-# SPECIALIZE wait :: MSemN Word -> Word -> IO () #-}+{-# SPECIALIZE wait :: MSemN Integer -> Integer -> IO () #-}+wait m wanted = seq wanted $ fmap snd $ waitF m (const (wanted,()))++-- | 'waitWith' takes the 'MSemN' and a pure function that takes the available quantity and computes the+-- amount wanted and a second value. The value wanted is stricly evaluated but the second value is+-- returned lazily.+--+-- 'waitF' allow positive, zero, and negative wanted values. Waiters may block, and will be handled+-- fairly in FIFO order.+--+-- If 'waitF' returns without interruption then it left the 'MSemN' with a remaining quantity that was+-- greater than or equal to zero. If 'waitF' or the provided function are interrupted then no+-- quantity is lost. If 'waitF' returns without interruption then it is known that each previous+-- waiter has each definitely either been interrupted or has retured without interruption.+--+-- Note: A long running pure function will block all other access to the 'MSemN' while it is+-- evaluated.+waitF :: Integral i => MSemN i -> (i -> (i,b)) -> IO (i,b)+{-# SPECIALIZE waitF :: MSemN Int -> (Int -> (Int,b)) -> IO (Int,b) #-}+{-# SPECIALIZE waitF :: MSemN Word -> (Word -> (Word,b)) -> IO (Word,b) #-}+{-# SPECIALIZE waitF :: MSemN Integer -> (Integer -> (Integer,b)) -> IO (Integer,b) #-}+waitF m f = seq f $ mask_ . withMVar (queueWait m) $ \ () -> do+ (out,mustWait) <- modifyMVar (quantityStore m) $ \ ms -> do+ -- Assume: ((headWait is empty) OR (headWants is Nothing))+ -- Nothing in this scope can block+ --+ -- headWait might be full here if the predecessor waitF blocked and died and signal (tried to)+ -- feed it.+ recovered <- fmap (fromMaybe 0) (tryTakeMVar (headWait m))+ let total = avail ms + recovered+ outVal@(wantedVal,_) = f total+ if wantedVal <= total -- forces wantedVal+ then do+ ms' <- evaluate MS { avail = total - wantedVal, headWants = Nothing }+ return (ms', (outVal,False))+ else do+ ms' <- evaluate MS { avail = total, headWants = Just wantedVal }+ return (ms', (outVal,True))+ -- quantityStore is now released, queueWait is still held, race with signal now possible+ -- Assert: (headWait is empty) AND (mustWait == (headWants is Just)) at release+ -- Proof: tryTakeMVar forced (headWait is empty), and+ -- the if-then-else branches ensured (mustWait == (headWants is Just))+ -- This assertion implies ((headWait is empty) OR (headWants is Nothing)) invariant holds (point X)+ when mustWait (void (takeMVar (headWait m)))+ return out+ -- Invariant: ((headWait is empty) OR (headWants is Nothing))+ -- Proof: 1) mustWait was false+ -- nothing happened since (point X) except perhaps race with signal+ -- signal maintained invariant+ -- 2) mustWait was true+ -- 2a) takeMVar succeeded so headWait became full since (point X)+ -- this implies signal filled headWait and thus signal ended with (headWait is full)+ -- signal invariant ((headWait is empty) OR (headWants is Nothing)) implies (headWants is Nothing) was set+ -- (headWait is empty) by takeMVar and (headWants is Nothing) by implication+ -- 2b) takeMVar was interrupted and thus did nothing+ -- nothing happened since (point X) except perhaps race with signal+ -- signal maintained invariant++-- |'signal' allows positive, zero, and negative values, thus this is also way to remove quantity+-- that skips any threads in the 'wait'/'waitF' queue. If the new total is greater than the next+-- value being waited for (if present) then the first waiter is woken. If there are queued waiters+-- then the next one will wake after a waiter has proceeded and notice the remaining value; thus a+-- single 'signal' may result in several waiters obtaining values. Waking waiting threads is+-- asynchronous.+--+-- 'signal' may block, but it cannot be interrupted, which allows it to dependably restore value to+-- the 'MSemN'. All 'signal', 'signalF', 'peekAvail', and the head waiter may momentarily block in a+-- fair FIFO manner.+signal :: Integral i => MSemN i -> i -> IO ()+{-# SPECIALIZE signal :: MSemN Int -> Int -> IO () #-}+{-# SPECIALIZE signal :: MSemN Word -> Word -> IO () #-}+{-# SPECIALIZE signal :: MSemN Integer -> Integer -> IO () #-}+signal _ 0 = return () -- this case forces 'size'+signal m size = fmap snd $ signalF m (const (size,()))++-- | Instead of providing a fixed change to the available quantity, 'signalF' applies a provided+-- pure function to the available quantity to compute the change and a second value. The+-- requested change is stricly evaluated but the second value is returned lazily. If the new total is+-- greater than the next value being waited for then the first waiter is woken. If there are queued+-- waiters then the next one will wake after a waiter has proceeded and notice the remaining value;+-- thus a single 'signalF' may result in several waiters obtaining values. Waking waiting threads+-- is asynchronous.+--+-- 'signalF' may block, and it can be safely interrupted. If the provided function throws an error+-- or is interrupted then it leaves the 'MSemN' unchanged. All 'signal', 'signalF', 'peekAvail', and+-- the head waiter may momentarily block in a fair FIFO manner.+--+-- Note: A long running pure function will block all other access to the 'MSemN' while it is+-- evaluated.+signalF :: Integral i+ => MSemN i+ -> (i -> (i,b))+ -> IO (i,b)+{-# SPECIALIZE signalF :: MSemN Int -> (Int -> (Int,b)) -> IO (Int,b) #-}+{-# SPECIALIZE signalF :: MSemN Word -> (Word -> (Word,b)) -> IO (Word,b) #-}+{-# SPECIALIZE signalF :: MSemN Integer -> (Integer -> (Integer,b)) -> IO (Integer,b) #-}+signalF m f = seq f $ mask_ . modifyMVar (quantityStore m) $ \ ms -> do+ -- Assume: ((headWait is empty) OR (headWants is Nothing))+ -- Nothing in this scope can block+ let out@(size,_) = f (avail ms)+ ms' <- case headWants ms of+ Nothing -> evaluate ms { avail = avail ms + size }+ Just wantedVal -> do+ -- Because headWants is Just _ the assumption implies headWait is empty+ let total = avail ms + size+ if wantedVal <= total+ then do+ _didPlace <- tryPutMVar (headWait m) wantedVal -- _didPlace is always True+ evaluate MS { avail = total - wantedVal, headWants = Nothing }+ else do+ evaluate ms { avail = total }+ return (ms',out)+ -- Invariant: ((headWait is empty) OR (headWants is Nothing))+ -- Proof: Assume invariant originally holds when taking quantityStore+ -- 1) headWants originally Nothing, headWants and headWait unchanged, invariant still holds+ -- 2) headWants originally Just _ implies, by assumption, that (headWait is empty)+ -- if-then-branch: headWants changed to Nothing and headWait changed to filled, invariant satisfied+ -- if-else-branch: headWants and headWait unchanged, invariant still holds++-- | 'peekAvail' skips the queue of any blocked 'wait' and 'waitF' threads, but may momentarily+-- block on 'signal', 'signalF', other 'peekAvail', and the head waiter. This returns the amount of+-- value available to be taken. Using this value without producing unwanted race conditions is left+-- up to the programmer.+--+-- 'peekAvail' is an optimized form of \"signalF m (\x -> (0,x))\".+--+-- Quantity that has been passed to a blocked waiter but not picked up is not counted. If the+-- blocked waiter is killed before picking it up then the passed quantity will be recovered by the+-- next waiter. In this exceptional case this next waiter may see an available total that is+-- different than returned by peekAvail.+--+-- A version of 'peekAvail' that joins the FIFO queue of 'wait' and 'waitF' can be acheived by+-- \"waitF m (\x -> (0,x))\" but this will block if x is negative. On the other hand this method+-- will see the total including any recovered quantity.+peekAvail :: Integral i => MSemN i -> IO i+{-# SPECIALIZE peekAvail :: MSemN Int -> IO Int #-}+{-# SPECIALIZE peekAvail :: MSemN Word -> IO Word #-}+{-# SPECIALIZE peekAvail :: MSemN Integer -> IO Integer #-}+peekAvail m = withMVar (quantityStore m) (return . avail)
+ src/Control/Concurrent/SSem.hs view
@@ -0,0 +1,77 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.SSem+-- Copyright : (c) Chris Kuklewicz, 2012+-- License : BSD-style+-- +-- Maintainer : haskell@list.mightyreason.com+-- Stability : experimental+-- Portability : non-portable (concurrency)+--+-- Very simple quantity semaphore.+--+-----------------------------------------------------------------------------+module Control.Concurrent.SSem( SSem,new+ , withSem,wait,signal,tryWait+ , withSemN,waitN,signalN,tryWaitN+ , getValue) where++import Control.Concurrent.STM.SSemInternals(SSem(SSem))+import qualified Control.Concurrent.STM.SSem as S(wait,signal,tryWait,waitN,signalN,tryWaitN,getValue)+import Control.Concurrent.STM.TVar(newTVarIO)+import Control.Exception(bracket_)+import Control.Monad.STM(atomically)++-- | Create a new semaphore with the given argument as the initially available quantity. This+-- allows new semaphores to start with a negative, zero, or positive quantity.+new :: Int -> IO SSem+new = fmap SSem . newTVarIO++-- | It is recommended that all paired uses of 'wait' and 'signal' use the 'with' bracketed form+-- to ensure exceptions safety.+withSem :: SSem -> IO a -> IO a+withSem s = bracket_ (wait s) (signal s)++-- | It is recommended that all paired uses of 'waitN' and 'signalN' use the 'withN'+-- bracketed form to ensure exceptions safety.+withSemN :: SSem -> Int -> IO a -> IO a+withSemN s i = bracket_ (waitN s i) (signalN s i)++-- | Try to take a unit of value from the semaphore. This succeeds when the current quantity is+-- positive, and then reduces the quantity by one. Otherwise this will block and 'retry' until it+-- succeeds or is killed. This will never result in a negative quantity. If several threads are+-- retying then which one succeeds next is undefined -- an unlucky thread might starve.+wait :: SSem -> IO ()+wait = atomically . S.wait++-- | Try to take the given value from the semaphore. This succeeds when the quantity is greater or+-- equal to the given value, and then subtracts the given value from the quantity. Otherwise this+-- will block and 'retry' until it succeeds or is killed. This will never result in a negative+-- quantity. If several threads are retrying then which one succeeds next is undefined -- an+-- unlucky thread might starve.+waitN :: SSem -> Int-> IO ()+waitN s i = atomically (S.waitN s i)++-- | Signal that single unit of the semaphore is available. This increases the available quantity+-- by one.+signal :: SSem -> IO ()+signal = atomically . S.signal++-- | Signal that many units of the semaphore are available. This changes the available quantity by+-- adding the passed size.+signalN :: SSem-> Int -> IO ()+signalN s i = atomically (S.signalN s i)++-- | Non-waiting version of wait. `tryWait s` is defined as `tryWaitN s 1`+tryWait :: SSem -> IO (Maybe Int)+tryWait = atomically . S.tryWait++-- | Non-waiting version of waitN. It either takes the quantity from the semaphore like+-- waitN and returns `Just value taken` or finds insufficient quantity to take and returns+-- Nothing+tryWaitN :: SSem -> Int -> IO (Maybe Int)+tryWaitN s i = atomically (S.tryWaitN s i)++-- | This returns the current quantity in the semaphore. This is diffucult to use due to race conditions.+getValue :: SSem -> IO Int+getValue = atomically . S.getValue
+ src/Control/Concurrent/STM/SSem.hs view
@@ -0,0 +1,75 @@+-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.STM.SSem+-- Copyright : (c) Chris Kuklewicz, 2012+-- License : BSD-style+-- +-- Maintainer : haskell@list.mightyreason.com+-- Stability : experimental+-- Portability : non-portable (concurrency)+--+-- Very simple quantity semaphore.+--+-----------------------------------------------------------------------------+module Control.Concurrent.STM.SSem(SSem, new, wait, signal, tryWait+ , waitN, signalN, tryWaitN+ , getValue) where++import Control.Monad.STM(STM,retry)+import Control.Concurrent.STM.TVar(newTVar,readTVar,writeTVar)+import Control.Concurrent.STM.SSemInternals(SSem(SSem))++-- | Create a new semaphore with the given argument as the initially available quantity. This+-- allows new semaphores to start with a negative, zero, or positive quantity.+new :: Int -> STM SSem+new = fmap SSem . newTVar++-- | Try to take a unit of value from the semaphore. This succeeds when the current quantity is+-- positive, and then reduces the quantity by one. Otherwise this will 'retry'. This will never+-- result in a negative quantity. If several threads are retying then which one succeeds next is+-- undefined -- an unlucky thread might starve.+wait :: SSem -> STM ()+wait = flip waitN 1++-- | Try to take the given value from the semaphore. This succeeds when the quantity is greater or+-- equal to the given value, and then subtracts the given value from the quantity. Otherwise this+-- will 'retry'. This will never result in a negative quantity. If several threads are retrying+-- then which one succeeds next is undefined -- an unlucky thread might starve.+waitN :: SSem -> Int -> STM ()+waitN (SSem s) i = do+ v <- readTVar s+ if v >= i+ then writeTVar s $! v-i+ else retry++-- | Signal that single unit of the semaphore is available. This increases the available quantity+-- by one.+signal :: SSem -> STM ()+signal = flip signalN 1++-- | Signal that many units of the semaphore are available. This changes the available quantity by+-- adding the passed size.+signalN :: SSem -> Int -> STM ()+signalN (SSem s) i = do+ v <- readTVar s+ writeTVar s $! v+i++-- | Non-retrying version of 'wait'. `tryWait s` is defined as `tryN s 1`+tryWait :: SSem -> STM (Maybe Int)+tryWait = flip tryWaitN 1++-- | Non-retrying version of waitN. It either takes the quantity from the semaphore like+-- waitN and returns `Just value taken` or finds insufficient quantity to take and returns+-- Nothing+tryWaitN :: SSem -> Int -> STM (Maybe Int)+tryWaitN (SSem s) i = do+ v <- readTVar s+ if v >= i+ then do writeTVar s $! v-i+ return (Just i)+ else return Nothing++-- | Return the current quantity in the semaphore. This is potentially useful in a larger STM+-- transaciton and less useful as `atomically getValueSem :: IO Int` due to race conditions.+getValue :: SSem -> STM Int+getValue (SSem s) = readTVar s
+ src/Control/Concurrent/STM/SSemInternals.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE CPP, StandaloneDeriving, DeriveDataTypeable #-}++-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.STM.SSemInternals+-- Copyright : (c) Chris Kuklewicz, 2012+-- License : BSD-style+-- +-- Maintainer : haskell@list.mightyreason.com+-- Stability : experimental+-- Portability : non-portable (concurrency)+--+-- Very simple quantity semaphore. Declared here so that private constructor+-- can be shared in both STM and IO APIs but hidden from user.+--+-----------------------------------------------------------------------------++module Control.Concurrent.STM.SSemInternals(SSem(SSem)) where++import Control.Concurrent.STM.TVar(TVar)+import Data.Typeable(Typeable) -- Typeable(typeOf),TyCon,mkTyCon,mkTyConApp)++#include "Typeable.h"++newtype SSem = SSem (TVar Int) deriving (Eq)++INSTANCE_TYPEABLE0(SSem,semTc,"SSem")
+ tests/TestKillSem.hs view
@@ -0,0 +1,249 @@+{- some output from log of "cabal test", three old modules fail, three new modules pass:++Test SampleVar+0: forkIO read thread 1+0: stop thread 1+1: read interrupted+0: write sv #1+0: write sv #2 with timeout+0: timeout triggered, write sv #2 blocked, FAIL+++Test QSem+0: forkIO wait thread 1+0: stop thread 1+1: wait interrupted+0: signal q #1+0: forkIO wait thread 2+0: forkIO wait thread 3+0: signal q #2+2: wait done+0: stop thread 2+0: stop thread 3+3: wait interrupted (QUANTITY LOST) FAIL+False+++Test QSemN+0: forkIO wait thread 1+0: stop thread 1+1: wait interrupted+0: signal q #1+0: forkIO wait thread 2+0: forkIO wait thread 3+0: signal q #2+2: wait done+0: stop thread 2+0: stop thread 3+3: wait interrupted (QUANTITY LOST) FAIL+False+Expected 3 Failures for above code++++Test MSampleVar+0: forkIO read thread 1+0: stop thread 1+1: read interrupted+0: write sv #1+0: write sv #2 with timeout+0: write sv #2 returned, PASS+++Test MSem+0: forkIO wait thread 1+0: stop thread 1+1: wait interrupted+0: signal q #1+0: forkIO wait thread 2+2: wait done+0: forkIO wait thread 3+0: signal q #2+3: wait done (QUANTITY CONSERVED) PASS+0: stop thread 2+0: stop thread 3+True+++Test MSemN+0: forkIO wait thread 1+0: stop thread 1+1: wait interrupted+0: signal q #1+0: forkIO wait thread 2+2: wait done+0: forkIO wait thread 3+0: signal q #2+3: wait done (QUANTITY CONSERVED) PASS+0: stop thread 2+0: stop thread 3+True+Test suite TestSafeSemaphore: PASS+Test suite logged to: dist/test/SafeSemaphore-0.8.0-TestSafeSemaphore.log++-}+module Main where++import Prelude hiding (read)+import Control.Concurrent+import Control.Exception+import Control.Concurrent.QSem+import Control.Concurrent.QSemN+import qualified Control.Concurrent.MSem as MSem+import qualified Control.Concurrent.MSemN as MSemN+import qualified Control.Concurrent.MSemN2 as MSemN2+import qualified Control.Concurrent.SSem as SSem+import Control.Concurrent.MVar+import Test.HUnit+import System.Exit+import Control.Concurrent.SampleVar+import Control.Concurrent.MSampleVar as MSV+import System.Timeout++delay = threadDelay (1000*100)+--delay = yield -- now causes tests to fail in ghc 7.4++fork x = do m <- newEmptyMVar+ t <- forkIO (finally x (putMVar m ()))+ delay+ return (t,m)++stop (t,m) = do killThread t+ delay+ takeMVar m++-- True if test passed, False if test failed+-- This expects FIFO semantics for the waiters+testSem :: Integral n + => String+ -> (n -> IO a) + -> (a->IO ()) + -> (a -> IO ()) + -> IO Bool+testSem name new wait signal = do+ putStrLn ("\n\nTest "++ name)+ q <- new 0++ putStrLn "0: forkIO wait thread 1"+ (t1,m1) <- fork $ do+ wait q `onException` (putStrLn "1: wait interrupted")+ putStrLn "1: wait done UNEXPECTED"+ putStrLn "0: stop thread 1"+ stop (t1,m1)+ putStrLn "0: signal q #1"+ signal q+ delay++ putStrLn "0: forkIO wait thread 2"+ (t2,m2) <- fork $ do+ wait q `onException` (putStrLn "2: wait interrupted UNEXPECTED")+ putStrLn "2: wait done"+ delay++ result <- newEmptyMVar+ putStrLn "0: forkIO wait thread 3"+ (t3,m3) <- fork $ do+ wait q `onException` (putStrLn "3: wait interrupted (QUANTITY LOST) FAIL" >> putMVar result False)+ putStrLn "3: wait done (QUANTITY CONSERVED) PASS"+ putMVar result True+ putStrLn "0: signal q #2"+ signal q+ delay++ putStrLn "0: stop thread 2"+ stop (t2,m2)+ putStrLn "0: stop thread 3"+ stop (t3,m3)+ r <- takeMVar result+ print r+ return r++testSV name newEmpty read write = do+ putStrLn ("\n\nTest "++ name)+ sv <- newEmpty+ putStrLn "0: forkIO read thread 1"+ (t1,m1) <- fork $ do+ read sv `onException` (putStrLn "1: read interrupted")+ putStrLn "1: read done UNEXPECTED"+ putStrLn "0: stop thread 1"+ stop (t1,m1)+ putStrLn "0: write sv #1"+ write sv 1+ putStrLn "0: write sv #2 with timeout"+ m <- timeout (1000*100) (write sv 2)+ case m of+ Nothing -> do+ putStrLn "0: timeout triggered, write sv #2 blocked, FAIL"+ return False+ Just () -> do+ putStrLn "0: write sv #2 returned, PASS"+ return True++-- True if test passed, False if test failed+-- This does not expect FIFO semantics for the waiters, uses getValue instead+testSSem :: Integral n + => String+ -> (n -> IO a) + -> (a->IO ()) + -> (a -> IO ()) + -> (a -> IO Int)+ -> IO Bool+testSSem name new wait signal getValue = do+ putStrLn ("\n\nTest "++ name)+ q <- new 0++ putStrLn "0: forkIO wait thread 1"+ (t1,m1) <- fork $ do+ wait q `onException` (putStrLn "1: wait interrupted")+ putStrLn "1: wait done UNEXPECTED"+ putStrLn "0: stop thread 1"+ stop (t1,m1)+ putStrLn "0: signal q #1"+ signal q+ delay++ putStrLn "0: forkIO wait thread 2"+ (t2,m2) <- fork $ do+ wait q `onException` (putStrLn "2: wait interrupted")+ putStrLn "2: wait done"+ delay++ putStrLn "0: forkIO wait thread 3"+ (t3,m3) <- fork $ do+ wait q `onException` (putStrLn "3: wait interrupted")+ putStrLn "3: wait done"+ delay++ putStrLn "0: signal q #2"+ signal q+ delay++ putStrLn "0: stop thread 2"+ stop (t2,m2)+ putStrLn "0: stop thread 3"+ stop (t3,m3)+ r <- getValue q+ putStrLn $ "Final Value "++show r+ return (r==0)++testOldSV = test $ testSV "SampleVar" newEmptySampleVar readSampleVar writeSampleVar+testNewSV = test $ testSV "MSampleVar" newEmptySV readSV writeSV++testsQ = TestList . (testOldSV:) . map test $+ [ testSem "QSem" newQSem waitQSem signalQSem+ , testSem "QSemN" newQSemN (flip waitQSemN 1) (flip signalQSemN 1)+ ]++testsM = TestList . (testNewSV:) . map test $+ [ testSem "MSem" MSem.new MSem.wait MSem.signal+ , testSem "MSemN" MSemN.new (flip MSemN.wait 1) (flip MSemN.signal 1)+ , testSem "MSemN2" MSemN2.new (flip MSemN2.wait 1) (flip MSemN2.signal 1)+ , testSSem "SSem" SSem.new SSem.wait SSem.signal SSem.getValue+ ]++-- This is run by "cabal test"+main = do+ runTestTT testsQ+ putStrLn "Expected 3 Failures for above code\n"+ c <- runTestTT testsM+ if failures c == 0 then exitSuccess else exitFailure