SafeSemaphore-0.10.1: Much safer replacement for QSemN, QSem, and SampleVar

Safe Haskell | Safe
Language | Haskell98

Control.Concurrent.FairRWLock

Description

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. The policy choices here are different from the ones for the RWLock in concurrent-extras.

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.

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).

The preferred way to use this API is sticking to `new`, `withRead`, and `withWrite`.

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.

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.

Synopsis

- data RWLock
- data RWLockException = RWLockException ThreadId RWLockExceptionKind String
- data RWLockExceptionKind
  - = RWLock'acquireWrite
  - | RWLock'releaseWrite
  - | RWLock'acquireRead
  - | RWLock'releaseRead
- data FRW
  - = F
  - | R TMap
  - | W (ThreadId, (Int, Int))
- data LockKind
  - = ReaderKind {
    - unRK :: TSet
    }
  - | WriterKind {
    - unWK :: ThreadId
    }
- type TMap = Map ThreadId Int
- type TSet = Set ThreadId
- new :: IO RWLock
- withRead :: RWLock -> IO a -> IO a
- withWrite :: RWLock -> IO a -> IO a
- acquireRead :: RWLock -> IO ()
- acquireWrite :: RWLock -> IO ()
- releaseRead :: RWLock -> IO (Either RWLockException ())
- releaseWrite :: RWLock -> IO (Either RWLockException ())
- peekLock :: RWLock -> IO (FRW, [LockKind])
- checkLock :: RWLock -> IO (Int, Int)

# Documentation

data RWLock

Opaque type of the fair RWLock.

data RWLockException

Exception type thrown or returned by this module. "Impossible" conditions get the error thrown and usage problems get the error returned.

Constructors

RWLockException ThreadId RWLockExceptionKind String |

Instances

Show RWLockException |
Exception RWLockException |

data RWLockExceptionKind

Operation in which error arose,

Constructors

RWLock'acquireWrite |
RWLock'releaseWrite |
RWLock'acquireRead |
RWLock'releaseRead |

Instances

Show RWLockExceptionKind |

data FRW

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 number of read locks held (at least 1).

Constructors

F |
R TMap |
W (ThreadId, (Int, Int)) |

Instances

Show FRW |

data LockKind

Constructors

ReaderKind |
Fields unRK :: TSet
WriterKind |
Fields unWK :: ThreadId

Instances

Eq LockKind |
Ord LockKind |
Show LockKind |

type TMap = Map ThreadId Int

type TSet = Set ThreadId

new :: IO RWLock

Create a new RWLock which starts in a free and unlocked state.

withRead :: RWLock -> IO a -> IO a

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.

withWrite :: 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.

acquireRead :: RWLock -> IO ()

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.

acquireWrite :: 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.

releaseRead :: RWLock -> IO (Either RWLockException ())

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.

releaseWrite :: 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.

peekLock :: RWLock -> IO (FRW, [LockKind])

Observe which threads are holding the lock and which threads are waiting (in order). This is particularly useful for writing tests.

checkLock :: RWLock -> IO (Int, Int)

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.