The base package (versions 3 and 4) implementations of Control.Concurrent.QSem and QSemN (and perhaps SamepleVar) are not exception safe. The proposed replacement code is on hackage as SafeSemaphore and source from version 0.4.1 is also included on this page.

Exception correctness means that the semaphore does not lose any of its quantity if the waiter or signaler is interrupted before the operation finishes. QSem and QSemN violate this safetly.

SafeSemaphore defines MSem as the proposed replacements for QSem, and MSemQ as the proposed replacement for QSemN.

The SampleVar module in base also has the same kind of bug, but with SampleVar the rutnime error is worse because it can case writeSampleVar to block indefinitely. The SafeSemaphore package as of version 0.5.0 has a MSampleVar module that does not have this bug.

The GHC ticket is #3160.

The problem with QSem and QSemN is that a blocked waiter might be killed. This does not prevent a later signal from trying to pass quantity to a dead thread. This quantity is thus thrown out, a blatantly leaky abstraction. This is illustrated by the tests/TestKillSem.hs program in the SafeSemaphore package (run with cabal test, please read the log generated).

The program, preceded by its output as a comment is:

-- This is run by "cabal test"

This shows that quantity can be easily lost when using a QSem or QSemN, and shows that MSem and MSemN do not have this problem.

This code should be exception safe and exception correct. The API for QSem is slightly extended to allow peekAvail to query the amount of content in the semaphore. The semantics of QSem are slightly extended to allow a new MSem to be initialized with negative, zero, or positive quantity. The use of Int has been replaced with Integer. The wait operation has been added to encourage safely bracketing wait and signal.

Note that it does not allocate any MVars to manage the waiting queue. Only MSem.new allocates them. This should be more efficient than QSem.

-- Note that "Control.Concurrent.MSemN" offers a more powerful API for making decisions based on the available amount.

-- be taken. Using this value without producing unwanted race conditions is left up to the

-- 'signal', other 'peekAvail', and the head waiter. This returns the amount of value available to

-- | 'peekAvail' skips the queue of any blocked 'wait' threads, but may momentarily block on

-- mask_ might be as good as uninterruptibleMask_ since nothing below can block

-- the 'MSem'. All 'signal', 'peekAvail', and the head waiter may momentarily block in a fair FIFO

-- 'signal' may block, but it cannot be interrupted, which allows it to dependably restore value to

-- | 'signal' adds one unit to the sempahore.

-- actually may or may not block, a 'signal' could have already arrived.

-- without being interrupted so that a 'bracket' can ensure a matching 'signal' can be ensured.

-- mask_ is needed above because we may have just decremented 'avail' and we must finished 'wait'

-- interrupted or has retured without interruption.

-- returns without interruption then it is known that each earlier waiter has definitely either been

-- greater than or equal to zero. If 'wait' is interrupted then no quantity is lost. If 'wait'

-- If 'wait' returns without interruption then it left the 'MSem' with a remaining quantity that was

-- |'wait' will take one unit of value from the sempahore, but will block if the quantity available

-- 'with' uses 'bracket_' to ensure 'wait' and 'signal' get called correctly.

-- operation. 'with' ensures the quantity of the sempahore cannot be lost if there are exceptions.

-- | 'with' takes a unit of value from the semaphore to hold while performing the provided

-- |'new' allows positive, zero, and negative initial values. The initial value is forced here to

-- ^ Used as FIFO queue for waiter, held by head of queue. Never updated.

-- ^ Used to lock access to state of semaphore quantity. Never updated.

-- units, and which can start with positive, zero, or negative value.

-- | A 'MSem' is a semaphore in which the available quantity can be added and removed in single

-- ^ The head of the waiter queue blocks on headWait. Never updated.

-- ^ This is the quantity available to be taken from the semaphore. Often updated.

-- positive quantity. 'wait' always leaves the 'MSem' with non-negative quantity.

-- the activity throws an exception. 'new' can initialize the semaphore to negative, zero, or

-- If 'with' is used to guard a critical section then no quantity of the semaphore will be lost if

-- guarantee is that blocked threads are FIFO.

-- This semaphore gracefully handles threads which die while blocked waiting. The fairness

-- is intended to improve on "Control.Concurrent.QSem".

-- A semaphore in which operations may 'wait' for or 'signal' single units of value. This modules

The API for MSemN follows QSemN with several more complicated additions. All quantity arguments may be negative, zero, or positive. There are waitF, signalF, and withF operations that take a pure function to computes the quantity change based on the current quantity in the semaphore. And peekAvail was added to query the semaphore's quantity.