knit-haskell-0.8.0.0: a minimal Rmarkdown sort-of-thing for haskell, by way of Pandoc
Copyright(c) Adam Conner-Sax 2019
LicenseBSD-3-Clause
Maintaineradam_conner_sax@yahoo.com
Stabilityexperimental
Safe HaskellNone
LanguageHaskell2010

Knit.Effect.AtomicCache

Description

This module defines a key/value store (Polysemy) effect. Rather than return the usual Maybe v from a lookup, the cache returns a Maybe (WithCacheTime v), where WithCacheTime a wraps a value of type a along with a time-stamp (of type UTCTime). This module provides a thread-safe in-memory implementation as well as a disk-based persistent implementation and a combination of the two, where the disk-based layer sits behind the in-memory layer. In our use case, the stored values will be arrays of bytes, the result of serializing whatever data we wish to cache.

WithCacheTime is intended to simplify tracking dependencies among cached computations. For example, imagine you have two long running computations which you wish to cache so you need only run those computations once:

computeA :: m a
computeA = ...

computeB :: m b
computeB = ...

cachedA :: WithCacheTime m a 
cachedA :: retrieveOrMake serialize "a.bin" (pure ()) (const computeA)

cachedB :: WithCacheTime m b
cachedB = retrieveOrMake serialize "b.bin" (pure ()) (const computeB)

and you have a computation which depends on a and b and should also be cached, but we want to make sure it gets recomputed if either a or b do. We use the applicative instance of WithCacheTime to combine cached results into and inject them into later computations while taking into account the newest time-stamp among the dependencies:

computeC :: a -> b -> m c
computeC a b = ...

cDeps :: WithCachedTime m (a, b)
cDeps = (,) $ cachedA <*> cachedB

cachedC :: WithCacheTime m c
cachedC = retrieveOrMake serialize "c.bin" cDeps $ \(a, b) -> computeC a b

As with cachedA and cachedB, cachedC will run the computation if the key, "c.bin" in this case, is absent from the cache. In addition, cachedC will be recomputed even if it is in the cache, if the time-stamp of the cached value is older than either the time stamp of cachedA or cachedB.

WithCacheTime m a holds the time-stamp and a monadic computation which will produce an a. This allows deferral of the deserialization of cached data until we know that we need to use it. In the example above, suppose a is retrieved from cache, and b is computed fresh. cachedA holds a timestamp (the modification time of the file in cache or the time a was cached in memory) and a monadic computation which will deserialize the cached byte array retrieved for a. cachedB holds a time-stamp (the time the computation of b completes) and the trivial monadic action return b. Since b was just computed, the cached c is outdated and will be recomputed. At that point a is deserialized, b is unwrapped and thse are given to the function to compute c, which is then stored in cache as well as returned in the WithCacheTime m c, holding a new time-stamp.

If multiple threads attempt to lookup or retrieveOrMake at the same key at close to the same time, the first request will proceed, loading from cache if possible, and the other threads will block until the in-memory cache is populated or the first thread fails to fill in data.

This is intended to save CPU in the relatively common case that, e.g., several threads are launched to analyze the same data. The functions which load that data from on-disk-cache or produce it from other analyses need only be run once. Using the cache to facilitate this sharing still requires each thread to deserialize the data. If that cost is significant, you may want to compute the data before launching the threads.

NB: Should the action given to create the data, the (b -> m a) argument of retrieveOrMake somehow fail, this may lead to a situation where it runs on the first thread, fails, then runs on all the other threads simultaneously, presumably failing all those times as well.

Examples are available, and might be useful for seeing how all this works.

Synopsis

Effect

data Cache k v m a Source #

Key/Value store effect requiring its implementation to return values with time-stamps.

Instances

Instances details
type DefiningModule (Cache :: Type -> Type -> k -> Type -> Type) Source # 
Instance details

Defined in Knit.Effect.AtomicCache

