haxl-2.0.1.1: A Haskell library for efficient, concurrent, and concise data access.

Safe HaskellNone
LanguageHaskell2010

Haxl.Core

Contents

Description

Everything needed to define data sources and to invoke the engine.

Synopsis

The monad and operations

newtype GenHaxl u a Source #

The Haxl monad, which does several things:

  • It is a reader monad for Env, which contains the current state of the scheduler, including unfetched requests and the run queue of computations.
  • It is a concurrency, or resumption, monad. A computation may run partially and return Blocked, in which case the framework should perform the outstanding requests in the RequestStore, and then resume the computation.
  • The Applicative combinator <*> explores both branches in the event that the left branch is Blocked, so that we can collect multiple requests and submit them as a batch.
  • It contains IO, so that we can perform real data fetching.

Constructors

GenHaxl 

Fields

Instances
Monad (GenHaxl u) Source # 
Instance details

Defined in Haxl.Core.Monad

Methods

(>>=) :: GenHaxl u a -> (a -> GenHaxl u b) -> GenHaxl u b #

(>>) :: GenHaxl u a -> GenHaxl u b -> GenHaxl u b #

return :: a -> GenHaxl u a #

fail :: String -> GenHaxl u a #

Functor (GenHaxl u) Source # 
Instance details

Defined in Haxl.Core.Monad

Methods

fmap :: (a -> b) -> GenHaxl u a -> GenHaxl u b #

(<$) :: a -> GenHaxl u b -> GenHaxl u a #

Applicative (GenHaxl u) Source # 
Instance details

Defined in Haxl.Core.Monad

Methods

pure :: a -> GenHaxl u a #

(<*>) :: GenHaxl u (a -> b) -> GenHaxl u a -> GenHaxl u b #

liftA2 :: (a -> b -> c) -> GenHaxl u a -> GenHaxl u b -> GenHaxl u c #

(*>) :: GenHaxl u a -> GenHaxl u b -> GenHaxl u b #

(<*) :: GenHaxl u a -> GenHaxl u b -> GenHaxl u a #

MonadThrow (GenHaxl u) Source #

Since: haxl-0.3.1.0

Instance details

Defined in Haxl.Core.Monad

Methods

throwM :: Exception e => e -> GenHaxl u a #

MonadCatch (GenHaxl u) Source #

Since: haxl-0.3.1.0

Instance details

Defined in Haxl.Core.Monad

Methods

catch :: Exception e => GenHaxl u a -> (e -> GenHaxl u a) -> GenHaxl u a #

Fractional a => Fractional (GenHaxl u a) # 
Instance details

Defined in Haxl.Prelude

Methods

(/) :: GenHaxl u a -> GenHaxl u a -> GenHaxl u a #

recip :: GenHaxl u a -> GenHaxl u a #

fromRational :: Rational -> GenHaxl u a #

Num a => Num (GenHaxl u a) # 
Instance details

Defined in Haxl.Prelude

Methods

(+) :: GenHaxl u a -> GenHaxl u a -> GenHaxl u a #

(-) :: GenHaxl u a -> GenHaxl u a -> GenHaxl u a #

(*) :: GenHaxl u a -> GenHaxl u a -> GenHaxl u a #

negate :: GenHaxl u a -> GenHaxl u a #

abs :: GenHaxl u a -> GenHaxl u a #

signum :: GenHaxl u a -> GenHaxl u a #

fromInteger :: Integer -> GenHaxl u a #

IsString a => IsString (GenHaxl u a) Source # 
Instance details

Defined in Haxl.Core.Monad

Methods

fromString :: String -> GenHaxl u a #

u1 ~ u2 => IfThenElse (GenHaxl u1 Bool) (GenHaxl u2 a) Source # 
Instance details

Defined in Haxl.Prelude

Methods

ifThenElse :: GenHaxl u1 Bool -> GenHaxl u2 a -> GenHaxl u2 a -> GenHaxl u2 a Source #

runHaxl :: forall u a. Env u -> GenHaxl u a -> IO a Source #

Runs a Haxl computation in the given Env.

Env

data Env u Source #

The data we carry around in the Haxl monad.

Constructors

Env 

