restricted-workers-0.1.0: src/System/Restricted/Worker/Pool.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
-- | A non-stripped pooling abstraction that restarts workers
-- Some got has been taken from 'Data.Pool' by bos
module System.Restricted.Worker.Pool
(
-- * Workers Pool
WorkersPool
, mkPool
-- * High-level operations on the pool
, withWorker
-- * Low-level operations on the pool
, takeWorker
, putWorker
, destroyWorker
) where
import Control.Applicative ((<$>), (<*>))
import Control.Concurrent (forkIO, killThread, threadDelay)
import Control.Concurrent.STM
import Control.Exception (mask, onException)
import Control.Monad (forM, forever, join, when)
import Control.Monad.Base (MonadBase(..))
import Control.Monad.Trans.Control (MonadBaseControl, control)
import Control.Monad.IO.Class (MonadIO, liftIO)
import System.Mem.Weak (addFinalizer)
import System.Restricted.Worker.Internal
import System.Restricted.Worker.Types
-- | A simple pool for workers. Workers are restarted from time to time
data WorkersPool w = Pool
{ newWorker :: Int -> WMonad w (Worker w, RestartWorker IO w)
-- ^ Action for creating a new worker
, maxWorkers :: Int
-- ^ Maximum number of initialized workers
, activeWorkers :: TVar Int
-- ^ Current number of active workers
, workers :: TVar [(Worker w, RestartWorker IO w)]
-- ^ A list of Workers
, restartRate :: Int
-- ^ How long we should wait before restarting the workers (in seconds)
}
-- | Create a new workers pool
mkPool :: (MonadIO (WMonad a))
=> (Int -> WMonad a (Worker a, RestartWorker IO a))
-- ^ An action that creates a new worker. Takes a unique number as an argument
-> Int
-- ^ Maximum number of workers in the pool
-> Int
-- ^ Restart rate (in seconds)
-> WMonad a (WorkersPool a)
mkPool newW maxW restartRate = do
res <- liftIO $ atomically $ newTVar []
num <- liftIO $ atomically $ newTVar (0 :: Int)
reaperT <- liftIO $ forkIO $ reaper res restartRate
let p = Pool newW maxW num res restartRate
liftIO $ addFinalizer p (killThread reaperT)
return p
reaper :: TVar [(Worker a, RestartWorker IO a)] -> Int -> IO ()
reaper wrkrs t' = forever $ do
let t = t' * 1000000
threadDelay t
workers <- readTVarIO wrkrs
workers' <- forM workers $ \(w, rw) -> (,) <$> rw w <*> return rw
atomically $ writeTVar wrkrs workers'
-- | Take worker from the pool.
-- The caller is responsible for putting the worker back into the pool
-- or destroying it with 'destroyWorker'
takeWorker :: (MonadIO (WMonad a), MonadBaseControl IO (WMonad a))
=> WorkersPool a -> WMonad a (Worker a, RestartWorker IO a)
takeWorker Pool{..} = do
res <- liftIO $ readTVarIO workers
case res of
((w@Worker{..}, restartW):xs) -> liftIO $ do
atomically $ writeTVar workers xs
workerDead <- not <$> workerAlive w
wrk <- if workerDead
then do
restartW w
else return w
return (wrk, restartW)
[] -> join $ liftIO . atomically $ do
activeRes <- readTVar activeWorkers
when (activeRes >= maxWorkers) retry
modifyTVar' activeWorkers (+1)
return $ control $ \runIO ->
runIO (newWorker (activeRes+1))
`onException`
atomically (modifyTVar' activeWorkers (subtract 1))
-- | Put the worker back in pool
putWorker :: WorkersPool a -> (Worker a, RestartWorker IO a) -> IO ()
putWorker Pool{..} w = atomically $
modifyTVar' workers (w:)
-- | Destroy a worker. Frees up space in the pool
destroyWorker :: WorkersPool a -> Worker a -> IO ()
destroyWorker Pool{..} w = do
_ <- killWorker w
atomically $ modifyTVar' activeWorkers (subtract 1)
-- | Like 'takeWorker' + 'putWorker' but takes care of the exception handling for you
withWorker :: (MonadBaseControl IO m, MonadBase (WMonad a) m,
MonadBaseControl IO (WMonad a), MonadIO (WMonad a))
=> WorkersPool a
-> ((Worker a, RestartWorker IO a) -> m b)
-> m b
withWorker pool cb = do
res <- liftBase $ takeWorker pool
control $ \runIO -> mask $ \restore -> do
ret <- restore (runIO (cb res))
`onException` destroyWorker pool (fst res)
putWorker pool res
return ret