-- | Caching implementation internals. -- -- This module is not part of the Data.Cache public API and should not be directly relied upon by users. -- Anything meant for external use defined here will be re-exported by a public module. module Data.Cache.Internal ( MonadCache (..), DelayMode (..), FailureMode (..), CacheOptions (..), ) where import Control.Concurrent import Control.Monad (forever) import Control.Monad.Catch import Control.Monad.IO.Class import Data.Time import System.Random.Stateful -- | The top-level 'Monad' in which caching operations are performed. -- -- This exists primarily for testing purposes. Production uses should drop in 'IO' and forget that this exists. class (MonadCatch m, MonadIO m) => MonadCache m where -- | The current system time. currentTime :: m UTCTime -- | Generate a random number between two bounds (inclusive). randomize :: (Int, Int) -> m Int -- | Delay execution of the current thread for a number of microseconds. delay :: Int -> m () -- | Spawn a new thread of execution to run an action. newThread :: m () -> m ThreadId -- | Run an action forever. repeatedly :: m () -> m () -- | Stop a thread of execution. The thread can be assumed to have been started using 'newThread'. killCache :: ThreadId -> m () instance MonadCache IO where currentTime = getCurrentTime randomize bounds = do gen <- getStdGen >>= newIOGenM uniformRM bounds gen delay = threadDelay newThread = forkIO repeatedly = forever killCache = killThread -- | The supported delay modes for a 'Data.Cache.PollingCache' instance. -- -- The delay associated with a cache instance define the amount of time that will pass between -- cache refreshes. data DelayMode a = -- | Delay for static number of microseconds between each refresh. DelayForMicroseconds Int | -- | Delay for a dynamic number of microseconds between each refresh. -- -- This is useful if different delays should be used for successes or failures, or -- if the result being retrieved contains information that could affect the delay period. DelayDynamically (Either SomeException a -> Int) | -- | Delay for a dynamic number of microseconds between each refresh within a set of bounds. -- -- This is similarly useful to 'DelayDynamically', but when a known lower and upper bound should -- be applied to the delay period. Regardless of the dynamic delay generated by the user-supplied -- function, the delay period will never be below the lower bound or above the upper bound. DelayDynamicallyWithBounds (Int, Int) (Either SomeException a -> Int) -- | The supported failure handling modes for a 'Data.Cache.PollingCache' instance. -- -- In the context of the cache action, "failure" means an Exception thrown from -- the user-supplied action that generates values to populate the cache. -- -- Because these operations are performed in a background thread, the user must decide how failures are to be handled -- upon cache creation. data FailureMode = -- | Failures should be ignored entirely; the most relaxed failure handling strategy. -- -- This means that 'Data.Cache.LoadFailed' will never be populated as a cache result. Ignore | -- | If a failure occurs, any previously cached value is immediately evicted from the cache; the strictest failure handling strategy. EvictImmediately | -- | Failures will be ignored unless they persist beyond the supplied time span. -- -- This is a middle-ground failure handling strategy that probably makes sense to use in most scenarios. -- The nature of asynchronous polling implies that somewhat stale values are not an issue to the consumer; -- therefore, allowing some level of transient failure can often improve reliability without sacrificing correctness. EvictAfterTime NominalDiffTime deriving (Eq, Show) -- | Options that dictate the behavior of a 'Data.Cache.PollingCache' instance. data CacheOptions a = CacheOptions { -- | The 'DelayMode' to use. delayMode :: DelayMode a, -- | The 'FailureMode' to use. failureMode :: FailureMode, -- | An optional fuzzing factor. -- -- If provided, this factor defines the maximum number of microseconds that can be randomly added to each -- delay period. This means that when fuzzing is enabled, the delay between any two refreshes will always be -- greater than the delay period generated by the cache's 'DelayMode'. 'Nothing' disables fuzzing completely. delayFuzzing :: Maybe Int }