Fields

Operations in the monad

env :: (Env u -> a) -> GenHaxl u a Source #

Extracts data from the Env.

withEnv :: Env u -> GenHaxl u a -> GenHaxl u a Source #

Returns a version of the Haxl computation which always uses the provided Env, ignoring the one specified by runHaxl.

withLabel :: ProfileLabel -> GenHaxl u a -> GenHaxl u a Source #

Label a computation so profiling data is attributed to the label.

Building the Env

initEnvWithData :: StateStore -> u -> Caches u -> IO (Env u) Source #

Initialize an environment with a StateStore, an input map, a preexisting DataCache, and a seed for the random number generator.

initEnv :: StateStore -> u -> IO (Env u) Source #

Initializes an environment with StateStore and an input map.

emptyEnv :: u -> IO (Env u) Source #

A new, empty environment.

Building the StateStore

stateGet :: forall r. StateKey r => StateStore -> Maybe (State r) Source #

Retrieves a State from the StateStore container.

stateSet :: forall f. StateKey f => State f -> StateStore -> StateStore Source #

Inserts a State in the StateStore container.

stateEmpty :: StateStore Source #

A StateStore with no entries.

Exceptions

throw :: Exception e => e -> GenHaxl u a Source #

Throw an exception in the Haxl monad

catch :: Exception e => GenHaxl u a -> (e -> GenHaxl u a) -> GenHaxl u a Source #

Catch an exception in the Haxl monad

catchIf :: Exception e => (e -> Bool) -> GenHaxl u a -> (e -> GenHaxl u a) -> GenHaxl u a Source #

Catch exceptions that satisfy a predicate

try :: Exception e => GenHaxl u a -> GenHaxl u (Either e a) Source #

Returns Left e if the computation throws an exception e, or Right a if it returns a result a.

tryToHaxlException :: GenHaxl u a -> GenHaxl u (Either HaxlException a) Source #

Like try, but lifts all exceptions into the HaxlException hierarchy. Uses unsafeToHaxlException internally. Typically this is used at the top level of a Haxl computation, to ensure that all exceptions are caught.

Data fetching and caching

dataFetch :: (DataSource u r, Request r a) => r a -> GenHaxl u a Source #

Performs actual fetching of data for a Request from a DataSource.

uncachedRequest :: (DataSource u r, Request r a) => r a -> GenHaxl u a Source #

A data request that is not cached. This is not what you want for normal read requests, because then multiple identical requests may return different results, and this invalidates some of the properties that we expect Haxl computations to respect: that data fetches can be arbitrarily reordered, and identical requests can be commoned up, for example.

uncachedRequest is useful for performing writes, provided those are done in a safe way - that is, not mixed with reads that might conflict in the same Haxl computation.

if we are recording or running a test, we fallback to using dataFetch This allows us to store the request in the cache when recording, which allows a transparent run afterwards. Without this, the test would try to call the datasource during testing and that would be an exception.

cacheRequest :: Request req a => req a -> Either SomeException a -> GenHaxl u () Source #

Inserts a request/result pair into the cache. Throws an exception if the request has already been issued, either via dataFetch or cacheRequest.

This can be used to pre-populate the cache when running tests, to avoid going to the actual data source and ensure that results are deterministic.

cacheResult :: Request r a => r a -> IO a -> GenHaxl u a Source #

Transparently provides caching. Useful for datasources that can return immediately, but also caches values. Exceptions thrown by the IO operation (except for asynchronous exceptions) are propagated into the Haxl monad and can be caught by catch and try.

cacheResultWithShow :: (Eq (r a), Hashable (r a), Typeable (r a)) => ShowReq r a -> r a -> IO a -> GenHaxl u a Source #

Transparently provides caching in the same way as cacheResult, but uses the given functions to show requests and their results.

cachedComputation :: forall req u a. (Eq (req a), Hashable (req a), Typeable (req a)) => req a -> GenHaxl u a -> GenHaxl u a Source #

cachedComputation memoizes a Haxl computation. The key is a request.

Note: These cached computations will not be included in the output of dumpCacheAsHaskell.

