packages feed

bounded-qsem-0.1.0.0: src/Control/Concurrent/BQSem.hs

{-# LANGUAGE Safe #-}

-- | Bounded quantity semaphores.
module Control.Concurrent.BQSem
  ( BQSem
  , newBQSem
  , waitBQSem
  , signalBQSem
  , getBQSemQuantity
    ) where

import Control.Concurrent.QSem
import Control.Concurrent.MVar
import Control.Exception (mask, onException)
import Control.Monad (unless)

-- | Bounded quantity semaphore in which the resource is acquired and released in units of one,
--   but with a maximum amount of units available at any given time.
data BQSem = BQSem
  { -- | Underlying unbounded quantity semaphore.
    unboundedQSem :: QSem
    -- | Maximum number of units.
  , bqsemBound :: Int
    -- | Counter of current units.
  , bqsemCounter :: MVar Int
    }

-- | Build a new 'BQSem' with supplied initial and maximum supply.
--   An exception is thrown in any of the following cases:
--
--   * Initial supply is negative.
--   * Maximum supply is less than 1.
--   * Initial supply exceeds maximum.
--
newBQSem
  :: Int -- ^ Initial unit supply.
  -> Int -- ^ Maximum unit supply.
  -> IO BQSem
newBQSem n0 m = do
  unless (n0 <= m) $ fail "newBQSem: Initial quantity must be less or equal than maximum."
  unless (m > 0) $ fail "newBQSem: Maximum quantity must be at least 1."
  qsem <- newQSem n0
  counter <- newMVar n0
  pure $ BQSem
    { unboundedQSem = qsem
    , bqsemBound = m
    , bqsemCounter = counter
      }

-- | Wait for a unit to become available.
waitBQSem :: BQSem -> IO ()
waitBQSem bqsem =
  mask $ \restore -> do
    restore $ waitQSem $ unboundedQSem bqsem
    let counter = bqsemCounter bqsem
    takeMVar counter >>=
      \n -> putMVar counter $! n - 1

-- | Make a new unit available, unless the maximum number of units has been reached,
--   in which case it does nothing (it doesn't block).
signalBQSem :: BQSem -> IO ()
signalBQSem bqsem =
  mask $ \restore -> do
    let counter = bqsemCounter bqsem
    n <- takeMVar counter
    if n == bqsemBound bqsem
       then putMVar counter n
       else do restore (signalQSem $ unboundedQSem bqsem) `onException` putMVar counter n
               putMVar counter $! n + 1

-- | Get current supply quantity.
getBQSemQuantity :: BQSem -> IO Int
getBQSemQuantity = readMVar . bqsemCounter