packages feed

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 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