Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
This module provides the ability to create reapers: dedicated cleanup threads. These threads will automatically spawn and die based on the presence of a workload to process on. Example uses include:
- Killing long-running jobs
- Closing unused connections in a connection pool
- Pruning a cache of old items (see example below)
For real-world usage, search the WAI family of packages for imports of Control.Reaper.
Synopsis
- data ReaperSettings workload item
- defaultReaperSettings :: ReaperSettings [item] item
- reaperAction :: ReaperSettings workload item -> workload -> IO (workload -> workload)
- reaperDelay :: ReaperSettings workload item -> Int
- reaperCons :: ReaperSettings workload item -> item -> workload -> workload
- reaperNull :: ReaperSettings workload item -> workload -> Bool
- reaperEmpty :: ReaperSettings workload item -> workload
- reaperThreadName :: ReaperSettings workload item -> String
- data Reaper workload item
- reaperAdd :: Reaper workload item -> item -> IO ()
- reaperRead :: Reaper workload item -> IO workload
- reaperModify :: Reaper workload item -> (workload -> workload) -> IO workload
- reaperStop :: Reaper workload item -> IO workload
- reaperKill :: Reaper workload item -> IO ()
- mkReaper :: ReaperSettings workload item -> IO (Reaper workload item)
- mkListAction :: (item -> IO (Maybe item')) -> [item] -> IO ([item'] -> [item'])
Example: Regularly cleaning a cache
In this example code, we use a Map
to cache fibonacci numbers, and a Reaper
to prune the cache.
NOTE: When using this module as a cache you should keep in mind that while
the reaper thread is active running your "reaperAction", the cache will
appear empty to concurrently running threads. Any newly created cache
entries will be on the temporary worklist, and will merged back into the the
main cache only once the "reaperAction" completes (together with the portion
of the extant worklist that the cleaner
callback decided to retain).
If you're looking for a cache that supports concurrent purging of stale items, but without exposing a transient empty cache during cleanup, this is not the cache implementation you need. This module was primarily designed for cleaning up stuck processes, or idle threads in a thread pool. The cache use-case was not a primary design focus.
The main
function first creates a Reaper
, with fields to initialize the
cache (reaperEmpty
), add items to it (reaperCons
), and prune it (reaperAction
).
The reaper will run every two seconds (reaperDelay
), but will stop running while
reaperNull
is true.
main
then loops infinitely (forever
). Each second it calculates the fibonacci number
for a value between 30 and 34, first trying the cache (reaperRead
and lookup
),
then falling back to manually calculating it (fib
)
and updating the cache with the result (reaperAdd
)
clean
simply removes items cached for more than 10 seconds.
This function is where you would perform IO-related cleanup,
like killing threads or closing connections, if that was the purpose of your reaper.
module Main where import Data.Time (UTCTime, getCurrentTime, diffUTCTime) import Control.Reaper import Control.Concurrent (threadDelay) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Control.Monad (forever) import System.Random (getStdRandom, randomR) fib ::Int
->Int
fib 0 = 0 fib 1 = 1 fib n = fib (n-1) + fib (n-2) type Cache =Map
Int
(Int
,UTCTime
) main :: IO () main = do reaper <-mkReaper
defaultReaperSettings
{reaperEmpty
= Map.empty
,reaperCons
= \(k, v, time) workload -> Map.insert
k (v, time) workload ,reaperAction
= clean ,reaperDelay
= 1000000 * 2 -- Clean every 2 seconds ,reaperNull
= Map.null
} forever $ do fibArg <-getStdRandom
(randomR
(30,34)) cache <-reaperRead
reaper let cachedResult = Map.lookup
fibArg cache case cachedResult ofJust
(fibResult, _createdAt) ->putStrLn
$ "Found in cache: `fib " ++show
fibArg ++ "` " ++show
fibResultNothing
-> do let fibResult = fib fibArgputStrLn
$ "Calculating `fib " ++show
fibArg ++ "` " ++show
fibResult time <-getCurrentTime
(reaperAdd
reaper) (fibArg, fibResult, time)threadDelay
1000000 -- 1 second -- Remove items > 10 seconds old clean :: Cache -> IO (Cache -> Cache) clean oldMap = do currentTime <-getCurrentTime
let pruned = Map.filter
(\(_, createdAt) -> currentTime `diffUTCTime` createdAt < 10.0) oldMap return (\newData -> Map.union
pruned newData)
Settings
data ReaperSettings workload item Source #
Settings for creating a reaper. This type has two parameters:
workload
gives the entire workload, whereas item
gives an
individual piece of the queue. A common approach is to have workload
be a list of item
s. This is encouraged by defaultReaperSettings
and
mkListAction
.
Since: 0.1.1
defaultReaperSettings :: ReaperSettings [item] item Source #
Default ReaperSettings
value, biased towards having a list of work
items.
Since: 0.1.1
Accessors
reaperAction :: ReaperSettings workload item -> workload -> IO (workload -> workload) Source #
The action to perform on a workload. The result of this is a
"workload modifying" function. In the common case of using lists,
the result should be a difference list that prepends the remaining
workload to the temporary workload. The temporary workload here
refers to items added to the workload while the reaper action is
running. For help with setting up such an action, see mkListAction
.
Default: do nothing with the workload, and then prepend it to the temporary workload. This is incredibly useless; you should definitely override this default.
Since: 0.1.1
reaperDelay :: ReaperSettings workload item -> Int Source #
reaperCons :: ReaperSettings workload item -> item -> workload -> workload Source #
Add an item onto a workload.
Default: list consing.
Since: 0.1.1
reaperNull :: ReaperSettings workload item -> workload -> Bool Source #
Check if a workload is empty, in which case the worker thread will shut down.
Default: null
.
Since: 0.1.1
reaperEmpty :: ReaperSettings workload item -> workload Source #
An empty workload.
Default: empty list.
Since: 0.1.1
reaperThreadName :: ReaperSettings workload item -> String Source #
Type
reaperRead :: Reaper workload item -> IO workload Source #
Reading workload.
reaperModify :: Reaper workload item -> (workload -> workload) -> IO workload Source #
Modify the workload. The resulting workload is returned.
If there is no reaper thread, the modifier will not be applied and
reaperEmpty
will be returned.
If the reaper is currently executing jobs, those jobs will not be in the given workload and the workload might appear empty.
If all jobs are removed by the modifier, the reaper thread will not be
killed. The reaper thread will only terminate if reaperKill
is called
or the result of reaperAction
satisfies reaperNull
.
Since: 0.2.0
reaperStop :: Reaper workload item -> IO workload Source #
Stopping the reaper thread if exists. The current workload is returned.
reaperKill :: Reaper workload item -> IO () Source #
Killing the reaper thread immediately if exists.
Creation
mkReaper :: ReaperSettings workload item -> IO (Reaper workload item) Source #
Create a reaper addition function. This function can be used to add new items to the workload. Spawning of reaper threads will be handled for you automatically.
Since: 0.1.1
Helper
mkListAction :: (item -> IO (Maybe item')) -> [item] -> IO ([item'] -> [item']) Source #
A helper function for creating reaperAction
functions. You would
provide this function with a function to process a single work item and
return either a new work item, or Nothing
if the work item is
expired.
Since: 0.1.1