preCacheComputation :: forall req u a. (Eq (req a), Hashable (req a), Typeable (req a)) => req a -> GenHaxl u a -> GenHaxl u a Source #

Like cachedComputation, but fails if the cache is already populated.

Memoization can be (ab)used to "mock" a cached computation, by pre-populating the cache with an alternative implementation. In that case we don't want the operation to populate the cache to silently succeed if the cache is already populated.

dumpCacheAsHaskell :: GenHaxl u String Source #

Dump the contents of the cache as Haskell code that, when compiled and run, will recreate the same cache contents. For example, the generated code looks something like this:

loadCache :: GenHaxl u ()
loadCache = do
  cacheRequest (ListWombats 3) (Right ([1,2,3]))
  cacheRequest (CountAardvarks "abcabc") (Right (2))

Memoization

newMemo :: GenHaxl u (MemoVar u a) Source #

Create a new MemoVar for storing a memoized computation. The created MemoVar is initially empty, not tied to any specific computation. Running this memo (with runMemo) without preparing it first (with prepareMemo) will result in an exception.

newMemoWith :: GenHaxl u a -> GenHaxl u (MemoVar u a) Source #

Convenience function, combines newMemo and prepareMemo.

prepareMemo :: MemoVar u a -> GenHaxl u a -> GenHaxl u () Source #

Store a computation within a supplied MemoVar. Any memo stored within the MemoVar already (regardless of completion) will be discarded, in favor of the supplied computation. A MemoVar must be prepared before it is run.

runMemo :: MemoVar u a -> GenHaxl u a Source #

Continue the memoized computation within a given MemoVar. Notes:

  1. If the memo contains a complete result, return that result.
  2. If the memo contains an in-progress computation, continue it as far as possible for this round.
  3. If the memo is empty (it was not prepared), throw an error.

For example, to memoize the computation one given by:

one :: Haxl Int
one = return 1

use:

do
  oneMemo <- newMemoWith one
  let memoizedOne = runMemo aMemo one
  oneResult <- memoizedOne

To memoize mutually dependent computations such as in:

h :: Haxl Int
h = do
  a <- f
  b <- g
  return (a + b)
 where
  f = return 42
  g = succ <$> f

without needing to reorder them, use:

h :: Haxl Int
h = do
  fMemoRef <- newMemo
  gMemoRef <- newMemo

  let f = runMemo fMemoRef
      g = runMemo gMemoRef

  prepareMemo fMemoRef $ return 42
  prepareMemo gMemoRef $ succ <$> f

  a <- f
  b <- g
  return (a + b)

memo :: (Typeable a, Typeable k, Hashable k, Eq k) => k -> GenHaxl u a -> GenHaxl u a Source #

Memoize a computation using an arbitrary key. The result will be calculated once; the second and subsequent time it will be returned immediately. It is the caller's responsibility to ensure that for every two calls memo key haxl, if they have the same key then they compute the same result.

memoUnique :: (Typeable a, Typeable k, Hashable k, Eq k) => MemoFingerprintKey a -> Text -> k -> GenHaxl u a -> GenHaxl u a Source #

Memoize a computation using its location and a Fingerprint. This ensures uniqueness across computations.

memoize :: GenHaxl u a -> GenHaxl u (GenHaxl u a) Source #

Transform a Haxl computation into a memoized version of itself.

Given a Haxl computation, memoize creates a version which stores its result in a MemoVar (which memoize creates), and returns the stored result on subsequent invocations. This permits the creation of local memos, whose lifetimes are scoped to the current function, rather than the entire request.

memoize1 :: (Eq a, Hashable a) => (a -> GenHaxl u b) -> GenHaxl u (a -> GenHaxl u b) Source #

Transform a 1-argument function returning a Haxl computation into a memoized version of itself.

Given a function f of type a -> GenHaxl u b, memoize1 creates a version which memoizes the results of f in a table keyed by its argument, and returns stored results on subsequent invocations with the same argument.

e.g.:

allFriends :: [Int] -> GenHaxl u [Int]
allFriends ids = do
  memoizedFriendsOf <- memoize1 friendsOf
  concat <$> mapM memoizeFriendsOf ids

The above implementation will not invoke the underlying friendsOf repeatedly for duplicate values in ids.

