lockpool-0.1.0.0: Control/Concurrent/LockPool.hs
{-# LANGUAGE BangPatterns #-}
module Control.Concurrent.LockPool
( LockPool
, LockingStats(..)
, withLockPool
, withLockPoolStats
, incrementLockPoolSize
, decrementLockPoolSize
, readLockPoolSize
, newLockPool
) where
import Control.Monad.STM
import Control.Concurrent.STM.TVar
import Control.Exception (bracket_,bracket)
import qualified System.Clock as CLK
-- | A LockPool lets us set an upper bound on the number of threads
-- that are doing something simultaneously.
data LockPool = LockPool
Int -- ^ absolute maximum holders
(TVar Int) -- ^ maximum number of lock holders
(TVar Int) -- ^ current number of lock holders
data Clocked a = Clocked !Integer !a
-- | The result of a computation along with some information
-- about how long we had to wait to acquire a lock and how
-- long the computation took.
data LockingStats = LockingStats
{ lsWaitedNanoseconds :: !Integer
, lsActionNanoseconds :: !Integer
, lsTakenLocks :: !Int
-- ^ Count of taken locks before releasing the lock
, lsTotalLocks :: !Int
-- ^ Count of total locks before adjusting the total
}
stopwatch :: IO a -> IO (Clocked a)
stopwatch x = do
t1 <- CLK.getTime CLK.Monotonic
a <- x
t2 <- CLK.getTime CLK.Monotonic
return (Clocked (CLK.toNanoSecs (CLK.diffTimeSpec t2 t1)) a)
stopwatch_ :: IO a -> IO Integer
stopwatch_ x = do
t1 <- CLK.getTime CLK.Monotonic
_ <- x
t2 <- CLK.getTime CLK.Monotonic
return (CLK.toNanoSecs (CLK.diffTimeSpec t2 t1))
withLockPool :: LockPool -> IO a -> IO a
withLockPool (LockPool _ maxHoldersVar currentHoldersVar) action =
bracket_ acquire release action
where
acquire = atomically $ do
maxHolders <- readTVar maxHoldersVar
currentHolders <- readTVar currentHoldersVar
check (currentHolders < maxHolders)
writeTVar currentHoldersVar $! (currentHolders + 1)
release = atomically $ modifyTVar' currentHoldersVar (subtract 1)
withLockPoolStats :: LockPool -> IO a -> IO (LockingStats,a)
withLockPoolStats (LockPool _ maxHoldersVar currentHoldersVar) x =
bracket acquire release action
where
acquire = stopwatch_ $ atomically $ do
maxHolders <- readTVar maxHoldersVar
currentHolders <- readTVar currentHoldersVar
check (currentHolders < maxHolders)
writeTVar currentHoldersVar $! (currentHolders + 1)
action waitedNs = do
Clocked actionNs a <- stopwatch x
taken <- readTVarIO currentHoldersVar
total <- readTVarIO maxHoldersVar
return (LockingStats waitedNs actionNs taken total, a)
release _ = atomically $ modifyTVar' currentHoldersVar (subtract 1)
-- | We do not allow the size to drop to zero since that
-- would halt all progress.
decrementLockPoolSize :: LockPool -> IO Int
decrementLockPoolSize (LockPool _ maxHoldersVar _) = atomically $ do
i <- readTVar maxHoldersVar
let i' = max 1 (i - 1)
writeTVar maxHoldersVar $! i'
return i'
incrementLockPoolSize :: LockPool -> IO Int
incrementLockPoolSize (LockPool absMax maxHoldersVar _) = atomically $ do
i <- readTVar maxHoldersVar
let i' = min absMax (i + 1)
writeTVar maxHoldersVar $! i'
return i'
readLockPoolSize :: LockPool -> IO Int
readLockPoolSize (LockPool _ maxHoldersVar _) =
readTVarIO maxHoldersVar
_readLockPoolHolds :: LockPool -> IO Int
_readLockPoolHolds (LockPool _ _ currentHoldersVar) =
readTVarIO currentHoldersVar
newLockPool ::
Int -- ^ absolute maximum number of holders
-> IO LockPool
newLockPool n = LockPool n
<$> newTVarIO 1
<*> newTVarIO 0
-- setLockPoolSize :: LockPool -> Int -> IO ()
-- setLockPoolSize (LockPool maxHoldersVar _) !newMax =
-- atomically (writeTVar maxHoldersVar newMax)