{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TupleSections #-}
module Haxl.Core.Monad
(
GenHaxl(..)
, Result(..)
, WriteTree(..)
, tellWrite
, write
, flattenWT
, appendWTs
, mbModifyWLRef
, Cont(..)
, toHaxl
, IVar(..)
, IVarContents(..)
, newIVar
, newFullIVar
, getIVar
, getIVarWithWrites
, putIVar
, ResultVal(..)
, done
, eitherToResult
, eitherToResultThrowIO
, CompleteReq(..)
, Env(..)
, Caches
, caches
, initEnvWithData
, initEnv
, emptyEnv
, env, withEnv
, speculate
, imperative
, JobList(..)
, appendJobList
, lengthJobList
, addJob
, throw
, raise
, catch
, catchIf
, try
, tryToHaxlException
, dumpCacheAsHaskell
, dumpCacheAsHaskellFn
#ifdef PROFILING
, withCallGraph
#endif
, unsafeLiftIO, unsafeToHaxlException
) where
import Haxl.Core.Flags
import Haxl.Core.Stats
import Haxl.Core.StateStore
import Haxl.Core.Exception
import Haxl.Core.RequestStore as RequestStore
import Haxl.Core.DataCache as DataCache
import Control.Arrow (left)
import Control.Concurrent.STM
import qualified Data.Text as Text
import qualified Control.Monad.Catch as Catch
import Control.Exception (Exception(..), SomeException, throwIO)
import Control.Monad
import qualified Control.Exception as Exception
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative hiding (Const)
#endif
#if __GLASGOW_HASKELL__ < 706
import Prelude hiding (catch)
#endif
import Data.IORef
import Data.Int
import GHC.Exts (IsString(..))
import Text.PrettyPrint hiding ((<>))
import Text.Printf
#ifdef EVENTLOG
import Control.Exception (bracket_)
import Debug.Trace (traceEventIO)
#endif
#ifdef PROFILING
import qualified Data.Map as Map
import Data.Text (Text)
import Data.Typeable
import GHC.Stack
import Haxl.Core.CallGraph
#endif
trace_ :: String -> a -> a
trace_ _ = id
data Env u w = Env
{ cacheRef :: {-# UNPACK #-} !(IORef (DataCache (IVar u w)))
, memoRef :: {-# UNPACK #-} !(IORef (DataCache (IVar u w)))
, flags :: !Flags
, userEnv :: u
, statsRef :: {-# UNPACK #-} !(IORef Stats)
, profLabel :: ProfileLabel
, profRef :: {-# UNPACK #-} !(IORef Profile)
, states :: StateStore
, reqStoreRef :: {-# UNPACK #-} !(IORef (RequestStore u))
, runQueueRef :: {-# UNPACK #-} !(IORef (JobList u w))
, submittedReqsRef :: {-# UNPACK #-} !(IORef ReqCountMap)
, completions :: {-# UNPACK #-} !(TVar [CompleteReq u w])
, pendingWaits :: [IO ()]
, speculative :: {-# UNPACK #-} !Int
, writeLogsRef :: {-# UNPACK #-} !(IORef (WriteTree w))
#ifdef PROFILING
, callGraphRef :: Maybe (IORef CallGraph)
, currFunction :: QualFunction
#endif
}
type Caches u w = (IORef (DataCache (IVar u w)), IORef (DataCache (IVar u w)))
caches :: Env u w -> Caches u w
caches env = (cacheRef env, memoRef env)
-- | Initialize an environment with a 'StateStore', an input map, a
-- preexisting 'DataCache', and a seed for the random number generator.
initEnvWithData :: StateStore -> u -> Caches u w -> IO (Env u w)
initEnvWithData states e (cref, mref) = do
sref <- newIORef emptyStats
pref <- newIORef emptyProfile
rs <- newIORef noRequests -- RequestStore
rq <- newIORef JobNil
sr <- newIORef emptyReqCounts
comps <- newTVarIO [] -- completion queue
wl <- newIORef NilWrites
return Env
{ cacheRef = cref
, memoRef = mref
, flags = defaultFlags
, userEnv = e
, states = states
, statsRef = sref
, profLabel = "MAIN"
, profRef = pref
, reqStoreRef = rs
, runQueueRef = rq
, submittedReqsRef = sr
, completions = comps
, pendingWaits = []
, speculative = 0
, writeLogsRef = wl
#ifdef PROFILING
, callGraphRef = Nothing
, currFunction = mainFunction
#endif
}
-- | Initializes an environment with 'StateStore' and an input map.
initEnv :: StateStore -> u -> IO (Env u w)
initEnv states e = do
cref <- newIORef emptyDataCache
mref <- newIORef emptyDataCache
initEnvWithData states e (cref,mref)
-- | A new, empty environment.
emptyEnv :: u -> IO (Env u w)
emptyEnv = initEnv stateEmpty
speculate :: Env u w -> Env u w
speculate env@Env{..}
| speculative == 0 = env { speculative = 1 }
| otherwise = env
imperative :: Env u w -> Env u w
imperative env@Env{..}
| speculative == 1 = env { speculative = 0 }
| otherwise = env
-- -----------------------------------------------------------------------------
-- WriteTree
-- | A tree of writes done during a Haxl computation. We could use a simple
-- list, but this allows us to avoid multiple mappends when concatenating
-- writes from two haxl computations.
--
-- Users should try to treat this data type as opaque, and prefer
-- to use @flattenWT@ to get a simple list of writes from a @WriteTree@.
data WriteTree w
= NilWrites
| SomeWrite w
| MergeWrites (WriteTree w) (WriteTree w)
deriving (Show)
appendWTs :: WriteTree w -> WriteTree w -> WriteTree w
appendWTs NilWrites w = w
appendWTs w NilWrites = w
appendWTs w1 w2 = MergeWrites w1 w2
-- This function must be called at the end of the Haxl computation to get
-- a list of writes.
flattenWT :: WriteTree w -> [w]
flattenWT = go []
where
go !ws NilWrites = ws
go !ws (SomeWrite w) = w : ws
go !ws (MergeWrites w1 w2) = go (go ws w2) w1
-- This is a convenience wrapper over modifyIORef, which only modifies
-- writeLogsRef IORef, for non NilWrites.
mbModifyWLRef :: WriteTree w -> IORef (WriteTree w) -> IO ()
mbModifyWLRef NilWrites _ = return ()
mbModifyWLRef !wt ref = modifyIORef' ref (`appendWTs` wt)
-- -----------------------------------------------------------------------------
-- | 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 writer monad for 'WriteTree'. We strongly advise these be
-- used only for logs used for debugging. These are not memoized.
-- Other relevant writes should be returned as function output,
-- which is the more "functional" way.
-- * 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.
--
newtype GenHaxl u w a = GenHaxl
{ unHaxl :: Env u w -> IO (Result u w a) }
tellWrite :: w -> GenHaxl u w ()
tellWrite = write . SomeWrite
write :: WriteTree w -> GenHaxl u w ()
write wt = GenHaxl $ \Env{..} -> do
mbModifyWLRef wt writeLogsRef
return $ Done ()
instance IsString a => IsString (GenHaxl u w a) where
fromString s = return (fromString s)
-- -----------------------------------------------------------------------------
-- JobList
-- | A list of computations together with the IVar into which they
-- should put their result.
--
-- This could be an ordinary list, but the optimised representation
-- saves space and time.
--
data JobList u w
= JobNil
| forall a . JobCons
(Env u w) -- See Note [make withEnv work] below.
(GenHaxl u w a)
{-# UNPACK #-} !(IVar u w a)
(JobList u w)
-- Note [make withEnv work]
--
-- The withEnv operation supplies a new Env for the scope of a GenHaxl
-- computation. The problem is that the computation might be split
-- into pieces and put onto various JobLists, so we have to be sure to
-- use the correct Env when we execute the pieces. Furthermore, if one
-- of these pieces blocks and gets run again later, we must ensure to
-- restart it with the correct Env. So we stash the Env along with
-- the continuation in the JobList.
appendJobList :: JobList u w -> JobList u w -> JobList u w
appendJobList JobNil c = c
appendJobList c JobNil = c
appendJobList (JobCons a b c d) e = JobCons a b c $! appendJobList d e
lengthJobList :: JobList u w -> Int
lengthJobList JobNil = 0
lengthJobList (JobCons _ _ _ j) = 1 + lengthJobList j
-- -----------------------------------------------------------------------------
-- IVar
-- | A synchronisation point. It either contains a value, or a list
-- of computations waiting for the value.
newtype IVar u w a = IVar (IORef (IVarContents u w a))
data IVarContents u w a
= IVarFull (ResultVal a w)
| IVarEmpty (JobList u w)
-- morally this is a list of @a -> GenHaxl u w ()@, but instead of
-- using a function, each computation begins with `getIVar` to grab
-- the value it is waiting for. This is less type safe but a little
-- faster (benchmarked with tests/MonadBench.hs).
newIVar :: IO (IVar u w a)
newIVar = IVar <$> newIORef (IVarEmpty JobNil)
newFullIVar :: ResultVal a w -> IO (IVar u w a)
newFullIVar r = IVar <$> newIORef (IVarFull r)
getIVar :: IVar u w a -> GenHaxl u w a
getIVar (IVar !ref) = GenHaxl $ \Env{..} -> do
e <- readIORef ref
case e of
IVarFull (Ok a _wt) -> return (Done a)
IVarFull (ThrowHaxl e _wt) -> return (Throw e)
IVarFull (ThrowIO e) -> throwIO e
IVarEmpty _ -> return (Blocked (IVar ref) (Cont (getIVar (IVar ref))))
-- Just a specialised version of getIVar, for efficiency in <*>
getIVarApply :: IVar u w (a -> b) -> a -> GenHaxl u w b
getIVarApply (IVar !ref) a = GenHaxl $ \Env{..} -> do
e <- readIORef ref
case e of
IVarFull (Ok f _wt) -> return (Done (f a))
IVarFull (ThrowHaxl e _wt) -> return (Throw e)
IVarFull (ThrowIO e) -> throwIO e
IVarEmpty _ ->
return (Blocked (IVar ref) (Cont (getIVarApply (IVar ref) a)))
-- Another specialised version of getIVar, for efficiency in cachedComputation
getIVarWithWrites :: IVar u w a -> GenHaxl u w a
getIVarWithWrites (IVar !ref) = GenHaxl $ \Env{..} -> do
e <- readIORef ref
case e of
IVarFull (Ok a wt) -> do
mbModifyWLRef wt writeLogsRef
return (Done a)
IVarFull (ThrowHaxl e wt) -> do
mbModifyWLRef wt writeLogsRef
return (Throw e)
IVarFull (ThrowIO e) -> throwIO e
IVarEmpty _ ->
return (Blocked (IVar ref) (Cont (getIVarWithWrites (IVar ref))))
putIVar :: IVar u w a -> ResultVal a w -> Env u w -> IO ()
putIVar (IVar ref) a Env{..} = do
e <- readIORef ref
case e of
IVarEmpty jobs -> do
writeIORef ref (IVarFull a)
modifyIORef' runQueueRef (appendJobList jobs)
IVarFull{} -> error "putIVar: multiple put"
{-# INLINE addJob #-}
addJob :: Env u w -> GenHaxl u w b -> IVar u w b -> IVar u w a -> IO ()
addJob env !haxl !resultIVar (IVar !ref) =
modifyIORef' ref $ \contents ->
case contents of
IVarEmpty list -> IVarEmpty (JobCons env haxl resultIVar list)
_ -> addJobPanic
addJobPanic :: forall a . a
addJobPanic = error "addJob: not empty"
-- -----------------------------------------------------------------------------
-- ResultVal
-- | The contents of a full IVar. We have to distinguish exceptions
-- thrown in the IO monad from exceptions thrown in the Haxl monad, so
-- that when the result is fetched using getIVar, we can throw the
-- exception in the right way.
data ResultVal a w
= Ok a (WriteTree w)
| ThrowHaxl SomeException (WriteTree w)
| ThrowIO SomeException
-- we get no write logs when an IO exception occurs
done :: ResultVal a w -> IO (Result u w a)
done (Ok a _) = return (Done a)
done (ThrowHaxl e _) = return (Throw e)
done (ThrowIO e) = throwIO e
eitherToResultThrowIO :: Either SomeException a -> ResultVal a w
eitherToResultThrowIO (Right a) = Ok a NilWrites
eitherToResultThrowIO (Left e)
| Just HaxlException{} <- fromException e = ThrowHaxl e NilWrites
| otherwise = ThrowIO e
eitherToResult :: Either SomeException a -> ResultVal a w
eitherToResult (Right a) = Ok a NilWrites
eitherToResult (Left e) = ThrowHaxl e NilWrites
-- -----------------------------------------------------------------------------
-- CompleteReq
-- | A completed request from a data source, containing the result,
-- and the 'IVar' representing the blocked computations. The job of a
-- data source is just to add these to a queue ('completions') using
-- 'putResult'; the scheduler collects them from the queue and unblocks
-- the relevant computations.
data CompleteReq u w
= forall a . CompleteReq
(Either SomeException a)
!(IVar u w a) -- IVar because the result is cached
{-# UNPACK #-} !Int64 -- see Note [tracking allocation in child threads]
{- Note [tracking allocation in child threads]
For a BackgroundFetch, we might be doing some of the work in a
separate thread, but we want to make sure that the parent thread gets
charged for the allocation, so that allocation limits still work.
The design is a bit tricky here. We want to track the allocation
accurately but without adding much overhead.
The best way to propagate the allocation back from the child thread is
through putResult. If we had some other method, we would also need a
way to synchronise it with the main runHaxl loop; the advantage of
putResult is that this is already a synchronisation method, because
runHaxl is waiting for the result of the dataFetch.
(slight wrinkle here: runHaxl might not wait for the result of the
dataFetch in the case where we do some speculative execution in
pAnd/pOr)
We need a special version of putResult for child threads
(putResultFromChildThread), because we don't want to propagate any
allocation from the runHaxl thread back to itself and count it twice.
We also want to capture the allocation as late as possible, so that we
count everything. For that reason, we pass a Bool down from putResult
into the function in the ResultVar, and it reads the allocation
counter as the last thing before adding the result to the completions
TVar.
The other problem to consider is how to capture the allocation when
the child thread is doing multiple putResults. Our solution here is
to ensure that the *last* one is a putResultFromChildThread, so it
captures all the allocation from everything leading up to it.
Why not reset the counter each time, so we could do multiple
putResultFromChildThreads? Because the child thread might be using an
allocation limit itself, and changing the counter would mess it up.
-}
-- -----------------------------------------------------------------------------
-- Result
-- | The result of a computation is either 'Done' with a value, 'Throw'
-- with an exception, or 'Blocked' on the result of a data fetch with
-- a continuation.
data Result u w a
= Done a
| Throw SomeException
| forall b . Blocked
{-# UNPACK #-} !(IVar u w b)
(Cont u w a)
-- ^ The 'IVar' is what we are blocked on; 'Cont' is the
-- continuation. This might be wrapped further if we're
-- nested inside multiple '>>=', before finally being added
-- to the 'IVar'. Morally @b -> GenHaxl u w a@, but see
-- 'IVar',
instance (Show a) => Show (Result u w a) where
show (Done a) = printf "Done(%s)" $ show a
show (Throw e) = printf "Throw(%s)" $ show e
show Blocked{} = "Blocked"
{- Note [Exception]
How do we want to represent Haxl exceptions (those that are thrown by
"throw" in the Haxl monad)?
1) Explicitly via a Throw constructor in the Result type
2) Using throwIO in the IO monad
If we did (2), we would have to use an exception handler in <*>,
because an exception in the right-hand argument of <*> should not
necessarily be thrown by the whole computation - an exception on the
left should get priority, and the left might currently be Blocked.
We must be careful about turning IO monad exceptions into Haxl
exceptions. An IO monad exception will normally propagate right
out of runHaxl and terminate the whole computation, whereas a Haxl
exception can get dropped on the floor, if it is on the right of
<*> and the left side also throws, for example. So turning an IO
monad exception into a Haxl exception is a dangerous thing to do.
In particular, we never want to do it for an asynchronous exception
(AllocationLimitExceeded, ThreadKilled, etc.), because these are
supposed to unconditionally terminate the computation.
There are three places where we take an arbitrary IO monad exception and
turn it into a Haxl exception:
* wrapFetchInCatch. Here we want to propagate a failure of the
data source to the callers of the data source, but if the
failure came from elsewhere (an asynchronous exception), then we
should just propagate it
* cacheResult (cache the results of IO operations): again,
failures of the IO operation should be visible to the caller as
a Haxl exception, but we exclude asynchronous exceptions from
this.
* unsafeToHaxlException: assume the caller knows what they're
doing, and just wrap all exceptions.
-}
-- -----------------------------------------------------------------------------
-- Cont
-- | A data representation of a Haxl continuation. This is to avoid
-- repeatedly traversing a left-biased tree in a continuation, leading
-- O(n^2) complexity for some pathalogical cases - see the "seql" benchmark
-- in tests/MonadBench.hs.
-- See "A Smart View on Datatypes", Jaskelioff/Rivas, ICFP'15
data Cont u w a
= Cont (GenHaxl u w a)
| forall b. Cont u w b :>>= (b -> GenHaxl u w a)
| forall b. (b -> a) :<$> (Cont u w b)
toHaxl :: Cont u w a -> GenHaxl u w a
toHaxl (Cont haxl) = haxl
toHaxl (m :>>= k) = toHaxlBind m k
toHaxl (f :<$> x) = toHaxlFmap f x
toHaxlBind :: Cont u w b -> (b -> GenHaxl u w a) -> GenHaxl u w a
toHaxlBind (m :>>= k) k2 = toHaxlBind m (k >=> k2)
toHaxlBind (Cont haxl) k = haxl >>= k
toHaxlBind (f :<$> x) k = toHaxlBind x (k . f)
toHaxlFmap :: (a -> b) -> Cont u w a -> GenHaxl u w b
toHaxlFmap f (m :>>= k) = toHaxlBind m (k >=> return . f)
toHaxlFmap f (Cont haxl) = f <$> haxl
toHaxlFmap f (g :<$> x) = toHaxlFmap (f . g) x
-- -----------------------------------------------------------------------------
-- Monad/Applicative instances
instance Monad (GenHaxl u w) where
return a = GenHaxl $ \_env -> return (Done a)
GenHaxl m >>= k = GenHaxl $ \env -> do
e <- m env
case e of
Done a -> unHaxl (k a) env
Throw e -> return (Throw e)
Blocked ivar cont -> trace_ ">>= Blocked" $
return (Blocked ivar (cont :>>= k))
fail msg = GenHaxl $ \_env ->
return $ Throw $ toException $ MonadFail $ Text.pack msg
-- We really want the Applicative version of >>
(>>) = (*>)
instance Functor (GenHaxl u w) where
fmap f (GenHaxl m) = GenHaxl $ \env -> do
r <- m env
case r of
Done a -> return (Done (f a))
Throw e -> return (Throw e)
Blocked ivar cont -> trace_ "fmap Blocked" $
return (Blocked ivar (f :<$> cont))
instance Applicative (GenHaxl u w) where
pure = return
GenHaxl ff <*> GenHaxl aa = GenHaxl $ \env -> do
rf <- ff env
case rf of
Done f -> do
ra <- aa env
case ra of
Done a -> trace_ "Done/Done" $ return (Done (f a))
Throw e -> trace_ "Done/Throw" $ return (Throw e)
Blocked ivar fcont -> trace_ "Done/Blocked" $
return (Blocked ivar (f :<$> fcont))
Throw e -> trace_ "Throw" $ return (Throw e)
Blocked ivar1 fcont -> do
ra <- aa env
case ra of
Done a -> trace_ "Blocked/Done" $
return (Blocked ivar1 (($ a) :<$> fcont))
Throw e -> trace_ "Blocked/Throw" $
return (Blocked ivar1 (fcont :>>= (\_ -> throw e)))
Blocked ivar2 acont -> trace_ "Blocked/Blocked" $ do
-- Note [Blocked/Blocked]
if speculative env /= 0
then
return (Blocked ivar1
(Cont (toHaxl fcont <*> toHaxl acont)))
else do
i <- newIVar
addJob env (toHaxl fcont) i ivar1
let cont = acont :>>= \a -> getIVarApply i a
return (Blocked ivar2 cont)
-- Note [Blocked/Blocked]
--
-- This is the tricky case: we're blocked on both sides of the <*>.
-- We need to divide the computation into two pieces that may continue
-- independently when the resources they are blocked on become
-- available. Moreover, the computation as a whole depends on the two
-- pieces. It works like this:
--
-- ff <*> aa
--
-- becomes
--
-- (ff >>= putIVar i) <*> (a <- aa; f <- getIVar i; return (f a)
--
-- where the IVar i is a new synchronisation point. If the right side
-- gets to the `getIVar` first, it will block until the left side has
-- called 'putIVar'.
--
-- We can also do it the other way around:
--
-- (do ff <- f; getIVar i; return (ff a)) <*> (a >>= putIVar i)
--
-- The first was slightly faster according to tests/MonadBench.hs.
-- -----------------------------------------------------------------------------
-- Env utils
-- | Extracts data from the 'Env'.
env :: (Env u w -> a) -> GenHaxl u w a
env f = GenHaxl $ \env -> return (Done (f env))
-- | Returns a version of the Haxl computation which always uses the
-- provided 'Env', ignoring the one specified by 'runHaxl'.
withEnv :: Env u w -> GenHaxl u w a -> GenHaxl u w a
withEnv newEnv (GenHaxl m) = GenHaxl $ \_env -> do
r <- m newEnv
case r of
Done a -> return (Done a)
Throw e -> return (Throw e)
Blocked ivar k ->
return (Blocked ivar (Cont (withEnv newEnv (toHaxl k))))
#ifdef PROFILING
withCallGraph
:: Typeable a
=> (a -> Maybe Text)
-> QualFunction
-> GenHaxl u w a
-> GenHaxl u w a
withCallGraph toText f a = do
coreEnv <- env id
value <- withEnv coreEnv{currFunction = f} a
case callGraphRef coreEnv of
Just graph -> unsafeLiftIO $ modifyIORef' graph
(updateCallGraph (f, currFunction coreEnv) (toText value))
_ -> throw $ CriticalError
"withCallGraph called without an IORef CallGraph"
return value
where
updateCallGraph :: FunctionCall -> Maybe Text -> CallGraph -> CallGraph
updateCallGraph fnCall@(childQFunc, _) (Just value) (edgeList, valueMap) =
(fnCall : edgeList, Map.insert childQFunc value valueMap)
updateCallGraph fnCall Nothing (edgeList, valueMap) =
(fnCall : edgeList, valueMap)
#endif
throw :: (Exception e) => e -> GenHaxl u w a
throw e = GenHaxl $ \_env -> raise e
raise :: (Exception e) => e -> IO (Result u w a)
raise e
#ifdef PROFILING
| Just (HaxlException Nothing h) <- fromException somex = do
stk <- currentCallStack
return (Throw (toException (HaxlException (Just stk) h)))
| otherwise
#endif
= return (Throw somex)
where
somex = toException e
catch :: Exception e => GenHaxl u w a -> (e -> GenHaxl u w a) -> GenHaxl u w a
catch (GenHaxl m) h = GenHaxl $ \env -> do
r <- m env
case r of
Done a -> return (Done a)
Throw e | Just e' <- fromException e -> unHaxl (h e') env
| otherwise -> return (Throw e)
Blocked ivar k -> return (Blocked ivar (Cont (catch (toHaxl k) h)))
catchIf
:: Exception e => (e -> Bool) -> GenHaxl u w a -> (e -> GenHaxl u w a)
-> GenHaxl u w a
catchIf cond haxl handler =
catch haxl $ \e -> if cond e then handler e else throw e
try :: Exception e => GenHaxl u w a -> GenHaxl u w (Either e a)
try haxl = (Right <$> haxl) `catch` (return . Left)
instance Catch.MonadThrow (GenHaxl u w) where throwM = Haxl.Core.Monad.throw
instance Catch.MonadCatch (GenHaxl u w) where catch = Haxl.Core.Monad.catch
unsafeLiftIO :: IO a -> GenHaxl u w a
unsafeLiftIO m = GenHaxl $ \_env -> Done <$> m
unsafeToHaxlException :: GenHaxl u w a -> GenHaxl u w a
unsafeToHaxlException (GenHaxl m) = GenHaxl $ \env -> do
r <- m env `Exception.catch` \e -> return (Throw e)
case r of
Blocked cvar c ->
return (Blocked cvar (Cont (unsafeToHaxlException (toHaxl c))))
other -> return other
tryToHaxlException :: GenHaxl u w a -> GenHaxl u w (Either HaxlException a)
tryToHaxlException h = left asHaxlException <$> try (unsafeToHaxlException h)
dumpCacheAsHaskell :: GenHaxl u w String
dumpCacheAsHaskell = dumpCacheAsHaskellFn "loadCache" "GenHaxl u w ()"
dumpCacheAsHaskellFn :: String -> String -> GenHaxl u w String
dumpCacheAsHaskellFn fnName fnType = do
ref <- env cacheRef
let
readIVar (IVar ref) = do
r <- readIORef ref
case r of
IVarFull (Ok a _) -> return (Just (Right a))
IVarFull (ThrowHaxl e _) -> return (Just (Left e))
IVarFull (ThrowIO e) -> return (Just (Left e))
IVarEmpty _ -> return Nothing
mk_cr (req, res) =
text "cacheRequest" <+> parens (text req) <+> parens (result res)
result (Left e) = text "except" <+> parens (text (show e))
result (Right s) = text "Right" <+> parens (text s)
entries <- unsafeLiftIO $ do
cache <- readIORef ref
showCache cache readIVar
let
body = if null entries
then text "return ()"
else vcat (map mk_cr (concatMap snd entries))
return $ show $
text (fnName ++ " :: " ++ fnType) $$
text (fnName ++ " = do") $$
nest 2 body $$
text ""