memoize2 :: (Eq a, Hashable a, Eq b, Hashable b) => (a -> b -> GenHaxl u c) -> GenHaxl u (a -> b -> GenHaxl u c) Source #

Transform a 2-argument function returning a Haxl computation, into a memoized version of itself.

The 2-ary version of memoize1, see its documentation for details.

data MemoFingerprintKey a where Source #

A memo key derived from a 128-bit MD5 hash. Do not use this directly, it is for use by automatically-generated memoization.

Conditionals

pAnd :: GenHaxl u Bool -> GenHaxl u Bool -> GenHaxl u Bool infixr 5 Source #

Parallel version of '(.&&)'. Both arguments are evaluated in parallel, and if either returns False then the other is not evaluated any further.

WARNING: exceptions may be unpredictable when using pAnd. If one argument returns False before the other completes, then pAnd returns False immediately, ignoring a possible exception that the other argument may have produced if it had been allowed to complete.

pOr :: GenHaxl u Bool -> GenHaxl u Bool -> GenHaxl u Bool infixr 4 Source #

Parallel version of '(.||)'. Both arguments are evaluated in parallel, and if either returns True then the other is not evaluated any further.

WARNING: exceptions may be unpredictable when using pOr. If one argument returns True before the other completes, then pOr returns True immediately, ignoring a possible exception that the other argument may have produced if it had been allowed to complete.

Statistics

newtype Stats Source #

Stats that we collect along the way.

Constructors

Stats [FetchStats] 
Instances
Show Stats Source # 
Instance details

Defined in Haxl.Core.Stats

Methods

showsPrec :: Int -> Stats -> ShowS #

show :: Stats -> String #

showList :: [Stats] -> ShowS #

Semigroup Stats Source # 
Instance details

Defined in Haxl.Core.Stats

Methods

(<>) :: Stats -> Stats -> Stats #

sconcat :: NonEmpty Stats -> Stats #

stimes :: Integral b => b -> Stats -> Stats #

Monoid Stats Source # 
Instance details

Defined in Haxl.Core.Stats

Methods

mempty :: Stats #

mappend :: Stats -> Stats -> Stats #

mconcat :: [Stats] -> Stats #

ToJSON Stats Source # 
Instance details

Defined in Haxl.Core.Stats

data FetchStats Source #

Maps data source name to the number of requests made in that round. The map only contains entries for sources that made requests in that round.

Constructors

FetchStats

Timing stats for a (batched) data fetch

FetchCall

The stack trace of a call to dataFetch. These are collected only when profiling and reportLevel is 5 or greater.

Fields

ppStats :: Stats -> String Source #

Pretty-print Stats.

ppFetchStats :: FetchStats -> String Source #

Pretty-print RoundStats.

profile :: Profile -> HashMap ProfileLabel ProfileData Source #

Data on individual labels.

data ProfileData Source #

Constructors

ProfileData 

Fields

Instances
Show ProfileData Source # 
Instance details

Defined in Haxl.Core.Stats

Tracing flags

data Flags Source #

Flags that control the operation of the engine.

Constructors

Flags 

Fields

  • trace :: !Int

    Tracing level (0 = quiet, 3 = very verbose).

  • report :: !Int

    Report level: * 0 = quiet * 1 = quiet (legacy, this used to do something) * 2 = data fetch stats & errors * 3 = (same as 2, this used to enable errors) * 4 = profiling * 5 = log stack traces of dataFetch calls

  • caching :: !Int

    Non-zero if caching is enabled. If caching is disabled, then we still do batching and de-duplication, but do not cache results.

  • recording :: !Int

    Non-zero if recording is enabled. This allows tests to record cache calls for datasources by making uncachedRequest behave like dataFetch

ifTrace :: Monad m => Flags -> Int -> m a -> m () Source #

Runs an action if the tracing level is above the given threshold.

ifReport :: Monad m => Flags -> Int -> m a -> m () Source #

Runs an action if the report level is above the given threshold.

ifProfiling :: Monad m => Flags -> m a -> m () Source #

Building data sources

class (DataSourceName req, StateKey req, ShowP req) => DataSource u req where Source #

