SafeSemaphore 0.6.0 → 0.7.0
raw patch · 4 files changed
+711/−94 lines, 4 filesdep +containersPVP ok
version bump matches the API change (PVP)
Dependencies added: containers
API changes (from Hackage documentation)
- Control.Concurrent.MSem: instance Eq MS
- Control.Concurrent.MSem: instance Eq MSem
- Control.Concurrent.MSem: instance Typeable MS
- Control.Concurrent.MSem: instance Typeable MSem
- Control.Concurrent.MSemN: instance Eq MS
- Control.Concurrent.MSemN: instance Eq MSemN
- Control.Concurrent.MSemN: instance Typeable MS
- Control.Concurrent.MSemN: instance Typeable MSemN
+ Control.Concurrent.FairRWLock: F :: FRW
+ Control.Concurrent.FairRWLock: R :: TMap -> FRW
+ Control.Concurrent.FairRWLock: RWLock'acquireRead :: RWLockExceptionKind
+ Control.Concurrent.FairRWLock: RWLock'acquireWrite :: RWLockExceptionKind
+ Control.Concurrent.FairRWLock: RWLock'releaseRead :: RWLockExceptionKind
+ Control.Concurrent.FairRWLock: RWLock'releaseWrite :: RWLockExceptionKind
+ Control.Concurrent.FairRWLock: RWLockException :: ThreadId -> RWLockExceptionKind -> String -> RWLockException
+ Control.Concurrent.FairRWLock: ReaderKind :: TSet -> LockKind
+ Control.Concurrent.FairRWLock: W :: (ThreadId, (Int, Int)) -> FRW
+ Control.Concurrent.FairRWLock: WriterKind :: ThreadId -> LockKind
+ Control.Concurrent.FairRWLock: acquireRead :: RWLock -> IO ()
+ Control.Concurrent.FairRWLock: acquireWrite :: RWLock -> IO ()
+ Control.Concurrent.FairRWLock: checkLock :: RWLock -> IO (Int, Int)
+ Control.Concurrent.FairRWLock: data FRW
+ Control.Concurrent.FairRWLock: data LockKind
+ Control.Concurrent.FairRWLock: data RWLock
+ Control.Concurrent.FairRWLock: data RWLockException
+ Control.Concurrent.FairRWLock: data RWLockExceptionKind
+ Control.Concurrent.FairRWLock: instance Eq LockKind
+ Control.Concurrent.FairRWLock: instance Eq LockUser
+ Control.Concurrent.FairRWLock: instance Exception RWLockException
+ Control.Concurrent.FairRWLock: instance Ord LockKind
+ Control.Concurrent.FairRWLock: instance Show FRW
+ Control.Concurrent.FairRWLock: instance Show LockKind
+ Control.Concurrent.FairRWLock: instance Show RWLockException
+ Control.Concurrent.FairRWLock: instance Show RWLockExceptionKind
+ Control.Concurrent.FairRWLock: instance Typeable LockUser
+ Control.Concurrent.FairRWLock: instance Typeable RWLockException
+ Control.Concurrent.FairRWLock: instance Typeable RWLockExceptionKind
+ Control.Concurrent.FairRWLock: new :: IO RWLock
+ Control.Concurrent.FairRWLock: peekLock :: RWLock -> IO (FRW, [LockKind])
+ Control.Concurrent.FairRWLock: releaseRead :: RWLock -> IO (Either RWLockException ())
+ Control.Concurrent.FairRWLock: releaseWrite :: RWLock -> IO (Either RWLockException ())
+ Control.Concurrent.FairRWLock: type TMap = Map ThreadId Int
+ Control.Concurrent.FairRWLock: type TSet = Set ThreadId
+ Control.Concurrent.FairRWLock: unRK :: LockKind -> TSet
+ Control.Concurrent.FairRWLock: unWK :: LockKind -> ThreadId
+ Control.Concurrent.FairRWLock: withRead :: RWLock -> IO a -> IO a
+ Control.Concurrent.FairRWLock: withWrite :: RWLock -> IO a -> IO a
+ Control.Concurrent.MSem: instance Eq (MSem i)
+ Control.Concurrent.MSem: instance Typeable1 MSem
+ Control.Concurrent.MSemN: instance Eq (MSemN i)
+ Control.Concurrent.MSemN: instance Eq i => Eq (MS i)
+ Control.Concurrent.MSemN: instance Typeable1 MS
+ Control.Concurrent.MSemN: instance Typeable1 MSemN
- Control.Concurrent.MSem: data MSem
+ Control.Concurrent.MSem: data MSem i
- Control.Concurrent.MSem: new :: Integer -> IO MSem
+ Control.Concurrent.MSem: new :: Integral i => i -> IO (MSem i)
- Control.Concurrent.MSem: peekAvail :: MSem -> IO Integer
+ Control.Concurrent.MSem: peekAvail :: Integral i => MSem i -> IO i
- Control.Concurrent.MSem: signal :: MSem -> IO ()
+ Control.Concurrent.MSem: signal :: Integral i => MSem i -> IO ()
- Control.Concurrent.MSem: wait :: MSem -> IO ()
+ Control.Concurrent.MSem: wait :: Integral i => MSem i -> IO ()
- Control.Concurrent.MSem: with :: MSem -> IO a -> IO a
+ Control.Concurrent.MSem: with :: Integral i => MSem i -> IO a -> IO a
- Control.Concurrent.MSemN: data MSemN
+ Control.Concurrent.MSemN: data MSemN i
- Control.Concurrent.MSemN: new :: Integer -> IO MSemN
+ Control.Concurrent.MSemN: new :: Integral i => i -> IO (MSemN i)
- Control.Concurrent.MSemN: peekAvail :: MSemN -> IO Integer
+ Control.Concurrent.MSemN: peekAvail :: Integral i => (MSemN i) -> IO i
- Control.Concurrent.MSemN: signal :: MSemN -> Integer -> IO ()
+ Control.Concurrent.MSemN: signal :: Integral i => (MSemN i) -> i -> IO ()
- Control.Concurrent.MSemN: signalF :: MSemN -> (Integer -> (Integer, b)) -> IO (Integer, b)
+ Control.Concurrent.MSemN: signalF :: Integral i => (MSemN i) -> (i -> (i, b)) -> IO (i, b)
- Control.Concurrent.MSemN: wait :: MSemN -> Integer -> IO ()
+ Control.Concurrent.MSemN: wait :: Integral i => (MSemN i) -> i -> IO ()
- Control.Concurrent.MSemN: waitF :: MSemN -> (Integer -> (Integer, b)) -> IO (Integer, b)
+ Control.Concurrent.MSemN: waitF :: Integral i => (MSemN i) -> (i -> (i, b)) -> IO (i, b)
- Control.Concurrent.MSemN: with :: MSemN -> Integer -> IO a -> IO a
+ Control.Concurrent.MSemN: with :: Integral i => (MSemN i) -> i -> IO a -> IO a
- Control.Concurrent.MSemN: withF :: MSemN -> (Integer -> (Integer, b)) -> ((Integer, 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 +4/−2
- src/Control/Concurrent/FairRWLock.hs +582/−0
- src/Control/Concurrent/MSem.hs +54/−40
- src/Control/Concurrent/MSemN.hs +71/−52
SafeSemaphore.cabal view
@@ -1,5 +1,5 @@ Name: SafeSemaphore-Version: 0.6.0+Version: 0.7.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@@ -17,9 +17,11 @@ Exposed-modules: Control.Concurrent.MSem Control.Concurrent.MSemN Control.Concurrent.MSampleVar- Build-depends: base < 5+ Control.Concurrent.FairRWLock+ Build-depends: base < 5, containers Test-Suite TestSafeSemaphore type: exitcode-stdio-1.0 main-is: tests/TestKillSem.hs build-depends: base < 5, SafeSemaphore, HUnit+
+ src/Control/Concurrent/FairRWLock.hs view
@@ -0,0 +1,582 @@+{-# LANGUAGE DeriveDataTypeable, PatternGuards #-}+{-| Provides a fair RWLock, similar to one from Java.++ <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'.++ 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. If the FairRWLock has a+ bug and finds itself in an impossible state then it will throw an error.++ 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.++ 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.++ 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.++-}+module Control.Concurrent.FairRWLock+ ( RWLock, RWLockException(..), RWLockExceptionKind(..),FRW(..),LockKind(..),TMap,TSet+ , new, peekLock, checkLock+ , acquireRead, acquireWrite+ , releaseRead, releaseWrite+ , withRead, withWrite+ ) where++import Control.Applicative(liftA2)+import Control.Concurrent+import Control.Exception+import Control.Monad((>=>),join,forM_)+import Data.Sequence((<|),(|>),(><),Seq,ViewL(..),ViewR(..))+import qualified Data.Sequence as Seq(empty,viewl,viewr,breakl,spanl)+import qualified Data.Foldable as F(toList)+import Data.Map(Map)+import qualified Data.Map as Map+import Data.Set(Set)+import qualified Data.Set as Set+import Data.Typeable(Typeable)++-- Try to make most impossible data states unrepresentable+type TMap = Map ThreadId Int -- nonempty, all values > 0+type TSet = Set ThreadId -- nonempty++data LockKind = ReaderKind { unRK :: TSet }+ | WriterKind { unWK :: ThreadId }+ deriving (Eq,Ord,Show)++-- LockQ may be empty+-- No duplicate ThreadIds in LockKinds+-- MVar () will be created empty, released once with putMVar+type LockQ = Seq (LockKind,MVar ())++data LockUser = + FreeLock+ | Readers { readerCounts :: TMap -- re-entrant count of reader locks held be each thread+ , queueR :: Maybe ( (ThreadId,MVar ()) -- empty or queue with leading Writer+ , LockQ )+ }+ | Writer { writerID :: ThreadId+ , writerCount -- re-entrant writer locks held by writerID, at least 1+ , readerCount :: !Int -- re-entrant reader locks held by writerID, at least 0+ , queue :: LockQ }+ deriving (Eq,Typeable)+++-- | Opaque type of the fair RWLock.+newtype RWLock = RWL (MVar LockUser)++-- | Exception type thrown or returned by this module. \"Impossible\" conditions get the error thrown+-- and usage problems get the error returned.+data RWLockException = RWLockException ThreadId RWLockExceptionKind String+ deriving (Show,Typeable)++-- | 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+-- 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.+data FRW = F | R TMap | W (ThreadId,(Int,Int)) deriving (Show)++-- | Create a new RWLock which starts in a free and unlocked state.+new :: IO RWLock+new = fmap RWL (newMVar FreeLock)++-- | This is by far the preferred way to acquire a read lock. This uses bracket_ to ensure+-- acquireRead and releaseRead are called correctly around the passed command.+--+-- This ought to ensure releaseRead will not return a (Left error), but if it does then this error+-- will be thrown.+--+-- This can block and be safely interrupted.+withRead :: RWLock -> IO a -> IO a+withRead = liftA2 bracket_ acquireRead (releaseRead >=> either throw return)++-- | This is by far the preferred way to acquire a write lock. This uses bracket_ to ensure+-- acquireWrite and releaseWrite are called correctly around the passed command.+--+-- This ought to ensure releaseWrite will not return a (Left error), but if it does then this error+-- will be thrown.+--+-- This can block and be safely interrupted.+withWrite :: RWLock -> IO a -> IO a+withWrite = liftA2 bracket_ acquireWrite (releaseWrite >=> either throw return)++-- | Observe which threads are holding the lock and which threads are waiting (in order). This is+-- particularly useful for writing tests.+peekLock :: RWLock -> IO (FRW,[LockKind])+peekLock (RWL rwlVar) = withMVar rwlVar $ \ rwd -> return $+ case rwd of+ FreeLock -> (F,[])+ Readers { readerCounts=rcs, queueR=qr } -> (R rcs,maybe [] (\((t,_),q) -> WriterKind t : map fst (F.toList q)) qr)+ Writer { writerID=it, writerCount=wc, readerCount=rc, queue=q } -> (W (it,(rc,wc)), map fst (F.toList q))++-- | checkLocks return a pair of numbers, the first is the count of read locks this thread holds,+-- the second is the number of write locks that this thread holds. This may be useful for sanity+-- checking complex usage of RWLocks.+--+-- This may block and be safely interrupted.+checkLock :: RWLock -> IO (Int,Int)+checkLock (RWL rwlVar) = do+ me <- myThreadId+ withMVar rwlVar $ \ rwd -> return $+ case rwd of+ FreeLock -> (0,0)+ Readers { readerCounts=rcs } ->+ case Map.lookup me rcs of+ Nothing -> (0,0)+ Just rc -> (rc,0)+ Writer { writerID=it, writerCount=wc, readerCount=rc } ->+ if it==me then (rc,wc) else (0,0)++-- | A thread that calls acquireRead must later call releaseRead once for each call to acquireRead.+--+-- If this thread has not previous called acquireRead then releaseRead will do nothing and return a+-- (Left error).+--+-- This can block but cannot be interrupted.+releaseRead :: RWLock -> IO (Either RWLockException ())+releaseRead (RWL rwlVar) = uninterruptibleMask_ $ do+ me <- myThreadId+ releaseRead' False me rwlVar -- False to indicate called 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+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+ impossible s x = throw+ (RWLockException me (if abandon then RWLock'acquireRead else RWLock'releaseRead) (imp s x))+ err :: Show x => String -> x -> IO (LockUser,Either RWLockException ())+ err s x = return . ((,) rwd) . Left $+ (RWLockException me (if abandon then RWLock'acquireRead else RWLock'releaseRead) (s++" : "++show x))+ ret :: LockUser -> IO (LockUser,Either RWLockException ())+ ret x = return (x,Right ())++ -- if there is a bug then dropReader may find an impossible situation when abandoning a thread, and throw an error+ dropReader :: LockQ -> IO LockQ+ dropReader q = do+ let inR (ReaderKind rcs,_) = Set.member me rcs+ inR _ = False+ (pre,myselfPost) = Seq.breakl inR q+ case Seq.viewl myselfPost of+ EmptyL ->+ impossible "failure to abandon acquireRead, RWLock locked by other thread(s) and this thread is not in queue" me+ (myself,mblock) :< post -> do+ let rcs' = Set.delete me (unRK myself) -- safe unRK call+ evaluate $ if Set.null rcs' then pre >< post else pre >< ((ReaderKind rcs',mblock) <| post)++ case rwd of+ FreeLock | abandon ->+ impossible "acquireRead interrupted with unlocked RWLock" me+ | otherwise ->+ 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+ rc' <- evaluate $ pred rc+ ret (w { readerCount=rc' })++ {-ditto-} | abandon -> do+ q' <- dropReader q+ ret (w { queue=q' })++ {-ditto-} | otherwise ->+ err "releaseRead called when not read locked " me++ r@(Readers { readerCounts=rcs,queueR=qR }) ->+ case Map.lookup me rcs of+ Just 1 -> do+ let rcs' = Map.delete me rcs+ if Map.null rcs'+ then case qR of+ Nothing ->+ ret FreeLock+ Just ((wid,mblock),q) -> do+ putMVar mblock ()+ ret (Writer { writerID=wid, writerCount=1, readerCount=0, queue=q })+ else ret (r { readerCounts=rcs' })++ Just rc -> do+ rc' <- evaluate $ pred rc+ rcs' <- evaluate $ Map.insert me rc' rcs+ ret (r { readerCounts=rcs' })++ Nothing | abandon ->+ case qR of+ Nothing ->+ impossible "acquireRead interrupted not holding lock and with no queue" (me,rcs)+ Just (w,q) -> do+ q' <- dropReader q+ ret (r { queueR = Just (w,q') })++ {-ditto-} | otherwise -> + 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.+--+-- If this thread has not previous called acquireWrite then releaseWrite will do nothing and return+-- a (Left error).+--+-- This can block but cannot be interrupted.+releaseWrite :: RWLock -> IO (Either RWLockException ())+releaseWrite (RWL rwlVar) = uninterruptibleMask_ $ do+ me <- myThreadId+ releaseWrite' False me rwlVar -- False to indicate called from releaseWrite++-- Nine non-impossible cases, plus one impossible case+-- Lock is Free+-- I have write lock, I only had 1 write lock and no read locks, promote from LockQ+-- , I only had 1 write lock and some read locks, convert me to reader and promote leading readers+-- , I have many write locks, just decrement the counter+-- Someone else has write lock, abandoning my acquireWrite+-- , releaseWrite called in error+-- Read lock held, releaseWrite called in error+-- , with no queue, abandoning acquireWrite is IMPOSSIBLE+-- , abandoning my leading acquireWrite+-- , abandoning my non-leading acquireWrite+releaseWrite' :: Bool -> ThreadId -> MVar LockUser -> IO (Either RWLockException ())+releaseWrite' abandon me rwlVar = uninterruptibleMask_ . modifyMVar rwlVar $ \ rwd -> do+ let impossible :: Show x => String -> x -> IO a+ impossible s x = throw+ (RWLockException me (if abandon then RWLock'acquireWrite else RWLock'releaseWrite) (imp s x))+ err :: Show x => String -> x -> IO (LockUser,Either RWLockException ())+ err s x = return . ((,) rwd) . Left $+ (RWLockException me (if abandon then RWLock'acquireWrite else RWLock'releaseWrite) (s++" : "++show x))+ ret :: LockUser -> IO (LockUser,Either RWLockException ())+ ret x = return (x,Right ())++ dropWriter :: LockQ -> IO LockQ+ dropWriter q = do+ let inW (WriterKind it,_) = me==it+ inW _ = False+ (pre,myselfPost) = Seq.breakl inW q+ case Seq.viewl myselfPost of+ EmptyL -> + impossible "failure to abandon acquireWrite, RWLock locked by other and not in queue" me+ _ :< post ->+ evaluate $ pre><post++ case rwd of+ FreeLock | abandon ->+ impossible "acquireWrite interrupted with unlocked RWLock" me+ | otherwise ->+ err "cannot releaseWrite lock from unlocked RWLock" me++ w@(Writer { writerID=it, writerCount=wc, readerCount=rc, queue=q }) | it==me -> do+ case (wc,rc) of+ (1,0) -> ret =<< promote q -- if abandon then this is the only valid case+ _ | abandon -> impossible "acquireWrite interrupted with write lock and bad RWLock state" (me,it,wc,rc)+ (1,_) -> ret =<< promoteReader rc q+ (_,_) -> ret (w { writerCount=(pred wc) })++ {-ditto-} | abandon -> do+ q' <- dropWriter q+ ret (w { queue=q' })++ {-ditto-} | otherwise -> do+ err "cannot releaseWrite when not not holding the write lock" (me,it)++ Readers { readerCounts=rcs} | not abandon ->+ err "cannot releaseWrite when RWLock is read locked" (me,rcs)+ + Readers { readerCounts=rcs, queueR=Nothing } ->+ impossible "failure to abandon acquireWrite, RWLock read locked and no queue" (me,rcs)++ r@(Readers { readerCounts=rcs, queueR=Just (w@(it,_),q) }) | it==me -> do+ (rcs'new,qr) <- splitReaders q+ ret (r { readerCounts=Map.union rcs rcs'new, queueR=qr })++ {- ditto -} | otherwise -> do+ q' <- dropWriter q+ ret (r { queueR=Just (w,q') })++ where+ -- | promote when converting from write lock straight to read lock+ promoteReader :: Int -> LockQ -> IO LockUser+ promoteReader rc q = do+ (rcs'new, qr) <- splitReaders q+ let rcs = Map.insert me rc rcs'new+ return (Readers { readerCounts=rcs, queueR=qr })++ -- | promote from releasing write lock+ promote :: LockQ -> IO LockUser+ promote qIn = do+ case Seq.viewl qIn of+ EmptyL -> return FreeLock++ (WriterKind it,mblock) :< qOut -> do+ putMVar mblock ()+ return (Writer { writerID=it, writerCount=1, readerCount=0, queue=qOut })++ _ -> do+ (rcs,qr) <- splitReaders qIn+ return (Readers { readerCounts=rcs, queueR=qr })++ -- | Merge (and wake) any and all readers on left end of LockQ, and return queueR value+ splitReaders :: LockQ -> IO (TMap,Maybe ((ThreadId,MVar ()),LockQ))+ splitReaders qIn = do+ let (more'Readers,qTail) = Seq.spanl isReader qIn+ (rks,mblocks) = unzip (F.toList more'Readers)+ rcs = Map.fromDistinctAscList . map (\k -> (k,1)) . F.toList . Set.unions . map unRK $ rks+ qr = case Seq.viewl qTail of+ EmptyL -> Nothing+ (wk,mblock) :< qOut -> Just ((unWK wk,mblock),qOut) -- unWK safe+ forM_ mblocks (\mblock -> putMVar mblock ())+ return (rcs,qr)+ where+ isReader (ReaderKind {},_) = True+ isReader _ = False++-- Six cases:+-- Lock is Free+-- I already have write lock+-- Someone else has write lock, 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++-- | 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.+--+-- All previous calls to acquireWrite by other threads will have succeeded and been released (or+-- interrupted) before this acquireRead will return.+--+-- The best way to use acquireRead is to use withRead instead to ensure releaseRead will be called+-- exactly once.+--+-- This may block and be safely interrupted. If interrupted then the RWLock will be left unchanged.+acquireRead :: RWLock -> IO ()+acquireRead (RWL rwlVar) = mask_ . join . modifyMVar rwlVar $ \ rwd -> do+ me <- myThreadId+ let safeBlock mblock = (readMVar mblock) `onException` (releaseRead' True me rwlVar)+ case rwd of+ FreeLock -> + return ( Readers { readerCounts=Map.singleton me 1, queueR=Nothing }+ , return () )++ w@(Writer { writerID=it, readerCount=rc, queue=q }) | it == me -> do+ rc' <- evaluate $ succ rc+ return ( w { readerCount=rc' }+ , return () )+ | otherwise -> do+ (q',mblock) <- enterQueueR q me+ return ( w { queue = q' }+ , safeBlock mblock )++ r@(Readers { readerCounts=rcs }) | Just rc <- Map.lookup me rcs -> do+ rc' <- evaluate $ succ rc+ rcs' <- evaluate $ Map.insert me rc' rcs+ return ( r { readerCounts=rcs' }+ , return () )++ r@(Readers { readerCounts=rcs, queueR=Nothing }) -> do+ rcs' <- evaluate $ Map.insert me 1 rcs+ return ( r { readerCounts=rcs' }+ , return () )++ r@(Readers { queueR=Just (w,q) }) -> do+ (q',mblock) <- enterQueueR q me+ return ( r { queueR=Just (w,q') }+ , safeBlock mblock )+ where+ -- Merge adjacent readers when adding to right end of LockQ+ enterQueueR :: LockQ -> ThreadId -> IO (LockQ,MVar ())+ enterQueueR qIn me = do+ case Seq.viewr qIn of+ pre :> (ReaderKind rcs,mblock) -> do+ rcs' <- addMe rcs+ return (pre |> (ReaderKind rcs', mblock),mblock)+ _ -> do+ mblock <- newEmptyMVar+ return (qIn |> (ReaderKind (Set.singleton me),mblock), mblock)+ where+ -- Paranoid check of design assertion, TODO: remove check+ addMe :: TSet -> IO TSet+ addMe rcs | Set.member me rcs = error (imp "enterQueueR.addMe when already in set" me)+ | otherwise = return (Set.insert me rcs)++-- 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+-- to acquireRead. If this acquireWrite gets interrupted then acquireReadPriority will to obtain+-- the read lock or put itself at the front of the queue if another thread holds the write lock.+acquireReadPriority :: RWLock -> IO ()+acquireReadPriority (RWL rwlVar) = uninterruptibleMask_ . join . modifyMVar rwlVar $ \ rwd -> do+ me <- myThreadId+ let safeBlock mblock = (readMVar mblock) `onException` (releaseRead' True me rwlVar)+ case rwd of+ FreeLock -> + return ( Readers { readerCounts=Map.singleton me 1, queueR=Nothing }+ , return () )++ w@(Writer { writerID=it, readerCount=rc, queue=q }) | it == me -> do+ rc' <- evaluate $ succ rc+ return ( w { readerCount=rc' }+ , return () )+ | otherwise -> do+ (q',mblock) <- enterQueueL me q+ return ( w { queue = q' }+ , safeBlock mblock )++ r@(Readers { readerCounts=rcs }) -> do+ case Map.lookup me rcs of+ Just rc -> do+ rc' <- evaluate $ succ rc+ rcs' <- evaluate $ Map.insert me rc' rcs+ return ( r { readerCounts=rcs' }+ , return () )++ Nothing -> do+ rcs' <- evaluate $ Map.insert me 1 rcs+ return ( r { readerCounts=rcs' }+ , return () )+ where+ -- Merge adjacent readers when adding to right end of LockQ+ enterQueueL :: ThreadId -> LockQ -> IO (LockQ,MVar ())+ enterQueueL me qIn = do+ case Seq.viewl qIn of+ (ReaderKind rcs,mblock) :< post -> do+ rcs' <- addMe rcs+ return ((ReaderKind rcs', mblock) <| post,mblock)+ _ -> do+ mblock <- newEmptyMVar+ return ((ReaderKind (Set.singleton me),mblock) <| qIn , mblock)+ where+ -- Paranoid check of design assertion, TODO: remove check+ addMe :: TSet -> IO TSet+ addMe rcs | Set.member me rcs = error (imp "enterQueueL.addMe when already in set" me)+ | otherwise = return (Set.insert me rcs)++-- Six cases:+-- Lock is Free+-- I already have write lock+-- Someone else has write lock+-- 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++-- | 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+-- releaseWrite calls.+--+-- All previous calls to acquireRead by other threads will have succeeded and been released (or+-- interrupted) before this acquireWrite will return.+--+-- The best way to use acquireWrite is to use withWrite instead to ensure releaseWrite will be+-- called exactly once.+--+-- This may block and usually be safely interrupted. If interrupted then the RWLock will be left+-- unchanged. The exception to being able to interrupted when this blocks is very subtle: if this+-- thread holds read locks and calls acquireWrite then it will release those read locks and go to+-- the back of the queue to acquire the write lock (it does not get to skip the queue). While+-- blocking waiting for the write lock to be available this thread may be interrupted. If not+-- interrupted then the write lock will eventually be acquired, followed by re-acquiring the+-- original number of read locks. But if acquireWrite is interrupted after releasing read locks+-- then it MUST restore those read locks on the way out. To do this the internal error handler will+-- use 'uninterruptibleMask_' and a special version of acquireRead that skips to the front of the+-- queue; when the current lock state is a reader this works instantly but when the current lock+-- state is a writer this thread will block in an UNINTERRUPTIBLE state until the current writer is+-- finished. Once this other writer is finished the error handler will obtain the read locks it+-- needs to allow the error propagation to continue.+acquireWrite :: RWLock -> IO ()+acquireWrite rwl@(RWL rwlVar) = mask_ . join . modifyMVar rwlVar $ \ rwd -> do+ me <- myThreadId+ let safeBlock mblock = (takeMVar mblock) `onException` (releaseWrite' True me rwlVar)+ case rwd of+ FreeLock ->+ return ( Writer { writerID=me, writerCount=1, readerCount=0, queue=Seq.empty }+ , return () )++ w@(Writer { writerID=it, writerCount=wc, queue=q }) | it==me ->+ return ( w { writerCount=(succ wc) }+ , return () )++ {-ditto-} | otherwise -> do+ mblock <- newEmptyMVar+ q' <- evaluate $ q |> (WriterKind me,mblock)+ return ( w { queue=q' }+ , safeBlock mblock )++ Readers { readerCounts=rcs } | Just rc <- Map.lookup me rcs -> do+ return ( rwd+ , withoutReads rc (acquireWrite rwl) )++ r@(Readers { queueR=Nothing }) -> do+ mblock <- newEmptyMVar+ let qr = Just ((me,mblock),Seq.empty)+ return ( r { queueR=qr }+ , safeBlock mblock )++ r@(Readers { queueR=Just (w,q) }) -> do+ mblock <- newEmptyMVar+ q' <- evaluate $ q |> (WriterKind me,mblock)+ return ( r { queueR=Just (w,q') }+ , safeBlock mblock )+ where+ withoutReads :: Int -> IO a -> IO a+ withoutReads n x = foldr (.) id (replicate n withoutRead) $ x+ withoutRead :: IO a -> IO a+ withoutRead = bracket_ (releaseRead rwl >>= either throw return) (acquireReadPriority rwl)++-- format impossible error strings to include standard description prefix+imp :: Show x => String -> x -> String+imp s x = "FairRWLock impossible error: "++s++" : "++show x++{-++subtle bug #1:++When converting from a read lock holding 'rc' read locks to a also holding a write lock, I first wrote:++replicateM_ rc (releaseRead rwl >>= either throw return)+acquireWrite rwl+replicateM_ rc (acquireRead rwl)++Imagine there are rc copies of withRead wrapped around the above:+withRead = liftA2 bracket_ acquireRead (releaseRead >=> either throw return)++Then the acquireWrite blocks and gets interrupted.+The releaseReads in the withRead will see a strange situation (not locked!) and call throw.++What is the answer? reverse the bracket for the release/acquire? Hmm..++-}
src/Control/Concurrent/MSem.hs view
@@ -17,6 +17,12 @@ -- 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@@ -26,40 +32,43 @@ ,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_,evaluate,mask_)+import Control.Exception(bracket_,uninterruptibleMask_,mask_) import Data.Typeable(Typeable) {- design notes are in MSemN.hs -} -data MS = MS { avail :: !Integer -- ^ This is the quantity available to be taken from the semaphore. Often updated.- , headWait :: MVar () -- ^ The head of the waiter queue blocks on headWait. Never updated.- }- deriving (Eq,Typeable)- -- | 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 = MSem { mSem :: !(MVar MS) -- ^ 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.- }+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.-new :: Integer -> IO MSem+--+-- 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- newHeadWait <- newEmptyMVar+ newMS <- newMVar $! initial newQueueWait <- newMVar ()- newMS <- newMVar $! (MS { avail = initial- , headWait = newHeadWait })+ newHeadWait <- newEmptyMVar return (MSem { mSem = newMS- , queueWait = newQueueWait })+ , 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 :: MSem -> IO a -> IO a+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@@ -69,50 +78,55 @@ -- 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 :: MSem -> IO ()-wait (MSem sem advance) = mask_ $ withMVar advance $ \ () -> do- todo <- mask_ $ modifyMVar sem $ \ m -> do+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 (m,Nothing)- Nothing -> if 1 <= avail m- then do- m' <- evaluate $ m { avail = avail m - 1 }- return (m', Nothing)- else do- return (m, Just (headWait m))+ 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.- case todo of- Nothing -> return ()- Just hw -> takeMVar hw -- actually may or may not block, a 'signal' could have already arrived.+ --+ -- 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 :: MSem -> IO ()-signal (MSem sem _) = uninterruptibleMask_ $ modifyMVar_ sem $ \ m -> do+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 avail m < 0- then evaluate m { avail = avail m + 1 }+ if ms < 0+ then return $! succ ms else do- didPlace <- tryPutMVar (headWait m) ()+ didPlace <- tryPutMVar (headWait m) () -- ms is never negative if didPlace- then return m- else evaluate m { avail = avail m + 1 }+ 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 :: MSem -> IO Integer-peekAvail (MSem sem _) = mask_ $ withMVar sem $ \ m -> do+-- 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 (avail m)+ Nothing -> return ms Just () -> do putMVar (headWait m) () -- cannot block- return (1 + avail m)+ return $! succ ms
src/Control/Concurrent/MSemN.hs view
@@ -18,6 +18,11 @@ -- 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@@ -30,6 +35,7 @@ ,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)@@ -54,35 +60,39 @@ -} -- MS has an invariant that "maybe True (> avail) headWants" is always True.-data MS = MS { avail :: !Integer -- ^ This is the quantity available to be taken from the semaphore. Often updated.- , headWants :: !(Maybe Integer) -- ^ If there is waiter then this is Just the amount being waited for. Often updated.- , headWait :: MVar () -- ^ The head of the waiter queue blocks on headWait. Never updated.- }+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 = MSemN { mSem :: !(MVar MS) -- ^ 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.- }+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 :: Integer -> IO MSemN+new :: Integral i => i -> IO (MSemN i)+{-# SPECIALIZE new :: Int -> IO (MSemN Int) #-}+{-# SPECIALIZE new :: Integer -> IO (MSemN Integer) #-} new initial = do- newHeadWait <- newEmptyMVar- newQueueWait <- newMVar () newMS <- newMVar $! (MS { avail = initial- , headWants = Nothing- , headWait = newHeadWait })+ , headWants = Nothing })+ newQueueWait <- newMVar ()+ newHeadWait <- newEmptyMVar return (MSemN { mSem = newMS- , queueWait = newQueueWait })+ , 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 :: MSemN -> Integer -> IO a -> IO a+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 @@ -93,8 +103,10 @@ -- -- Note: A long running pure function will block all other access to the 'MSemN' while it is -- evaluated.-withF :: MSemN -> (Integer -> (Integer,b)) -> ((Integer,b) -> IO a) -> IO a-withF m f = seq f $ bracket (waitF m f) (\(wanted,_) -> signal m wanted)+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.@@ -103,7 +115,9 @@ -- 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 :: MSemN -> Integer -> IO ()+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,())) @@ -121,32 +135,32 @@ -- -- Note: A long running pure function will block all other access to the 'MSemN' while it is -- evaluated.-waitF :: MSemN -> (Integer -> (Integer,b)) -> IO (Integer,b)-waitF (MSemN sem advance) f = seq f $ mask_ $ withMVar advance $ \ () -> do- (out@(wanted,_),todo) <- modifyMVar sem $ \ m -> do- let outVal@(wantedVal,_) = f (avail m)- -- assert that headDown is Nothing via new or signal or cleanup+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 m+ if wantedVal <= avail ms then do- let avail'down = avail m - wantedVal- m' <- evaluate $ m { avail = avail'down }- return (m', (outVal,Nothing))+ 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- m' <- evaluate $ m { headWants = Just wantedVal }- return (m', (outVal,Just (headWait m)))+ 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.- case todo of- Nothing -> return ()- Just hw -> do- let cleanup = uninterruptibleMask_ $ modifyMVar_ sem $ \m -> do- mStale <- tryTakeMVar (headWait m)- let avail' = avail m + maybe 0 (const wanted) mStale- evaluate $ m {avail = avail', headWants = Nothing}- takeMVar hw `onException` cleanup -- may not block if a 'signal' has already arrived.+ 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@@ -159,7 +173,9 @@ -- '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 :: MSemN -> Integer -> IO ()+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,())) @@ -177,19 +193,20 @@ -- -- Note: A long running pure function will block all other access to the 'MSemN' while it is -- evaluated.-signalF :: MSemN -> (Integer -> (Integer,b)) -> IO (Integer,b)-signalF (MSemN sem _) f = seq f $ modifyMVar sem $ \ m -> do- let out@(size,_) = f (avail m)- avail' <- evaluate $ avail m + size -- this forces 'size'- case headWants m of- Just wanted | wanted <= avail' -> do- let avail'down = avail' - wanted- m' <- evaluate $ m { avail = avail'down, headWants = Nothing }- putMVar (headWait m') () -- will always succeed without blocking- return (m',out)- _ -> do- m' <- evaluate $ m { avail = avail' }- return (m',out)+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@@ -200,5 +217,7 @@ -- -- A version of 'peekAvail' that joins the FIFO queue of 'wait' and 'waitF' can be acheived by -- \"waitF m (\x -> (0,x))\"-peekAvail :: MSemN -> IO Integer-peekAvail (MSemN sem _) = withMVar sem (return . avail)+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)