type DefiningModule (Cache :: Type -> Type -> k -> Type -> Type) = "Knit.Effect.AtomicCache"

Time Stamps

Types

data WithCacheTime m a Source #

Wrapper to hold (deserializable, if necessary) content and a timestamp. The stamp must be at or after the time the data was constructed

Instances

Instances details
Functor m => Functor (WithCacheTime m) Source # 
Instance details

Defined in Knit.Effect.AtomicCache

Methods

fmap :: (a -> b) -> WithCacheTime m a -> WithCacheTime m b #

(<$) :: a -> WithCacheTime m b -> WithCacheTime m a #

Applicative m => Applicative (WithCacheTime m) Source # 
Instance details

Defined in Knit.Effect.AtomicCache

Methods

pure :: a -> WithCacheTime m a #

(<*>) :: WithCacheTime m (a -> b) -> WithCacheTime m a -> WithCacheTime m b #

liftA2 :: (a -> b -> c) -> WithCacheTime m a -> WithCacheTime m b -> WithCacheTime m c #

(*>) :: WithCacheTime m a -> WithCacheTime m b -> WithCacheTime m b #

(<*) :: WithCacheTime m a -> WithCacheTime m b -> WithCacheTime m a #

Show (m a) => Show (WithCacheTime m a) Source # 
Instance details

Defined in Knit.Effect.AtomicCache

type ActionWithCacheTime r a = WithCacheTime (Sem r) a Source #

Specialize WithCacheTime for use with a Polysemy effects stack.

Constructors

withCacheTime :: Maybe UTCTime -> m a -> WithCacheTime m a Source #

Construct a WithCacheTime from a Maybe Time.UTCTime and an action.

onlyCacheTime :: Applicative m => Maybe UTCTime -> WithCacheTime m () Source #

Construct a WithCacheTime with a time and no action.

Combinators

ignoreCacheTime :: WithCacheTime m a -> m a Source #

Access the computation part of a WithCacheTime a. This or ignoreCacheTimeM is required to use the cached value as anything but input to another cached computation.

ignoreCacheTimeM :: Monad m => m (WithCacheTime m a) -> m a Source #

Access the computation part of an m (WithCacheTime a). This or ignoreCacheTime is required to use the cached value as anything but input to another cached computation.

cacheTime :: WithCacheTime m a -> Maybe UTCTime Source #

Access the Maybe Time.UTCTime part of a WithCacheTime

Utilities

wctMapAction :: (m a -> n b) -> WithCacheTime m a -> WithCacheTime n b Source #

Map one type of action to another. NB: 'WithCacheTime m' is a functor (as long as m is), so if m is not changing, you should prefer fmap to this function.

Cache Actions

encodeAndStore Source #

Arguments

:: (Show k, Member (Cache k ct) r, LogWithPrefixesLE r) 
=> Serialize CacheError r a ct

Record-Of-Functions for serialization/deserialization

-> k

Key

-> a

Data to encode and cache

-> Sem r () 

Combine the action of serializing and caching

retrieveAndDecode Source #

Arguments

:: forall ct k r a. (Member (Cache k ct) r, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) 
=> Serialize CacheError r a ct

Record-Of-Functions for serialization/deserialization

-> k

Key

-> Maybe UTCTime

UTCTime which cached data must be newer than. Use Nothing if any cached data is acceptable.

-> Sem r (ActionWithCacheTime r a)

Result of lookup or running computation, wrapped as ActionWithCacheTime. Throws CacheError if lookup fails.

Combine the action of retrieving from cache and deserializing. | Throws if item not found or any other error during retrieval

lookupAndDecode Source #

Arguments

:: forall ct k r a. (Member (Cache k ct) r, LogWithPrefixesLE r, Member (Embed IO) r, MemberWithError (Error CacheError) r, Show k) 
=> Serialize CacheError r a ct

Record-Of-Functions for serialization/deserialization

-> k

Key

-> Maybe UTCTime

UTCTime which cached data must be newer than. Use Nothing if any cached data is acceptable.

