-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Much safer replacement for QSemN, QSem, and SampleVar -- -- 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. @package SafeSemaphore @version 0.7.0 -- | 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 -- | Opaque type of the fair RWLock. data RWLock -- | 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 -> RWLockException -- | Operation in which error arose data RWLockExceptionKind RWLock'acquireWrite :: RWLockExceptionKind RWLock'releaseWrite :: RWLockExceptionKind RWLock'acquireRead :: RWLockExceptionKind RWLock'releaseRead :: RWLockExceptionKind -- | 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 :: FRW R :: TMap -> FRW W :: (ThreadId, (Int, Int)) -> FRW data LockKind ReaderKind :: TSet -> LockKind unRK :: LockKind -> TSet WriterKind :: ThreadId -> LockKind unWK :: LockKind -> ThreadId type TMap = Map ThreadId Int type TSet = Set ThreadId -- | Create a new RWLock which starts in a free and unlocked state. new :: IO RWLock -- | 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]) -- | 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) -- | 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 () -- | 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 () -- | 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 ()) -- | 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 ()) -- | 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 -- | 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 instance Typeable LockUser instance Typeable RWLockExceptionKind instance Typeable RWLockException instance Eq LockKind instance Ord LockKind instance Show LockKind instance Eq LockUser instance Show RWLockExceptionKind instance Show RWLockException instance Show FRW instance Exception RWLockException -- | MSampleVar is a safer version of the -- Control.Concurrent.SampleVar in base. The same problem as -- QSem(N) is being fixed, that of handling waiters that die before being -- woken normally. For Control.Concurrent.SampleVar in base this -- error can lead to thinking a full SampleVar is really empty -- and cause writeSampleVar to hang. The MSampleVar in -- this module is immune to this error, and has a simpler implementation. module Control.Concurrent.MSampleVar -- | Sample variables are slightly different from a normal MVar: -- -- -- -- The readers queue in FIFO order, with the lead reader joining the -- writers in a second FIFO queue to access the stored value. Thus -- writers can jump the queue of non-leading waiting readers to update -- the value, but the lead reader has to wait on all previous writes to -- finish before taking the value. -- -- This design choice emphasises that each reader sees the most -- up-to-date value possible while still guaranteeing progress. data MSampleVar a -- | newEmptySV allocates a new MSampleVar in an empty state. No -- futher allocation is done when using the MSampleVar. newEmptySV :: IO (MSampleVar a) -- | newSV allocates a new MSampleVar containing the passed value. -- The value is not evalated or forced, but stored lazily. No futher -- allocation is done when using the MSampleVar. newSV :: a -> IO (MSampleVar a) -- | If the MSampleVar is full, forget the value and leave it empty. -- Otherwise, do nothing. This avoids any the FIFO queue of blocked -- readSV threads. -- -- emptySV can block and be interrupted, in which case it does -- nothing. If emptySV returns then it left the MSampleVar -- in an empty state. emptySV :: MSampleVar a -> IO () -- | Wait for a value to become available, then take it and return. The -- queue of blocked readSV threads is a fair FIFO queue. -- -- readSV can block and be interrupted, in which case it takes -- nothing. If 'readSV returns normally then it has taken a value. readSV :: MSampleVar a -> IO a -- | Write a value into the MSampleVar, overwriting any previous -- value that was there. -- -- writeSV can block and be interrupted, in which case it does -- nothing. writeSV :: MSampleVar a -> a -> IO () -- | isEmptySV can block and be interrupted, in which case it does -- nothing. If isEmptySV returns then it reports the momentary -- status the MSampleVar. Using this value without producing -- unwanted race conditions is left up to the programmer. isEmptySV :: MSampleVar a -> IO Bool instance Eq (MSampleVar a) instance Typeable1 MSampleVar -- | 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 -- | A MSemN is a quantity semaphore, in which the available -- quantity may be signalled or waited for in arbitrary amounts. data MSemN i -- | 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) -- | 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 -- | 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 () -- | 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 () -- | 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 -- | 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) -- | 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) -- | 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 instance Typeable1 MS instance Typeable1 MSemN instance Eq i => Eq (MS i) instance Eq (MSemN i) -- | 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 -- | 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 -- | 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) -- | 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 -- | 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 () -- | 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 () -- | 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 instance Typeable1 MSem instance Eq (MSem i)