{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Haxl.Core.Monad
(
GenHaxl(..)
, Result(..)
, WriteTree(..)
, tellWrite
, tellWriteNoMemo
, write
, writeNoMemo
, flattenWT
, appendWTs
, mbModifyWLRef
, Cont(..)
, toHaxl
, IVar(..)
, IVarContents(..)
, newIVar
, newFullIVar
, withCurrentCCS
, getIVar
, getIVarWithWrites
, putIVar
, ResultVal(..)
, done
, eitherToResult
, eitherToResultThrowIO
, CompleteReq(..)
, Env(..)
, DataCacheItem(..)
, Caches
, caches
, initEnvWithData
, initEnv
, emptyEnv
, env, withEnv
, nextCallId
, sanitizeEnv
, ProfileCurrent(..)
, 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 Haxl.Core.Util (trace_)
import Control.Applicative (liftA2)
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)
#if __GLASGOW_HASKELL__ >= 808
import Control.Monad hiding (MonadFail)
import qualified Control.Monad as CTL
#else
import Control.Monad
#endif
import qualified Control.Exception as Exception
import Data.IORef
import Data.Int
import Data.Either (rights)
#if __GLASGOW_HASKELL__ < 804
import Data.Semigroup
#endif
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 Foreign.Ptr (Ptr)
import GHC.Stack
import Haxl.Core.CallGraph
#endif
data DataCacheItem u w a = DataCacheItem (IVar u w a) {-# UNPACK #-} !CallId
data Env u w = Env
{ dataCache :: {-# UNPACK #-} !(DataCache (DataCacheItem u w))
, memoCache :: {-# UNPACK #-} !(DataCache (DataCacheItem u w))
, memoKey :: {-# UNPACK #-} !CallId
, flags :: !Flags
, userEnv :: u
, statsRef :: {-# UNPACK #-} !(IORef Stats)
, statsBatchIdRef :: {-# UNPACK #-} !(IORef Int)
, callIdRef :: {-# UNPACK #-} !(IORef CallId)
, profCurrent :: ProfileCurrent
, 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])
, writeLogsRef :: {-# UNPACK #-} !(IORef (WriteTree w))
, writeLogsRefNoMemo :: {-# UNPACK #-} !(IORef (WriteTree w))
#ifdef PROFILING
, callGraphRef :: Maybe (IORef CallGraph)
, currFunction :: QualFunction
#endif
}
data ProfileCurrent = ProfileCurrent
{ profCurrentKey :: {-# UNPACK #-} !ProfileKey
, profCurrentLabel :: {-# UNPACK #-} !ProfileLabel
}
type Caches u w = (DataCache (DataCacheItem u w), DataCache (DataCacheItem u w))
caches :: Env u w -> Caches u w
caches env = (dataCache env, memoCache env)
getMaxCallId :: DataCache (DataCacheItem u w) -> IO (Maybe Int)
getMaxCallId c = do
callIds <- rights . concatMap snd <$>
DataCache.readCache c (\(DataCacheItem _ i) -> return i)
case callIds of
[] -> return Nothing
vals -> return $ Just (maximum vals)
-- | 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 (dcache, mcache) = do
newCid <- max <$>
(maybe 0 ((+) 1) <$> getMaxCallId dcache) <*>
(maybe 0 ((+) 1) <$> getMaxCallId mcache)
ciref<- newIORef newCid
sref <- newIORef emptyStats
sbref <- newIORef 0
pref <- newIORef emptyProfile
rs <- newIORef noRequests -- RequestStore
rq <- newIORef JobNil -- RunQueue
sr <- newIORef emptyReqCounts -- SubmittedReqs
comps <- newTVarIO [] -- completion queue
wl <- newIORef NilWrites
wlnm <- newIORef NilWrites
return Env
{ dataCache = dcache
, memoCache = mcache
, memoKey = (-1)
, flags = defaultFlags
, userEnv = e
, states = states
, statsRef = sref
, statsBatchIdRef = sbref
, profCurrent = ProfileCurrent 0 "MAIN"
, callIdRef = ciref
, profRef = pref
, reqStoreRef = rs
, runQueueRef = rq
, submittedReqsRef = sr
, completions = comps
, writeLogsRef = wl
, writeLogsRefNoMemo = wlnm
#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
dcache <- emptyDataCache
mcache <- emptyDataCache
initEnvWithData states e (dcache, mcache)
-- | A new, empty environment.
emptyEnv :: u -> IO (Env u w)
emptyEnv = initEnv stateEmpty
-- | If you're using the env from a failed Haxl computation in a second Haxl
-- computation, it is recommended to sanitize the Env to remove all empty
-- IVars - especially if it's possible the first Haxl computation could've
-- been interrupted via an async exception. This is because if the Haxl
-- computation was interrupted by an exception, it's possible that there are
-- entries in the cache which are still blocked, while the results from
-- outgone fetches have been discarded.
sanitizeEnv :: Env u w -> IO (Env u w)
sanitizeEnv env@Env{..} = do
sanitizedDC <- DataCache.filter isIVarFull dataCache
sanitizedMC <- DataCache.filter isIVarFull memoCache
rs <- newIORef noRequests -- RequestStore
rq <- newIORef JobNil -- RunQueue
comps <- newTVarIO [] -- completion queue
sr <- newIORef emptyReqCounts -- SubmittedReqs
return env
{ dataCache = sanitizedDC
, memoCache = sanitizedMC
, reqStoreRef = rs
, runQueueRef = rq
, completions = comps
, submittedReqsRef = sr
}
where
isIVarFull (DataCacheItem IVar{..} _) = do
ivarContents <- readIORef ivarRef
case ivarContents of
IVarFull _ -> return True
_ -> return False
-- -----------------------------------------------------------------------------
-- 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.
-- Haxl provides no guarantees on the order of the returned logs.
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'. These can be used to do
-- arbitrary "logs" from any Haxl computation. These are better than
-- doing arbitrary IO from a Haxl computation as these writes also get
-- memoized if the Haxl computation associated with them is memoized.
-- Now if this memoized computation is run again, you'll get the writes
-- twice.
-- * 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 ()
tellWriteNoMemo :: w -> GenHaxl u w ()
tellWriteNoMemo = writeNoMemo . SomeWrite
writeNoMemo :: WriteTree w -> GenHaxl u w ()
writeNoMemo wt = GenHaxl $ \Env{..} -> do
mbModifyWLRef wt writeLogsRefNoMemo
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.
#ifdef PROFILING
data IVar u w a = IVar
{ ivarRef :: {-# UNPACK #-} !(IORef (IVarContents u w a))
, ivarCCS :: {-# UNPACK #-} !(Ptr CostCentreStack)
#else
newtype IVar u w a = IVar
{ ivarRef :: IORef (IVarContents u w a)
#endif
}
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 = do
ivarRef <- newIORef (IVarEmpty JobNil)
#ifdef PROFILING
ivarCCS <- getCurrentCCS ivarRef
#endif
return IVar{..}
newFullIVar :: ResultVal a w -> IO (IVar u w a)
newFullIVar r = do
ivarRef <- newIORef (IVarFull r)
#ifdef PROFILING
ivarCCS <- getCurrentCCS ivarRef
#endif
return IVar{..}
withCurrentCCS :: IVar u w a -> IO (IVar u w a)
#ifdef PROFILING
withCurrentCCS ivar = do
ccs <- getCurrentCCS ivar
return ivar{ivarCCS = ccs}
#else
withCurrentCCS = return
#endif
getIVar :: IVar u w a -> GenHaxl u w a
getIVar i@IVar{ivarRef = !ref} = GenHaxl $ \Env{..} -> do
e <- readIORef ref
case e of
IVarFull (Ok a _wt) -> return (Done a)
IVarFull (ThrowHaxl e _wt) -> raiseFromIVar i e
IVarFull (ThrowIO e) -> throwIO e
IVarEmpty _ -> return (Blocked i (Return i))
getIVarApply :: IVar u w (a -> b) -> a -> GenHaxl u w b
getIVarApply i@IVar{ivarRef = !ref} a = GenHaxl $ \Env{..} -> do
e <- readIORef ref
case e of
IVarFull (Ok f _wt) -> return (Done (f a))
IVarFull (ThrowHaxl e _wt) -> raiseFromIVar i e
IVarFull (ThrowIO e) -> throwIO e
IVarEmpty _ ->
return (Blocked i (Cont (getIVarApply i a)))
getIVarWithWrites :: IVar u w a -> GenHaxl u w a
getIVarWithWrites i@IVar{ivarRef = !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
raiseFromIVar i e
IVarFull (ThrowIO e) -> throwIO e
IVarEmpty _ ->
return (Blocked i (Cont (getIVarWithWrites i)))
putIVar :: IVar u w a -> ResultVal a w -> Env u w -> IO ()
putIVar IVar{ivarRef = !ref} a Env{..} = do
e <- readIORef ref
case e of
IVarEmpty jobs -> do
writeIORef ref (IVarFull a)
modifyIORef' runQueueRef (appendJobList jobs)
IVarFull{} -> return ()
{-# INLINE addJob #-}
addJob :: Env u w -> GenHaxl u w b -> IVar u w b -> IVar u w a -> IO ()
addJob env !haxl !resultIVar IVar{ivarRef = !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"
data ResultVal a w
= Ok a (WriteTree w)
| ThrowHaxl SomeException (WriteTree w)
| ThrowIO SomeException
done :: ResultVal a w -> IO (Result u w a)
done (Ok a _) = return (Done a)
done (ThrowHaxl e _) = raise 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
data CompleteReq u w
= forall a . CompleteReq
(Either SomeException a)
!(IVar u w a)
{-# UNPACK #-} !Int64
data Result u w a
= Done a
| Throw SomeException
| forall b . Blocked
{-# UNPACK #-} !(IVar u w b)
(Cont u w a)
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"
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)
| Return (IVar u w a)
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
toHaxl (Return i) = getIVar i
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)
toHaxlBind (Return i) k = getIVar i >>= k
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
toHaxlFmap f (Return i) = f <$> getIVar i
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))
(>>) = (*>)
#if __GLASGOW_HASKELL__ >= 808
instance CTL.MonadFail (GenHaxl u w) where
#endif
fail msg = GenHaxl $ \_env ->
return $ Throw $ toException $ MonadFail $ Text.pack msg
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" $
blockedBlocked env ivar1 fcont ivar2 acont
instance Semigroup a => Semigroup (GenHaxl u w a) where
(<>) = liftA2 (<>)
instance Monoid a => Monoid (GenHaxl u w a) where
mempty = pure mempty
mappend = liftA2 mappend
blockedBlocked
:: Env u w
-> IVar u w c
-> Cont u w (a -> b)
-> IVar u w d
-> Cont u w a
-> IO (Result u w b)
blockedBlocked _ _ (Return i) ivar2 acont =
return (Blocked ivar2 (acont :>>= getIVarApply i))
blockedBlocked _ _ (g :<$> Return i) ivar2 acont =
return (Blocked ivar2 (acont :>>= \ a -> (\f -> g f a) <$> getIVar i))
blockedBlocked env ivar1 fcont ivar2 acont = do
i <- newIVar
addJob env (toHaxl fcont) i ivar1
let cont = acont :>>= \a -> getIVarApply i a
return (Blocked ivar2 cont)
env :: (Env u w -> a) -> GenHaxl u w a
env f = GenHaxl $ \env -> return (Done (f env))
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))))
nextCallId :: Env u w -> IO CallId
nextCallId env = atomicModifyIORef' (callIdRef env) $ \x -> (x+1,x+1)
#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
raiseFromIVar :: Exception e => IVar u w a -> e -> IO (Result u w b)
#ifdef PROFILING
raiseFromIVar ivar e
| Just (HaxlException Nothing h) <- fromException somex = do
stk <- ccsToStrings (ivarCCS ivar)
return (Throw (toException (HaxlException (Just stk) h)))
| otherwise
#else
raiseFromIVar _ivar e
#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 -> do
rethrowAsyncExceptions 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 ()" "cacheRequest"
dumpCacheAsHaskellFn :: String -> String -> String -> GenHaxl u w String
dumpCacheAsHaskellFn fnName fnType cacheFn = do
cache <- env dataCache
let
readIVar (DataCacheItem IVar{ivarRef = !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 cacheFn <+> 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
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 ""