The class of data sources, parameterised over the request type for that data source. Every data source must implement this class.

A data source keeps track of its state by creating an instance of StateKey to map the request type to its state. In this case, the type of the state should probably be a reference type of some kind, such as IORef.

For a complete example data source, see Examples.

Minimal complete definition

fetch

Methods

fetch Source #

Arguments

:: State req

Current state.

-> Flags

Tracing flags.

-> u

User environment.

-> PerformFetch req

Fetch the data; see PerformFetch.

Issues a list of fetches to this DataSource. The BlockedFetch objects contain both the request and the ResultVars into which to put the results.

schedulerHint :: u -> SchedulerHint req Source #

class ShowP f where Source #

A class of type constructors for which we can show all parameterizations.

Minimal complete definition

showp

Methods

showp :: f a -> String Source #

class DataSourceName (req :: * -> *) where Source #

Minimal complete definition

dataSourceName

Methods

dataSourceName :: Proxy req -> Text Source #

The name of this DataSource, used in tracing and stats. Must take a dummy request.

Instances
Typeable tag => DataSourceName (ConcurrentIOReq tag) Source # 
Instance details

Defined in Haxl.DataSource.ConcurrentIO

type Request req a = (Eq (req a), Hashable (req a), Typeable (req a), Show (req a), Show a) Source #

A convenience only: package up Eq, Hashable, Typeable, and Show for requests into a single constraint.

data BlockedFetch r Source #

A BlockedFetch is a pair of

  • The request to fetch (with result type a)
  • A ResultVar to store either the result or an error

We often want to collect together multiple requests, but they return different types, and the type system wouldn't let us put them together in a list because all the elements of the list must have the same type. So we wrap up these types inside the BlockedFetch type, so that they all look the same and we can put them in a list.

When we unpack the BlockedFetch and get the request and the ResultVar out, the type system knows that the result type of the request matches the type parameter of the ResultVar, so it will let us take the result of the request and store it in the ResultVar.

Constructors

BlockedFetch (r a) (ResultVar a) 

data PerformFetch req Source #

A data source can fetch data in one of four ways.

Constructors

SyncFetch ([BlockedFetch req] -> IO ())

Fully synchronous, returns only when all the data is fetched. See syncFetch for an example.

AsyncFetch ([BlockedFetch req] -> IO () -> IO ())

Asynchronous; performs an arbitrary IO action while the data is being fetched, but only returns when all the data is fetched. See asyncFetch for an example.

BackgroundFetch ([BlockedFetch req] -> IO ())

Fetches the data in the background, calling putResult at any time in the future. This is the best kind of fetch, because it provides the most concurrency.

FutureFetch ([BlockedFetch req] -> IO (IO ()))

Returns an IO action that, when performed, waits for the data to be received. This is the second-best type of fetch, because the scheduler still has to perform the blocking wait at some point in the future, and when it has multiple blocking waits to perform, it can't know which one will return first.

Why not just forkIO the IO action to make a FutureFetch into a BackgroundFetch? The blocking wait will probably do a safe FFI call, which means it needs its own OS thread. If we don't want to create an arbitrary number of OS threads, then FutureFetch enables all the blocking waits to be done on a single thread. Also, you might have a data source that requires all calls to be made in the same OS thread.

class Typeable f => StateKey (f :: * -> *) where Source #

StateKey maps one type to another type. A type that is an instance of StateKey can store and retrieve information from a StateStore.

Associated Types

data State f Source #

Methods

getStateType :: Proxy f -> TypeRep Source #

We default this to typeOf1, but if f is itself a complex type that is already applied to some paramaters, we want to be able to use the same state by using typeOf2, etc

Instances
Typeable tag => StateKey (ConcurrentIOReq tag) Source # 
Instance details

Defined in Haxl.DataSource.ConcurrentIO

Associated Types

data State (ConcurrentIOReq tag) :: * Source #

data SchedulerHint (req :: * -> *) Source #

Hints to the scheduler about this data source

Constructors

TryToBatch

Hold data-source requests while we execute as much as we can, so that we can hopefully collect more requests to batch.

SubmitImmediately

