module Control.Reaper (
ReaperSettings
, defaultReaperSettings
, reaperAction
, reaperDelay
, reaperCons
, reaperNull
, reaperEmpty
, Reaper(..)
, mkReaper
, mkListAction
) where
import Control.AutoUpdate.Util (atomicModifyIORef')
import Control.Concurrent (forkIO, threadDelay)
import Control.Exception (mask_)
import Control.Monad (join, void)
import Data.IORef (IORef, newIORef, readIORef)
data ReaperSettings workload item = ReaperSettings
{ reaperAction :: workload -> IO (workload -> workload)
, reaperDelay :: !Int
, reaperCons :: item -> workload -> workload
, reaperNull :: workload -> Bool
, reaperEmpty :: workload
}
defaultReaperSettings :: ReaperSettings [item] item
defaultReaperSettings = ReaperSettings
{ reaperAction = \wl -> return (wl ++)
, reaperDelay = 30000000
, reaperCons = (:)
, reaperNull = null
, reaperEmpty = []
}
data Reaper workload item = Reaper {
reaperAdd :: item -> IO ()
, reaperRead :: IO workload
, reaperStop :: IO workload
}
data State workload = NoReaper
| Workload workload
mkReaper :: ReaperSettings workload item -> IO (Reaper workload item)
mkReaper settings@ReaperSettings{..} = do
stateRef <- newIORef NoReaper
return Reaper {
reaperAdd = update settings stateRef
, reaperRead = readRef stateRef
, reaperStop = stop stateRef
}
where
readRef stateRef = do
mx <- readIORef stateRef
case mx of
NoReaper -> return reaperEmpty
Workload wl -> return wl
stop stateRef = atomicModifyIORef' stateRef $ \mx ->
case mx of
NoReaper -> (NoReaper, reaperEmpty)
Workload x -> (Workload reaperEmpty, x)
update :: ReaperSettings workload item -> IORef (State workload) -> item
-> IO ()
update settings@ReaperSettings{..} stateRef item =
mask_ $ join $ atomicModifyIORef' stateRef cons
where
cons NoReaper = (Workload $ reaperCons item reaperEmpty
,spawn settings stateRef)
cons (Workload wl) = (Workload $ reaperCons item wl
,return ())
spawn :: ReaperSettings workload item -> IORef (State workload) -> IO ()
spawn settings stateRef = void . forkIO $ reaper settings stateRef
reaper :: ReaperSettings workload item -> IORef (State workload) -> IO ()
reaper settings@ReaperSettings{..} stateRef = do
threadDelay reaperDelay
wl <- atomicModifyIORef' stateRef swapWithEmpty
merge <- reaperAction wl
join $ atomicModifyIORef' stateRef (check merge)
where
swapWithEmpty NoReaper = error "Control.Reaper.reaper: unexpected NoReaper (1)"
swapWithEmpty (Workload wl) = (Workload reaperEmpty, wl)
check _ NoReaper = error "Control.Reaper.reaper: unexpected NoReaper (2)"
check merge (Workload wl)
| reaperNull wl' = (NoReaper, return ())
| otherwise = (Workload wl', reaper settings stateRef)
where
wl' = merge wl
mkListAction :: (item -> IO (Maybe item'))
-> [item]
-> IO ([item'] -> [item'])
mkListAction f =
go id
where
go front [] = return front
go front (x:xs) = do
my <- f x
let front' =
case my of
Nothing -> front
Just y -> front . (y:)
go front' xs