{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module UnliftIO.Reaper
(
ReaperSettings
, defaultReaperSettings
, reaperAction
, reaperDelay
, reaperCons
, reaperNull
, reaperEmpty
, reaperThreadName
, Reaper
, reaperAdd
, reaperRead
, reaperModify
, reaperStop
, reaperKill
, mkReaper
, mkListAction
)
where
import Control.Monad (forM_, join)
import Control.Monad.IO.Class (MonadIO (..))
import Data.Functor.Identity
import GHC.Conc.Sync (labelThread)
import UnliftIO (MonadUnliftIO)
import UnliftIO.Concurrent (ThreadId, forkIO, killThread, threadDelay)
import UnliftIO.Exception (mask_)
import UnliftIO.IORef (IORef, atomicModifyIORef', newIORef, readIORef, writeIORef)
import UnliftIO.Reaper.Internal
data ReaperSettings m workload item = ReaperSettings
{ forall (m :: * -> *) workload item.
ReaperSettings m workload item
-> workload -> m (workload -> workload)
reaperAction :: workload -> m (workload -> workload)
, forall (m :: * -> *) workload item.
ReaperSettings m workload item -> Int
reaperDelay :: {-# UNPACK #-} !Int
, forall (m :: * -> *) workload item.
ReaperSettings m workload item -> item -> workload -> workload
reaperCons :: item -> workload -> workload
, forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload -> Bool
reaperNull :: workload -> Bool
, forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload
reaperEmpty :: workload
, forall (m :: * -> *) workload item.
ReaperSettings m workload item -> String
reaperThreadName :: String
}
defaultReaperSettings :: ReaperSettings Identity [item] item
defaultReaperSettings :: forall item. ReaperSettings Identity [item] item
defaultReaperSettings =
ReaperSettings
{ reaperAction :: [item] -> Identity ([item] -> [item])
reaperAction = \[item]
wl -> forall (f :: * -> *) a. Applicative f => a -> f a
pure ([item]
wl forall a. [a] -> [a] -> [a]
++)
, reaperDelay :: Int
reaperDelay = Int
30000000
, reaperCons :: item -> [item] -> [item]
reaperCons = (:)
, reaperNull :: [item] -> Bool
reaperNull = forall (t :: * -> *) a. Foldable t => t a -> Bool
null
, reaperEmpty :: [item]
reaperEmpty = []
, reaperThreadName :: String
reaperThreadName = String
"Reaper"
}
data State workload
=
NoReaper
|
Workload !workload
mkReaper :: MonadUnliftIO m => ReaperSettings m workload item -> m (Reaper m workload item)
mkReaper :: forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item -> m (Reaper m workload item)
mkReaper settings :: ReaperSettings m workload item
settings@ReaperSettings {workload
Int
String
workload -> m (workload -> workload)
workload -> Bool
item -> workload -> workload
reaperThreadName :: String
reaperEmpty :: workload
reaperNull :: workload -> Bool
reaperCons :: item -> workload -> workload
reaperDelay :: Int
reaperAction :: workload -> m (workload -> workload)
reaperThreadName :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> String
reaperEmpty :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload
reaperNull :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload -> Bool
reaperCons :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> item -> workload -> workload
reaperDelay :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> Int
reaperAction :: forall (m :: * -> *) workload item.
ReaperSettings m workload item
-> workload -> m (workload -> workload)
..} = do
IORef (State workload)
stateRef <- forall (m :: * -> *) a. MonadIO m => a -> m (IORef a)
newIORef forall workload. State workload
NoReaper
IORef (Maybe ThreadId)
tidRef <- forall (m :: * -> *) a. MonadIO m => a -> m (IORef a)
newIORef forall a. Maybe a
Nothing
forall (m :: * -> *) a. Monad m => a -> m a
return
Reaper
{ reaperAdd :: item -> m ()
reaperAdd = forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item
-> IORef (State workload) -> IORef (Maybe ThreadId) -> item -> m ()
add ReaperSettings m workload item
settings IORef (State workload)
stateRef IORef (Maybe ThreadId)
tidRef
, reaperRead :: m workload
reaperRead = forall {m :: * -> *}.
MonadIO m =>
IORef (State workload) -> m workload
readRef IORef (State workload)
stateRef
, reaperModify :: (workload -> workload) -> m workload
reaperModify = forall {m :: * -> *}.
MonadIO m =>
IORef (State workload) -> (workload -> workload) -> m workload
modifyRef IORef (State workload)
stateRef
, reaperStop :: m workload
reaperStop = forall {m :: * -> *}.
MonadIO m =>
IORef (State workload) -> m workload
stop IORef (State workload)
stateRef
, reaperKill :: m ()
reaperKill = forall {m :: * -> *} {t :: * -> *}.
(MonadIO m, Foldable t) =>
IORef (t ThreadId) -> m ()
kill IORef (Maybe ThreadId)
tidRef
}
where
readRef :: IORef (State workload) -> m workload
readRef IORef (State workload)
stateRef = do
State workload
mx <- forall (m :: * -> *) a. MonadIO m => IORef a -> m a
readIORef IORef (State workload)
stateRef
case State workload
mx of
State workload
NoReaper -> forall (m :: * -> *) a. Monad m => a -> m a
return workload
reaperEmpty
Workload workload
wl -> forall (m :: * -> *) a. Monad m => a -> m a
return workload
wl
modifyRef :: IORef (State workload) -> (workload -> workload) -> m workload
modifyRef IORef (State workload)
stateRef workload -> workload
modifier = forall (m :: * -> *) a b.
MonadIO m =>
IORef a -> (a -> (a, b)) -> m b
atomicModifyIORef' IORef (State workload)
stateRef forall a b. (a -> b) -> a -> b
$ \case
State workload
NoReaper ->
(forall workload. State workload
NoReaper, workload
reaperEmpty)
Workload workload
wl ->
let !wl' :: workload
wl' = workload -> workload
modifier workload
wl
in (forall workload. workload -> State workload
Workload workload
wl', workload
wl')
stop :: IORef (State workload) -> m workload
stop IORef (State workload)
stateRef = forall (m :: * -> *) a b.
MonadIO m =>
IORef a -> (a -> (a, b)) -> m b
atomicModifyIORef' IORef (State workload)
stateRef forall a b. (a -> b) -> a -> b
$ \case
State workload
NoReaper -> (forall workload. State workload
NoReaper, workload
reaperEmpty)
Workload workload
x -> (forall workload. workload -> State workload
Workload workload
reaperEmpty, workload
x)
kill :: IORef (t ThreadId) -> m ()
kill IORef (t ThreadId)
tidRef = do
t ThreadId
mtid <- forall (m :: * -> *) a. MonadIO m => IORef a -> m a
readIORef IORef (t ThreadId)
tidRef
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ t ThreadId
mtid forall (m :: * -> *). MonadIO m => ThreadId -> m ()
killThread
add ::
MonadUnliftIO m =>
ReaperSettings m workload item ->
IORef (State workload) ->
IORef (Maybe ThreadId) ->
item ->
m ()
add :: forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item
-> IORef (State workload) -> IORef (Maybe ThreadId) -> item -> m ()
add settings :: ReaperSettings m workload item
settings@ReaperSettings {workload
Int
String
workload -> m (workload -> workload)
workload -> Bool
item -> workload -> workload
reaperThreadName :: String
reaperEmpty :: workload
reaperNull :: workload -> Bool
reaperCons :: item -> workload -> workload
reaperDelay :: Int
reaperAction :: workload -> m (workload -> workload)
reaperThreadName :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> String
reaperEmpty :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload
reaperNull :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload -> Bool
reaperCons :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> item -> workload -> workload
reaperDelay :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> Int
reaperAction :: forall (m :: * -> *) workload item.
ReaperSettings m workload item
-> workload -> m (workload -> workload)
..} IORef (State workload)
stateRef IORef (Maybe ThreadId)
tidRef item
item =
forall (m :: * -> *) a. MonadUnliftIO m => m a -> m a
mask_ forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => m (m a) -> m a
join forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a b.
MonadIO m =>
IORef a -> (a -> (a, b)) -> m b
atomicModifyIORef' IORef (State workload)
stateRef State workload -> (State workload, m ())
cons
where
cons :: State workload -> (State workload, m ())
cons State workload
NoReaper =
let wl :: workload
wl = item -> workload -> workload
reaperCons item
item workload
reaperEmpty
in (forall workload. workload -> State workload
Workload workload
wl, forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item
-> IORef (State workload) -> IORef (Maybe ThreadId) -> m ()
spawn ReaperSettings m workload item
settings IORef (State workload)
stateRef IORef (Maybe ThreadId)
tidRef)
cons (Workload workload
wl) =
let wl' :: workload
wl' = item -> workload -> workload
reaperCons item
item workload
wl
in (forall workload. workload -> State workload
Workload workload
wl', forall (m :: * -> *) a. Monad m => a -> m a
return ())
spawn ::
MonadUnliftIO m =>
ReaperSettings m workload item ->
IORef (State workload) ->
IORef (Maybe ThreadId) ->
m ()
spawn :: forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item
-> IORef (State workload) -> IORef (Maybe ThreadId) -> m ()
spawn ReaperSettings m workload item
settings IORef (State workload)
stateRef IORef (Maybe ThreadId)
tidRef = do
ThreadId
tid <- forall (m :: * -> *). MonadUnliftIO m => m () -> m ThreadId
forkIO forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item
-> IORef (State workload) -> IORef (Maybe ThreadId) -> m ()
reaper ReaperSettings m workload item
settings IORef (State workload)
stateRef IORef (Maybe ThreadId)
tidRef
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. ThreadId -> String -> IO ()
labelThread ThreadId
tid forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) workload item.
ReaperSettings m workload item -> String
reaperThreadName ReaperSettings m workload item
settings
forall (m :: * -> *) a. MonadIO m => IORef a -> a -> m ()
writeIORef IORef (Maybe ThreadId)
tidRef forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a
Just ThreadId
tid
reaper ::
MonadUnliftIO m =>
ReaperSettings m workload item ->
IORef (State workload) ->
IORef (Maybe ThreadId) ->
m ()
reaper :: forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item
-> IORef (State workload) -> IORef (Maybe ThreadId) -> m ()
reaper settings :: ReaperSettings m workload item
settings@ReaperSettings {workload
Int
String
workload -> m (workload -> workload)
workload -> Bool
item -> workload -> workload
reaperThreadName :: String
reaperEmpty :: workload
reaperNull :: workload -> Bool
reaperCons :: item -> workload -> workload
reaperDelay :: Int
reaperAction :: workload -> m (workload -> workload)
reaperThreadName :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> String
reaperEmpty :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload
reaperNull :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> workload -> Bool
reaperCons :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> item -> workload -> workload
reaperDelay :: forall (m :: * -> *) workload item.
ReaperSettings m workload item -> Int
reaperAction :: forall (m :: * -> *) workload item.
ReaperSettings m workload item
-> workload -> m (workload -> workload)
..} IORef (State workload)
stateRef IORef (Maybe ThreadId)
tidRef = do
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay Int
reaperDelay
workload
wl <- forall (m :: * -> *) a b.
MonadIO m =>
IORef a -> (a -> (a, b)) -> m b
atomicModifyIORef' IORef (State workload)
stateRef forall {b}. State b -> (State workload, b)
swapWithEmpty
!workload -> workload
merge <- workload -> m (workload -> workload)
reaperAction workload
wl
Bool
cont <- forall (m :: * -> *) a b.
MonadIO m =>
IORef a -> (a -> (a, b)) -> m b
atomicModifyIORef' IORef (State workload)
stateRef (forall {workload}.
(workload -> workload) -> State workload -> (State workload, Bool)
check workload -> workload
merge)
if Bool
cont
then forall (m :: * -> *) workload item.
MonadUnliftIO m =>
ReaperSettings m workload item
-> IORef (State workload) -> IORef (Maybe ThreadId) -> m ()
reaper ReaperSettings m workload item
settings IORef (State workload)
stateRef IORef (Maybe ThreadId)
tidRef
else forall (m :: * -> *) a. MonadIO m => IORef a -> a -> m ()
writeIORef IORef (Maybe ThreadId)
tidRef forall a. Maybe a
Nothing
where
swapWithEmpty :: State b -> (State workload, b)
swapWithEmpty State b
NoReaper = forall a. HasCallStack => String -> a
error String
"Control.Reaper.reaper: unexpected NoReaper (1)"
swapWithEmpty (Workload b
wl) = (forall workload. workload -> State workload
Workload workload
reaperEmpty, b
wl)
check :: (workload -> workload) -> State workload -> (State workload, Bool)
check workload -> workload
_ State workload
NoReaper = forall a. HasCallStack => String -> a
error String
"Control.Reaper.reaper: unexpected NoReaper (2)"
check workload -> workload
merge (Workload workload
wl)
| workload -> Bool
reaperNull workload
wl' = (forall workload. State workload
NoReaper, Bool
False)
| Bool
otherwise = (forall workload. workload -> State workload
Workload workload
wl', Bool
True)
where
wl' :: workload
wl' = workload -> workload
merge workload
wl
mkListAction ::
Monad m =>
(item -> m (Maybe item')) ->
[item] ->
m ([item'] -> [item'])
mkListAction :: forall (m :: * -> *) item item'.
Monad m =>
(item -> m (Maybe item')) -> [item] -> m ([item'] -> [item'])
mkListAction item -> m (Maybe item')
f =
forall {c}. ([item'] -> c) -> [item] -> m ([item'] -> c)
go forall a. a -> a
id
where
go :: ([item'] -> c) -> [item] -> m ([item'] -> c)
go ![item'] -> c
front [] = forall (f :: * -> *) a. Applicative f => a -> f a
pure [item'] -> c
front
go ![item'] -> c
front (item
x : [item]
xs) = do
Maybe item'
my <- item -> m (Maybe item')
f item
x
let front' :: [item'] -> c
front' =
case Maybe item'
my of
Maybe item'
Nothing -> [item'] -> c
front
Just item'
y -> [item'] -> c
front forall b c a. (b -> c) -> (a -> b) -> a -> c
. (item'
y forall a. a -> [a] -> [a]
:)
([item'] -> c) -> [item] -> m ([item'] -> c)
go [item'] -> c
front' [item]
xs