packages feed

polling-cache-0.0.1.0: src/Data/Cache/Internal.hs

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