-> Sem r (Maybe (ActionWithCacheTime r a))

Result of lookup or running computation, wrapped as ActionWithCacheTime. Returns 'Nothing" if lookup fails.

Combine the action of retrieving from cache and deserializing. | Returns Nothing if item not found, and throws on any other error.

retrieveOrMake Source #

Arguments

:: forall ct k r a b. (Member (Cache k ct) r, LogWithPrefixesLE r, Member (Embed IO) r, MemberWithError (Error CacheError) r, Show k) 
=> Serialize CacheError r a ct

Record-Of-Functions for serialization/deserialization

-> k

Key

-> ActionWithCacheTime r b

Cached Dependencies

-> (b -> Sem r a)

Computation to produce a if lookup fails.

-> Sem r (ActionWithCacheTime r a)

Result of lookup or running computation, wrapped as ActionWithCacheTime

Lookup key and, if that fails, run an action to update the cache. Further, if the item is in cache, but older than time-stamp of the supplied 'ActionWithCacheTime r b', this function calls the given b -> P.Sem r (Maybe a) with the cached value from the supplied 'ActionWithCacheTime m b'. Throws if item not found *and* making fails.

clear :: Member (Cache k ct) r => k -> Sem r () Source #

Clear the cache at a given key. Throws an exception if item is not present.

clearIfPresent :: (Member (Cache k ct) r, MemberWithError (Error CacheError) r) => k -> Sem r () Source #

Clear the cache at a given key. Doesn't throw if item is missing.

Effect Interpretations

Persist To Disk

persistStreamlyByteArray :: (Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => (k -> FilePath) -> InterpreterFor (Cache k (Array Word8)) r Source #

Interpreter for Cache via persistence to disk as a Streamly Memory.Array (Contiguous storage of Storables) of Bytes (Word8)

persistLazyByteString :: (Members '[Embed IO] r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) => (k -> FilePath) -> InterpreterFor (Cache k ByteString) r Source #

Interpreter for Cache via persistence to disk as a lazy ByteString

persistStrictByteString :: (Members '[Embed IO] r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) => (k -> FilePath) -> InterpreterFor (Cache k ByteString) r Source #

Interpreter for Cache via persistence to disk as a strict ByteString

Thread-safe Map

type AtomicMemCache k v = TVar (Map k (TMVar (Maybe (WithCacheTime Identity v)))) Source #

Specific type of in-memory cache.

runAtomicInMemoryCache :: (Ord k, Show k, Member (Embed IO) r, LogWithPrefixesLE r) => AtomicMemCache k ct -> InterpreterFor (Cache k ct) r Source #

Interpreter for in-memory only AtomicMemCache

Combined Map/Disk

runBackedAtomicInMemoryCache :: (Ord k, Show k, LogWithPrefixesLE r, Members '[Embed IO, Cache k ct] r) => AtomicMemCache k ct -> InterpreterFor (Cache k ct) r Source #

interpret Cache via a different-Cache-backed AtomicMemCache

runPersistenceBackedAtomicInMemoryCache :: (Ord k, Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => InterpreterFor (Cache k ct) r -> AtomicMemCache k ct -> InterpreterFor (Cache k ct) r Source #

Interpret Cache via AtomicMemCache and an interpreter for a backing cache, usually a persistence layer.

runPersistenceBackedAtomicInMemoryCache' :: (Ord k, Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => InterpreterFor (Cache k ct) r -> InterpreterFor (Cache k ct) r Source #

Interpret Cache via AtomicMemCache and an interpreter for a backing cache, usually a persistence layer. Create a new, empty, AtomicMemCache to begin.

Exceptions

data CacheError Source #

Error Type for Cache errors. Simplifies catching and reporting them.

Instances

Instances details
Eq CacheError Source # 
Instance details

Defined in Knit.Effect.AtomicCache

Show CacheError Source # 
Instance details

Defined in Knit.Effect.AtomicCache