auto-update 0.1.3 → 0.1.3.1
raw patch · 5 files changed
+166/−47 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- ChangeLog.md +11/−2
- Control/AutoUpdate.hs +41/−22
- Control/Debounce.hs +27/−11
- Control/Reaper.hs +86/−11
- auto-update.cabal +1/−1
ChangeLog.md view
@@ -1,2 +1,11 @@-__0.1.3__ Adding a new AIP - reaperKill-__0.1.2__ Added Control.Debounce+## 0.1.3.1++* Doc improvements++## 0.1.3++* Adding a new AIP - reaperKill++## 0.1.2++* Added Control.Debounce
Control/AutoUpdate.hs view
@@ -1,21 +1,40 @@--- | A common problem is the desire to have an action run at a scheduled--- interval, but only if it is needed. For example, instead of having--- every web request result in a new @getCurrentTime@ call, we'd like to--- have a single worker thread run every second, updating an @IORef@.--- However, if the request frequency is less than once per second, this is--- a pessimization, and worse, kills idle GC.+-- | In a multithreaded environment, running actions on a regularly scheduled+-- background thread can dramatically improve performance.+-- For example, web servers need to return the current time with each HTTP response.+-- For a high-volume server, it's much faster for a dedicated thread to run every+-- second, and write the current time to a shared 'IORef', than it is for each+-- request to make its own call to 'getCurrentTime'. ----- This library allows you to define actions which will either be--- performed by a dedicated thread or, in times of low volume, will be--- executed by the calling thread.+-- But for a low-volume server, whose request frequency is less than once per +-- second, that approach will result in /more/ calls to 'getCurrentTime' than +-- necessary, and worse, kills idle GC.+--+-- This library solves that problem by allowing you to define actions which will+-- either be performed by a dedicated thread, or, in times of low volume, will +-- be executed by the calling thread.+--+-- Example usage:+--+-- @+-- import "Data.Time"+-- import "Control.AutoUpdate"+--+-- getTime <- 'mkAutoUpdate' 'defaultUpdateSettings'+-- { 'updateAction' = 'Data.Time.Clock.getCurrentTime'+-- , 'updateFreq' = 1000000 -- The default frequency, once per second+-- }+-- currentTime <- getTime+-- @+--+-- For more examples, <http://www.yesodweb.com/blog/2014/08/announcing-auto-update see the blog post introducing this library>. module Control.AutoUpdate ( -- * Type UpdateSettings , defaultUpdateSettings -- * Accessors+ , updateAction , updateFreq , updateSpawnThreshold- , updateAction -- * Creation , mkAutoUpdate ) where@@ -27,9 +46,9 @@ import Control.Monad (void) import Data.IORef (newIORef, readIORef, writeIORef) --- | Default value for creating an @UpdateSettings@.+-- | Default value for creating an 'UpdateSettings'. ----- Since 0.1.0+-- @since 0.1.0 defaultUpdateSettings :: UpdateSettings () defaultUpdateSettings = UpdateSettings { updateFreq = 1000000@@ -39,44 +58,44 @@ -- | Settings to control how values are updated. ----- This should be constructed using @defaultUpdateSettings@ and record+-- This should be constructed using 'defaultUpdateSettings' and record -- update syntax, e.g.: -- -- @--- let set = defaultUpdateSettings { updateAction = getCurrentTime }+-- let settings = 'defaultUpdateSettings' { 'updateAction' = 'Data.Time.Clock.getCurrentTime' } -- @ ----- Since 0.1.0+-- @since 0.1.0 data UpdateSettings a = UpdateSettings { updateFreq :: Int -- ^ Microseconds between update calls. Same considerations as- -- @threadDelay@ apply.+ -- 'threadDelay' apply. -- -- Default: 1 second (1000000) --- -- Since 0.1.0+ -- @since 0.1.0 , updateSpawnThreshold :: Int -- ^ NOTE: This value no longer has any effect, since worker threads are -- dedicated instead of spawned on demand. --- -- Previously, this determined: How many times the data must be requested+ -- Previously, this determined how many times the data must be requested -- before we decide to spawn a dedicated thread. -- -- Default: 3 --- -- Since 0.1.0+ -- @since 0.1.0 , updateAction :: IO a -- ^ Action to be performed to get the current value. -- -- Default: does nothing. --- -- Since 0.1.0+ -- @since 0.1.0 } -- | Generate an action which will either read from an automatically -- updated value, or run the update action in the current thread. ----- Since 0.1.0+-- @since 0.1.0 mkAutoUpdate :: UpdateSettings a -> IO (IO a) mkAutoUpdate us = do -- A baton to tell the worker thread to generate a new value.@@ -158,7 +177,7 @@ -- we have a current value, use it Right val -> return val --- | Turn a runtime exception into an impure exception, so that all @IO@+-- | Turn a runtime exception into an impure exception, so that all 'IO' -- actions will complete successfully. This simply defers the exception until -- the value is forced. catchSome :: IO a -> IO a
Control/Debounce.hs view
@@ -3,10 +3,26 @@ -- period of time. -- -- This is useful as an optimization, for example to ensure that logs are only--- flushed to disk at most once per second. See the fast-logger package for an--- example usage.+-- flushed to disk at most once per second. ----- Since 0.1.2+-- Example usage:+--+-- @+-- printString <- 'mkDebounce' 'defaultDebounceSettings'+-- { 'debounceAction' = putStrLn "Running action"+-- , 'debounceFreq' = 5000000 -- 5 seconds+-- }+-- @+--+-- >>> printString+-- Running action+-- >>> printString+-- <Wait five seconds>+-- Running action+--+-- See the fast-logger package ("System.Log.FastLogger") for real-world usage.+--+-- @since 0.1.2 module Control.Debounce ( -- * Type DebounceSettings@@ -25,14 +41,14 @@ -- | Settings to control how debouncing should work. ----- This should be constructed using @defaultDebounceSettings@ and record+-- This should be constructed using 'defaultDebounceSettings' and record -- update syntax, e.g.: -- -- @--- let set = defaultDebounceSettings { debounceAction = flushLog }+-- let settings = 'defaultDebounceSettings' { 'debounceAction' = flushLog } -- @ ----- Since 0.1.2+-- @since 0.1.2 data DebounceSettings = DebounceSettings { debounceFreq :: Int -- ^ Microseconds lag required between subsequence calls to the debounced@@ -40,7 +56,7 @@ -- -- Default: 1 second (1000000) --- -- Since 0.1.2+ -- @since 0.1.2 , debounceAction :: IO () -- ^ Action to be performed. --@@ -48,12 +64,12 @@ -- -- Default: does nothing. --- -- Since 0.1.2+ -- @since 0.1.2 } --- | Default value for creating a @DebounceSettings@.+-- | Default value for creating a 'DebounceSettings'. ----- Since 0.1.2+-- @since 0.1.2 defaultDebounceSettings :: DebounceSettings defaultDebounceSettings = DebounceSettings { debounceFreq = 1000000@@ -64,7 +80,7 @@ -- performed. The action will either be performed immediately, or after the -- current cooldown period has expired. ----- Since 0.1.2+-- @since 0.1.2 mkDebounce :: DebounceSettings -> IO (IO ()) mkDebounce (DebounceSettings freq action) = do baton <- newEmptyMVar
Control/Reaper.hs view
@@ -3,8 +3,18 @@ -- | 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.+-- 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 <https://github.com/yesodweb/wai WAI family of packages>+-- for imports of "Control.Reaper". module Control.Reaper (+ -- * Example: Regularly cleaning a cache+ -- $example1+ -- * Settings ReaperSettings , defaultReaperSettings@@ -33,7 +43,7 @@ -- be a list of @item@s. This is encouraged by 'defaultReaperSettings' and -- 'mkListAction'. ----- Since 0.1.1+-- @since 0.1.1 data ReaperSettings workload item = ReaperSettings { reaperAction :: workload -> IO (workload -> workload) -- ^ The action to perform on a workload. The result of this is a@@ -46,38 +56,38 @@ -- temporary workload. This is incredibly useless; you should -- definitely override this default. --- -- Since 0.1.1+ -- @since 0.1.1 , reaperDelay :: {-# UNPACK #-} !Int -- ^ Number of microseconds to delay between calls of 'reaperAction'. -- -- Default: 30 seconds. --- -- Since 0.1.1+ -- @since 0.1.1 , reaperCons :: item -> workload -> workload -- ^ Add an item onto a workload. -- -- Default: list consing. --- -- Since 0.1.1+ -- @since 0.1.1 , reaperNull :: workload -> Bool -- ^ Check if a workload is empty, in which case the worker thread -- will shut down. -- -- Default: 'null'. --- -- Since 0.1.1+ -- @since 0.1.1 , reaperEmpty :: workload -- ^ An empty workload. -- -- Default: empty list. --- -- Since 0.1.1+ -- @since 0.1.1 } -- | Default @ReaperSettings@ value, biased towards having a list of work -- items. ----- Since 0.1.1+-- @since 0.1.1 defaultReaperSettings :: ReaperSettings [item] item defaultReaperSettings = ReaperSettings { reaperAction = \wl -> return (wl ++)@@ -104,11 +114,11 @@ data State workload = NoReaper -- ^ No reaper thread | Workload workload -- ^ The current jobs --- | Create a reaper addition function. This funciton can be used to add+-- | 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+-- @since 0.1.1 mkReaper :: ReaperSettings workload item -> IO (Reaper workload item) mkReaper settings@ReaperSettings{..} = do stateRef <- newIORef NoReaper@@ -187,7 +197,7 @@ -- return either a new work item, or @Nothing@ if the work item is -- expired. ----- Since 0.1.1+-- @since 0.1.1 mkListAction :: (item -> IO (Maybe item')) -> [item] -> IO ([item'] -> [item'])@@ -202,3 +212,68 @@ Nothing -> front Just y -> front . (y:) go front' xs++-- $example1+-- In this example code, we use a 'Data.Map.Strict.Map' to cache fibonacci numbers, and a 'Reaper' to prune the cache. +--+-- 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 ('Control.Monad.forever'). Each second it calculates the fibonacci number +-- for a value between 30 and 34, first trying the cache ('reaperRead' and 'Data.Map.Strict.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 = 'Data.Map.Strict.Map' 'Int' ('Int', 'Data.Time.Clock.UTCTime')+--+-- main :: IO ()+-- main = do+-- reaper <- 'mkReaper' 'defaultReaperSettings'+-- { 'reaperEmpty' = Map.'Data.Map.Strict.empty'+-- , 'reaperCons' = \\(k, v, time) workload -> Map.'Data.Map.Strict.insert' k (v, time) workload+-- , 'reaperAction' = clean+-- , 'reaperDelay' = 1000000 * 2 -- Clean every 2 seconds+-- , 'reaperNull' = Map.'Data.Map.Strict.null'+-- }+-- forever $ do+-- fibArg <- 'System.Random.getStdRandom' ('System.Random.randomR' (30,34))+-- cache <- 'reaperRead' reaper+-- let cachedResult = Map.'Data.Map.Strict.lookup' fibArg cache+-- case cachedResult of+-- 'Just' (fibResult, _createdAt) -> 'putStrLn' $ "Found in cache: `fib " ++ 'show' fibArg ++ "` " ++ 'show' fibResult+-- 'Nothing' -> do+-- let fibResult = fib fibArg+-- 'putStrLn' $ "Calculating `fib " ++ 'show' fibArg ++ "` " ++ 'show' fibResult+-- time <- 'Data.Time.Clock.getCurrentTime'+-- ('reaperAdd' reaper) (fibArg, fibResult, time)+-- 'threadDelay' 1000000 -- 1 second+--+-- -- Remove items > 10 seconds old+-- clean :: Cache -> IO (Cache -> Cache)+-- clean oldMap = do+-- currentTime <- 'Data.Time.Clock.getCurrentTime'+-- let pruned = Map.'Data.Map.Strict.filter' (\\(_, createdAt) -> currentTime \`diffUTCTime\` createdAt < 10.0) oldMap+-- return (\\newData -> Map.'Data.Map.Strict.union' pruned newData)+-- @
auto-update.cabal view
@@ -1,5 +1,5 @@ name: auto-update-version: 0.1.3+version: 0.1.3.1 synopsis: Efficiently run periodic, on-demand actions description: API docs and the README are available at <http://www.stackage.org/package/auto-update>. homepage: https://github.com/yesodweb/wai