Submit a request via fetch as soon as we have one, don't try to batch multiple requests. This is really only useful if the data source returns BackgroundFetch, otherwise requests to this data source will be performed synchronously, one at a time.

Result variables

newtype ResultVar a Source #

A sink for the result of a data fetch in BlockedFetch

Constructors

ResultVar (Either SomeException a -> Bool -> IO ()) 

putFailure :: Exception e => ResultVar a -> e -> IO () Source #

putSuccess :: ResultVar a -> a -> IO () Source #

putResultFromChildThread :: ResultVar a -> Either SomeException a -> IO () Source #

Like putResult, but used to get correct accounting when work is being done in child threads. This is particularly important for data sources that are using BackgroundFetch, The allocation performed in the child thread up to this point will be propagated back to the thread that called runHaxl.

Note: if you're doing multiple putResult calls in the same thread ensure that only the last one is putResultFromChildThread. If you make multiple putResultFromChildThread calls, the allocation will be counted multiple times.

If you are reusing a thread for multiple fetches, you should call System.Mem.setAllocationCounter 0 after putResultFromChildThread, so that allocation is not counted multiple times.

Default fetch implementations

asyncFetch Source #

Arguments

:: ((service -> IO ()) -> IO ())

Wrapper to perform an action in the context of a service.

-> (service -> IO ())

Dispatch all the pending requests and wait for the results

-> (forall a. service -> request a -> IO (IO (Either SomeException a)))

Submits an individual request to the service.

-> State request

Currently unused.

-> Flags

Currently unused.

-> u

Currently unused.

-> PerformFetch request 

asyncFetchWithDispatch Source #

Arguments

:: ((service -> IO ()) -> IO ())

Wrapper to perform an action in the context of a service.

-> (service -> IO ())

Dispatch all the pending requests

-> (service -> IO ())

Wait for the results

-> (forall a. service -> request a -> IO (IO (Either SomeException a)))

Enqueue an individual request to the service.

-> State request

Currently unused.

-> Flags

Currently unused.

-> u

Currently unused.

-> PerformFetch request 

Common implementation templates for fetch of DataSource.

Example usage:

fetch = syncFetch MyDS.withService MyDS.retrieve
  $ \service request -> case request of
    This x -> MyDS.fetchThis service x
    That y -> MyDS.fetchThat service y

asyncFetchAcquireRelease Source #

Arguments

:: IO service

Resource acquisition for this datasource

-> (service -> IO ())

Resource release

-> (service -> IO ())

Dispatch all the pending requests and wait for the results

-> (service -> IO ())

Wait for the results

-> (forall a. service -> request a -> IO (IO (Either SomeException a)))

Submits an individual request to the service.

-> State request

Currently unused.

-> Flags

Currently unused.

-> u

Currently unused.

-> PerformFetch request 

A version of asyncFetch (actually asyncFetchWithDispatch) that handles exceptions correctly. You should use this instead of asyncFetch or asyncFetchWithDispatch. The danger with asyncFetch is that if an exception is thrown by withService, the inner action won't be executed, and we'll drop some data-fetches in the same round.

asyncFetchAcquireRelease behaves like the following:

asyncFetchAcquireRelease acquire release dispatch wait enqueue =
  AsyncFetch $ \requests inner ->
    bracket acquire release $ \service -> do
      getResults <- mapM (submitFetch service enqueue) requests
      dispatch service
      inner
      wait service
      sequence_ getResults

except that inner is run even if acquire, enqueue, or dispatch throws, unless an async exception is received.

stubFetch :: Exception e => (forall a. r a -> e) -> State r -> Flags -> u -> PerformFetch r Source #

syncFetch Source #

Arguments

:: ((service -> IO ()) -> IO ())

Wrapper to perform an action in the context of a service.

-> (service -> IO ())

Dispatch all the pending requests and wait for the results

-> (forall a. service -> request a -> IO (IO (Either SomeException a)))

Submits an individual request to the service.

-> State request

Currently unused.

-> Flags

Currently unused.

-> u

Currently unused.

-> PerformFetch request 

Utilities

setError :: Exception e => (forall a. r a -> e) -> BlockedFetch r -> IO () Source #

Function for easily setting a fetch to a particular exception

Exceptions