-----------------------------------------------------------------------------
-- |
-- Module      :  Control.Concurrent.STM.SSem
-- Copyright   :  (c) Chris Kuklewicz, 2012
-- License     :  BSD-style
-- 
-- Maintainer  :  haskell@list.mightyreason.com
-- Stability   :  experimental
-- Portability :  non-portable (concurrency)
--
-- Very simple quantity semaphore.
--
-----------------------------------------------------------------------------
module Control.Concurrent.STM.SSem(SSem, new, wait, signal, tryWait
                                  , waitN, signalN, tryWaitN
                                  , getValue) where

import Control.Monad.STM(STM,retry)
import Control.Concurrent.STM.TVar(newTVar,readTVar,writeTVar)
import Control.Concurrent.STM.SSemInternals(SSem(SSem))

-- | Create a new semaphore with the given argument as the initially available quantity.  This
-- allows new semaphores to start with a negative, zero, or positive quantity.
new :: Int -> STM SSem
new = fmap SSem . newTVar

-- | Try to take a unit of value from the semaphore.  This succeeds when the current quantity is
-- positive, and then reduces the quantity by one.  Otherwise this will 'retry'.  This will never
-- result in a negative quantity.  If several threads are retying then which one succeeds next is
-- undefined -- an unlucky thread might starve.
wait :: SSem -> STM ()
wait = flip waitN 1

-- | Try to take the given value from the semaphore.  This succeeds when the quantity is greater or
-- equal to the given value, and then subtracts the given value from the quantity.  Otherwise this
-- will 'retry'.  This will never result in a negative quantity.  If several threads are retrying
-- then which one succeeds next is undefined -- an unlucky thread might starve.
waitN :: SSem -> Int -> STM ()
waitN (SSem s) i = do
  v <- readTVar s
  if v >= i
    then writeTVar s $! v-i
    else retry

-- | Signal that single unit of the semaphore is available.  This increases the available quantity
-- by one.
signal :: SSem -> STM ()
signal = flip signalN 1

-- | Signal that many units of the semaphore are available.  This changes the available quantity by
-- adding the passed size.
signalN :: SSem -> Int -> STM ()
signalN (SSem s) i = do
  v <- readTVar s
  writeTVar s $! v+i

-- | Non-retrying version of 'wait'.  `tryWait s` is defined as `tryN s 1`
tryWait :: SSem -> STM (Maybe Int)
tryWait = flip tryWaitN 1

-- | Non-retrying version of waitN.  It either takes the quantity from the semaphore like
-- waitN and returns `Just value taken` or finds insufficient quantity to take and returns
-- Nothing
tryWaitN :: SSem -> Int -> STM (Maybe Int)
tryWaitN (SSem s) i = do
  v <- readTVar s
  if v >= i
    then do writeTVar s $! v-i
            return (Just i)
    else return Nothing

-- | Return the current quantity in the semaphore.  This is potentially useful in a larger STM
-- transaciton and less useful as `atomically getValueSem :: IO Int` due to race conditions.
getValue :: SSem -> STM Int
getValue (SSem s) = readTVar s