#if __GLASGOW_HASKELL >= 800
#else
#endif
module Haxl.Core.Monad (
GenHaxl (..), runHaxl,
env, withEnv, withLabel, withFingerprintLabel,
Env(..), Caches, caches, initEnvWithData, initEnv, emptyEnv,
throw, catch, catchIf, try, tryToHaxlException,
ShowReq, dataFetch, dataFetchWithShow, uncachedRequest, cacheRequest,
cacheResult, cacheResultWithShow, cachedComputation,
dumpCacheAsHaskell, dumpCacheAsHaskellFn,
newMemo, newMemoWith, prepareMemo, runMemo,
newMemo1, newMemoWith1, prepareMemo1, runMemo1,
newMemo2, newMemoWith2, prepareMemo2, runMemo2,
unsafeLiftIO, unsafeToHaxlException,
pAnd, pOr
) where
import Haxl.Core.Types
import Haxl.Core.ShowP
import Haxl.Core.StateStore
import Haxl.Core.Exception
import Haxl.Core.RequestStore
import Haxl.Core.Util
import Haxl.Core.DataCache as DataCache
import qualified Data.Text as Text
import qualified Control.Monad.Catch as Catch
import Control.Exception (Exception(..), SomeException)
#if __GLASGOW_HASKELL__ >= 710
import GHC.Conc (getAllocationCounter, setAllocationCounter)
#endif
import Control.Monad
import qualified Control.Exception as Exception
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative hiding (Const)
#endif
import Control.DeepSeq
import GHC.Exts (IsString(..), Addr#)
#if __GLASGOW_HASKELL__ < 706
import Prelude hiding (catch)
#endif
import Data.Functor.Constant
import Data.Hashable
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import Data.IORef
import Data.List
import qualified Data.Map as Map
import Data.Monoid
import Data.Time
import Data.Typeable
import Text.Printf
import Text.PrettyPrint hiding ((<>))
import Control.Arrow (left)
#ifdef EVENTLOG
import Control.Exception (bracket_)
import Debug.Trace (traceEventIO)
#endif
#ifdef PROFILING
import GHC.Stack
#endif
#if __GLASGOW_HASKELL__ < 710
import Data.Int (Int64)
getAllocationCounter :: IO Int64
getAllocationCounter = return 0
setAllocationCounter :: Int64 -> IO ()
setAllocationCounter _ = return ()
#endif
data Env u = Env
{ cacheRef :: !(IORef (DataCache ResultVar))
, memoRef :: !(IORef (DataCache (MemoVar u)))
, flags :: !Flags
, userEnv :: u
, statsRef :: !(IORef Stats)
, profLabel :: ProfileLabel
, profRef :: !(IORef Profile)
, states :: StateStore
}
type Caches u = (IORef (DataCache ResultVar), IORef (DataCache (MemoVar u)))
caches :: Env u -> Caches u
caches env = (cacheRef env, memoRef env)
initEnvWithData :: StateStore -> u -> Caches u -> IO (Env u)
initEnvWithData states e (cref, mref) = do
sref <- newIORef emptyStats
pref <- newIORef emptyProfile
return Env
{ cacheRef = cref
, memoRef = mref
, flags = defaultFlags
, userEnv = e
, states = states
, statsRef = sref
, profLabel = "MAIN"
, profRef = pref
}
initEnv :: StateStore -> u -> IO (Env u)
initEnv states e = do
cref <- newIORef emptyDataCache
mref <- newIORef emptyDataCache
initEnvWithData states e (cref,mref)
emptyEnv :: u -> IO (Env u)
emptyEnv = initEnv stateEmpty
newtype GenHaxl u a = GenHaxl
{ unHaxl :: Env u -> IORef (RequestStore u) -> IO (Result u a) }
data Result u a
= Done a
| Throw SomeException
| Blocked (Cont u a)
data Cont u a
= Cont (GenHaxl u a)
| forall b. Cont u b :>>= (b -> GenHaxl u a)
| forall b. (Cont u (b -> a)) :<*> (Cont u b)
| forall b. (b -> a) :<$> (Cont u b)
toHaxl :: Cont u a -> GenHaxl u a
toHaxl (Cont haxl) = haxl
toHaxl ((m :>>= k1) :>>= k2) = toHaxl (m :>>= (k1 >=> k2))
toHaxl (c :>>= k) = toHaxl c >>= k
toHaxl ((f :<$> i) :<*> (g :<$> j)) =
toHaxl (((\x y -> f x (g y)) :<$> i) :<*> j)
toHaxl (f :<*> x) = toHaxl f <*> toHaxl x
toHaxl (f :<$> (g :<$> x)) = toHaxl ((f . g) :<$> x)
toHaxl (f :<$> x) = fmap f (toHaxl x)
instance (Show a) => Show (Result u a) where
show (Done a) = printf "Done(%s)" $ show a
show (Throw e) = printf "Throw(%s)" $ show e
show Blocked{} = "Blocked"
instance Monad (GenHaxl u) where
return a = GenHaxl $ \_env _ref -> return (Done a)
GenHaxl m >>= k = GenHaxl $ \env ref -> do
e <- m env ref
case e of
Done a -> unHaxl (k a) env ref
Throw e -> return (Throw e)
Blocked cont -> return (Blocked (cont :>>= k))
fail msg = GenHaxl $ \_env _ref ->
return $ Throw $ toException $ MonadFail $ Text.pack msg
(>>) = (*>)
instance Functor (GenHaxl u) where
fmap f (GenHaxl m) = GenHaxl $ \env ref -> do
r <- m env ref
case r of
Done a -> return (Done (f a))
Throw e -> return (Throw e)
Blocked a' -> return (Blocked (f :<$> a'))
instance Applicative (GenHaxl u) where
pure = return
GenHaxl f <*> GenHaxl a = GenHaxl $ \env ref -> do
r <- f env ref
case r of
Throw e -> return (Throw e)
Done f' -> do
ra <- a env ref
case ra of
Done a' -> return (Done (f' a'))
Throw e -> return (Throw e)
Blocked a' -> return (Blocked (f' :<$> a'))
Blocked f' -> do
ra <- a env ref
case ra of
Done a' -> return (Blocked (($ a') :<$> f'))
Throw e -> return (Blocked (f' :<*> Cont (throw e)))
Blocked a' -> return (Blocked (f' :<*> a'))
runHaxl :: Env u -> GenHaxl u a -> IO a
#ifdef EVENTLOG
runHaxl env h = do
let go !n env c = do
traceEventIO "START computation"
ref <- newIORef noRequests
e <- (unHaxl $ toHaxl c) env ref
traceEventIO "STOP computation"
case e of
Done a -> return a
Throw e -> Exception.throw e
Blocked cont -> do
bs <- readIORef ref
writeIORef ref noRequests
traceEventIO "START performFetches"
n' <- performFetches n env bs
traceEventIO "STOP performFetches"
when (caching (flags env) == 0) $
writeIORef (cacheRef env) emptyDataCache
go n' env cont
traceEventIO "START runHaxl"
r <- go 0 env (Cont h)
traceEventIO "STOP runHaxl"
return r
#else
runHaxl env (GenHaxl haxl) = do
ref <- newIORef noRequests
e <- haxl env ref
case e of
Done a -> return a
Throw e -> Exception.throw e
Blocked cont -> do
bs <- readIORef ref
writeIORef ref noRequests
void (performFetches 0 env bs)
when (caching (flags env) == 0) $
writeIORef (cacheRef env) emptyDataCache
runHaxl env (toHaxl cont)
#endif
env :: (Env u -> a) -> GenHaxl u a
env f = GenHaxl $ \env _ref -> return (Done (f env))
withEnv :: Env u -> GenHaxl u a -> GenHaxl u a
withEnv newEnv (GenHaxl m) = GenHaxl $ \_env ref -> do
r <- m newEnv ref
case r of
Done a -> return (Done a)
Throw e -> return (Throw e)
Blocked k -> return (Blocked (Cont (withEnv newEnv (toHaxl k))))
withLabel :: ProfileLabel -> GenHaxl u a -> GenHaxl u a
withLabel l (GenHaxl m) = GenHaxl $ \env ref ->
if report (flags env) < 4
then m env ref
else collectProfileData l m env ref
withFingerprintLabel :: Addr# -> Addr# -> GenHaxl u a -> GenHaxl u a
withFingerprintLabel mnPtr nPtr (GenHaxl m) = GenHaxl $ \env ref ->
if report (flags env) < 4
then m env ref
else collectProfileData
(Text.unpackCString# mnPtr <> "." <> Text.unpackCString# nPtr)
m env ref
collectProfileData
:: ProfileLabel
-> (Env u -> IORef (RequestStore u) -> IO (Result u a))
-> Env u -> IORef (RequestStore u)
-> IO (Result u a)
collectProfileData l m env ref = do
a0 <- getAllocationCounter
r <- m env{profLabel=l} ref
a1 <- getAllocationCounter
modifyProfileData env l (a0 a1)
setAllocationCounter a1
case r of
Done a -> return (Done a)
Throw e -> return (Throw e)
Blocked k -> return (Blocked (Cont (withLabel l (toHaxl k))))
modifyProfileData :: Env u -> ProfileLabel -> AllocCount -> IO ()
modifyProfileData env label allocs =
modifyIORef' (profRef env) $ \ p ->
p { profile =
HashMap.insertWith updEntry label newEntry .
HashMap.insertWith updCaller caller newCaller $
profile p }
where caller = profLabel env
newEntry =
emptyProfileData
{ profileAllocs = allocs
, profileDeps = HashSet.singleton caller }
updEntry _ old =
old { profileAllocs = profileAllocs old + allocs
, profileDeps = HashSet.insert caller (profileDeps old) }
newCaller =
emptyProfileData { profileAllocs = allocs }
updCaller _ old =
old { profileAllocs = profileAllocs old allocs }
incrementMemoHitCounterFor :: ProfileLabel -> Profile -> Profile
incrementMemoHitCounterFor lbl p =
p { profile = HashMap.adjust incrementMemoHitCounter lbl (profile p) }
incrementMemoHitCounter :: ProfileData -> ProfileData
incrementMemoHitCounter pd = pd { profileMemoHits = succ (profileMemoHits pd) }
throw :: (Exception e) => e -> GenHaxl u a
throw e = GenHaxl $ \_env _ref -> raise e
raise :: (Exception e) => e -> IO (Result u 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 a -> (e -> GenHaxl u a) -> GenHaxl u a
catch (GenHaxl m) h = GenHaxl $ \env ref -> do
r <- m env ref
case r of
Done a -> return (Done a)
Throw e | Just e' <- fromException e -> unHaxl (h e') env ref
| otherwise -> return (Throw e)
Blocked k -> return (Blocked (Cont (catch (toHaxl k) h)))
catchIf
:: Exception e => (e -> Bool) -> GenHaxl u a -> (e -> GenHaxl u a)
-> GenHaxl u a
catchIf cond haxl handler =
catch haxl $ \e -> if cond e then handler e else throw e
try :: Exception e => GenHaxl u a -> GenHaxl u (Either e a)
try haxl = (Right <$> haxl) `catch` (return . Left)
instance Catch.MonadThrow (GenHaxl u) where throwM = Haxl.Core.Monad.throw
instance Catch.MonadCatch (GenHaxl u) where catch = Haxl.Core.Monad.catch
unsafeLiftIO :: IO a -> GenHaxl u a
unsafeLiftIO m = GenHaxl $ \_env _ref -> Done <$> m
unsafeToHaxlException :: GenHaxl u a -> GenHaxl u a
unsafeToHaxlException (GenHaxl m) = GenHaxl $ \env ref -> do
r <- m env ref `Exception.catch` \e -> return (Throw e)
case r of
Blocked c -> return (Blocked (Cont (unsafeToHaxlException (toHaxl c))))
other -> return other
tryToHaxlException :: GenHaxl u a -> GenHaxl u (Either HaxlException a)
tryToHaxlException h = left asHaxlException <$> try (unsafeToHaxlException h)
data CacheResult a
= Uncached (ResultVar a)
| CachedNotFetched (ResultVar a)
| Cached (Either SomeException a)
cached :: Request r a => Env u -> r a -> IO (CacheResult a)
cached = cachedWithInsert show DataCache.insert
type ShowReq r a = (r a -> String, a -> String)
cachedWithInsert
:: Typeable (r a)
=> (r a -> String)
-> (r a -> ResultVar a -> DataCache ResultVar -> DataCache ResultVar) -> Env u
-> r a -> IO (CacheResult a)
cachedWithInsert showFn insertFn env req = do
let
doFetch insertFn request cache = do
rvar <- newEmptyResult
writeIORef (cacheRef env) $! insertFn request rvar cache
return (Uncached rvar)
cache <- readIORef (cacheRef env)
case DataCache.lookup req cache of
Nothing -> doFetch insertFn req cache
Just rvar -> do
mb <- tryReadResult rvar
case mb of
Nothing -> return (CachedNotFetched rvar)
Just r -> do
ifTrace (flags env) 3 $ putStrLn $ case r of
Left _ -> "Cached error: " ++ showFn req
Right _ -> "Cached request: " ++ showFn req
return (Cached r)
logFetch :: Env u -> (r a -> String) -> r a -> IO ()
#ifdef PROFILING
logFetch env showFn req = do
ifReport (flags env) 5 $ do
stack <- currentCallStack
modifyIORef' (statsRef env) $ \(Stats s) ->
Stats (FetchCall (showFn req) stack : s)
#else
logFetch _ _ _ = return ()
#endif
dataFetch :: (DataSource u r, Request r a) => r a -> GenHaxl u a
dataFetch = dataFetchWithInsert show DataCache.insert
dataFetchWithShow
:: (DataSource u r, Eq (r a), Hashable (r a), Typeable (r a))
=> ShowReq r a
-> r a -> GenHaxl u a
dataFetchWithShow (showReq, showRes) = dataFetchWithInsert showReq
(DataCache.insertWithShow showReq showRes)
dataFetchWithInsert
:: (DataSource u r, Eq (r a), Hashable (r a), Typeable (r a))
=> (r a -> String)
-> (r a -> ResultVar a -> DataCache ResultVar -> DataCache ResultVar)
-> r a
-> GenHaxl u a
dataFetchWithInsert showFn insertFn req = GenHaxl $ \env ref -> do
res <- cachedWithInsert showFn insertFn env req
ifProfiling (flags env) $ addProfileFetch env req
case res of
Uncached rvar -> do
logFetch env showFn req
modifyIORef' ref $ \bs -> addRequest (BlockedFetch req rvar) bs
return $ Blocked (Cont (continueFetch showFn req rvar))
CachedNotFetched rvar ->
return (Blocked (Cont (continueFetch showFn req rvar)))
Cached (Left ex) -> return (Throw ex)
Cached (Right a) -> return (Done a)
addProfileFetch
:: (DataSourceName r, Eq (r a), Hashable (r a), Typeable (r a))
=> Env u -> r a -> IO ()
addProfileFetch env req = do
c <- getAllocationCounter
modifyIORef' (profRef env) $ \ p ->
let
dsName :: Text.Text
dsName = dataSourceName req
upd :: Round -> ProfileData -> ProfileData
upd round d =
d { profileFetches = Map.alter (Just . f) round (profileFetches d) }
f Nothing = HashMap.singleton dsName 1
f (Just hm) = HashMap.insertWith (+) dsName 1 hm
in case DataCache.lookup req (profileCache p) of
Nothing ->
let r = profileRound p
in p { profile = HashMap.adjust (upd r) (profLabel env) (profile p)
, profileCache =
DataCache.insertNotShowable req (Constant r) (profileCache p)
}
Just (Constant r) ->
p { profile = HashMap.adjust (upd r) (profLabel env) (profile p) }
setAllocationCounter c
uncachedRequest :: (DataSource u r, Show (r a)) => r a -> GenHaxl u a
uncachedRequest req = GenHaxl $ \_env ref -> do
rvar <- newEmptyResult
modifyIORef' ref $ \bs -> addRequest (BlockedFetch req rvar) bs
return $ Blocked (Cont (continueFetch show req rvar))
continueFetch
:: (r a -> String)
-> r a -> ResultVar a -> GenHaxl u a
continueFetch showFn req rvar = GenHaxl $ \_env _ref -> do
m <- tryReadResult rvar
case m of
Nothing -> raise . DataSourceError $
Text.pack (showFn req) <> " did not set contents of result var"
Just r -> done r
cacheResult :: Request r a => r a -> IO a -> GenHaxl u a
cacheResult = cacheResultWithInsert show DataCache.insert
cacheResultWithShow
:: (Eq (r a), Hashable (r a), Typeable (r a))
=> ShowReq r a -> r a -> IO a -> GenHaxl u a
cacheResultWithShow (showReq, showRes) = cacheResultWithInsert showReq
(DataCache.insertWithShow showReq showRes)
cacheResultWithInsert
:: Typeable (r a)
=> (r a -> String)
-> (r a -> ResultVar a -> DataCache ResultVar -> DataCache ResultVar) -> r a
-> IO a -> GenHaxl u a
cacheResultWithInsert showFn insertFn req val = GenHaxl $ \env _ref -> do
cachedResult <- cachedWithInsert showFn insertFn env req
case cachedResult of
Uncached rvar -> do
result <- Exception.try val
putResult rvar result
case result of
Left e -> do rethrowAsyncExceptions e; done result
_other -> done result
Cached result -> done result
CachedNotFetched _ -> corruptCache
where
corruptCache = raise . DataSourceError $ Text.concat
[ Text.pack (showFn req)
, " has a corrupted cache value: these requests are meant to"
, " return immediately without an intermediate value. Either"
, " the cache was updated incorrectly, or you're calling"
, " cacheResult on a query that involves a blocking fetch."
]
cacheRequest
:: Request req a => req a -> Either SomeException a -> GenHaxl u ()
cacheRequest request result = GenHaxl $ \env _ref -> do
res <- cached env request
case res of
Uncached rvar -> do
putResult rvar result
return $ Done ()
_other -> raise $
DataSourceError "cacheRequest: request is already in the cache"
instance IsString a => IsString (GenHaxl u a) where
fromString s = return (fromString s)
performFetches :: forall u. Int -> Env u -> RequestStore u -> IO Int
performFetches n env reqs = do
let f = flags env
sref = statsRef env
jobs = contents reqs
!n' = n + length jobs
t0 <- getCurrentTime
a0 <- getAllocationCounter
let
roundstats =
[ (dataSourceName (getReq reqs), length reqs)
| BlockedFetches reqs <- jobs ]
where
getReq :: [BlockedFetch r] -> r a
getReq = undefined
ifTrace f 1 $
printf "Batch data fetch (%s)\n" $
intercalate (", "::String) $
map (\(name,num) -> printf "%d %s" num (Text.unpack name)) roundstats
ifTrace f 3 $
forM_ jobs $ \(BlockedFetches reqs) ->
forM_ reqs $ \(BlockedFetch r _) -> putStrLn (showp r)
let
applyFetch (i, BlockedFetches (reqs :: [BlockedFetch r])) =
case stateGet (states env) of
Nothing ->
return (SyncFetch (mapM_ (setError e) reqs))
where e req = DataSourceError $
"data source not initialized: " <>
dataSourceName req <>
": " <>
Text.pack (showp req)
Just state ->
return $ wrapFetchInTrace i (length reqs)
(dataSourceName (undefined :: r a))
$ wrapFetchInCatch reqs
$ fetch state f (userEnv env) reqs
fetches <- mapM applyFetch $ zip [n..] jobs
deepStats <-
if report f >= 2
then do
(refs, timedfetches) <- mapAndUnzipM wrapFetchInStats fetches
scheduleFetches timedfetches
mapM (fmap Just . readIORef) refs
else do
scheduleFetches fetches
return $ repeat Nothing
failures <-
if report f >= 3
then
forM jobs $ \(BlockedFetches reqs) ->
fmap (Just . length) . flip filterM reqs $ \(BlockedFetch _ rvar) -> do
mb <- tryReadResult rvar
return $ case mb of
Just (Right _) -> False
_ -> True
else return $ repeat Nothing
let dsroundstats = HashMap.fromList
[ (name, DataSourceRoundStats { dataSourceFetches = dsfetch
, dataSourceTime = fst <$> dsStats
, dataSourceAllocation = snd <$> dsStats
, dataSourceFailures = dsfailure
})
| ((name, dsfetch), dsStats, dsfailure) <-
zip3 roundstats deepStats failures]
a1 <- getAllocationCounter
t1 <- getCurrentTime
let
roundtime = realToFrac (diffUTCTime t1 t0) :: Double
allocation = fromIntegral $ a0 a1
ifReport f 1 $
modifyIORef' sref $ \(Stats rounds) -> roundstats `deepseq`
Stats (RoundStats (microsecs roundtime) allocation dsroundstats: rounds)
ifTrace f 1 $
printf "Batch data fetch done (%.2fs)\n" (realToFrac roundtime :: Double)
ifProfiling f $
modifyIORef' (profRef env) $ \ p -> p { profileRound = 1 + profileRound p }
return n'
wrapFetchInCatch :: [BlockedFetch req] -> PerformFetch -> PerformFetch
wrapFetchInCatch reqs fetch =
case fetch of
SyncFetch io ->
SyncFetch (io `Exception.catch` handler)
AsyncFetch fio ->
AsyncFetch (\io -> fio io `Exception.catch` handler)
where
handler :: SomeException -> IO ()
handler e = do
rethrowAsyncExceptions e
mapM_ (forceError e) reqs
forceError e (BlockedFetch _ rvar) = do
void $ tryTakeResult rvar
putResult rvar (except e)
wrapFetchInStats :: PerformFetch -> IO (IORef (Microseconds, Int), PerformFetch)
wrapFetchInStats f = do
r <- newIORef (0, 0)
case f of
SyncFetch io -> return (r, SyncFetch (statsForIO io >>= writeIORef r))
AsyncFetch f -> do
inner_r <- newIORef (0, 0)
return (r, AsyncFetch $ \inner -> do
(totalTime, totalAlloc) <-
statsForIO (f (statsForIO inner >>= writeIORef inner_r))
(innerTime, innerAlloc) <- readIORef inner_r
writeIORef r (totalTime innerTime, totalAlloc innerAlloc))
where
statsForIO io = do
prevAlloc <- getAllocationCounter
t <- time io
postAlloc <- getAllocationCounter
return (t, fromIntegral $ prevAlloc postAlloc)
wrapFetchInTrace :: Int -> Int -> Text.Text -> PerformFetch -> PerformFetch
#ifdef EVENTLOG
wrapFetchInTrace i n dsName f =
case f of
SyncFetch io -> SyncFetch (wrapF "Sync" io)
AsyncFetch fio -> AsyncFetch (wrapF "Async" . fio . unwrapF "Async")
where
d = Text.unpack dsName
wrapF :: String -> IO a -> IO a
wrapF ty = bracket_ (traceEventIO $ printf "START %d %s (%d %s)" i d n ty)
(traceEventIO $ printf "STOP %d %s (%d %s)" i d n ty)
unwrapF :: String -> IO a -> IO a
unwrapF ty = bracket_ (traceEventIO $ printf "STOP %d %s (%d %s)" i d n ty)
(traceEventIO $ printf "START %d %s (%d %s)" i d n ty)
#else
wrapFetchInTrace _ _ _ f = f
#endif
time :: IO () -> IO Microseconds
time io = do
t0 <- getCurrentTime
io
t1 <- getCurrentTime
return . microsecs . realToFrac $ t1 `diffUTCTime` t0
microsecs :: Double -> Microseconds
microsecs t = round (t * 10^(6::Int))
scheduleFetches :: [PerformFetch] -> IO()
scheduleFetches fetches = async_fetches sync_fetches
where
async_fetches :: IO () -> IO ()
async_fetches = compose [f | AsyncFetch f <- fetches]
sync_fetches :: IO ()
sync_fetches = sequence_ [io | SyncFetch io <- fetches]
newtype MemoVar u a = MemoVar (IORef (MemoStatus u a))
newtype MemoVar1 u a b = MemoVar1 (IORef (MemoStatus1 u a b))
newtype MemoVar2 u a b c = MemoVar2 (IORef (MemoStatus2 u a b c))
data MemoStatus u a
= MemoInProgress (RoundId u) (GenHaxl u a)
| MemoDone (Either SomeException a)
| MemoNew (GenHaxl u a)
| MemoEmpty
data MemoStatus1 u a b
= MemoEmpty1
| MemoTbl1 ( a -> GenHaxl u b
, HashMap.HashMap a
(MemoVar u b))
data MemoStatus2 u a b c
= MemoEmpty2
| MemoTbl2 ( a -> b -> GenHaxl u c
, HashMap.HashMap a
(HashMap.HashMap b
(MemoVar u c)))
type RoundId u = IORef (RequestStore u)
cachedComputation
:: forall req u a.
( Eq (req a)
, Hashable (req a)
, Typeable (req a))
=> req a -> GenHaxl u a -> GenHaxl u a
cachedComputation req haxl = do
env <- env id
cache <- unsafeLiftIO $ readIORef (memoRef env)
unsafeLiftIO $ ifProfiling (flags env) $
modifyIORef' (profRef env) (incrementMemoHitCounterFor (profLabel env))
memoVar <- case DataCache.lookup req cache of
Nothing -> do
memoVar <- newMemoWith haxl
unsafeLiftIO $ writeIORef (memoRef env) $!
DataCache.insertNotShowable req memoVar cache
return memoVar
Just memoVar -> return memoVar
runMemo memoVar
done :: Either SomeException a -> IO (Result u a)
done = return . either Throw Done
dumpCacheAsHaskell :: GenHaxl u String
dumpCacheAsHaskell = dumpCacheAsHaskellFn "loadCache" "GenHaxl u ()"
dumpCacheAsHaskellFn :: String -> String -> GenHaxl u String
dumpCacheAsHaskellFn fnName fnType = do
ref <- env cacheRef
entries <- unsafeLiftIO $ readIORef ref >>= showCache
let
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)
return $ show $
text (fnName ++ " :: " ++ fnType) $$
text (fnName ++ " = do") $$
nest 2 (vcat (map mk_cr (concatMap snd entries))) $$
text ""
newMemo :: GenHaxl u (MemoVar u a)
newMemo = unsafeLiftIO $ MemoVar <$> newIORef MemoEmpty
prepareMemo :: MemoVar u a -> GenHaxl u a -> GenHaxl u ()
prepareMemo (MemoVar memoRef) memoCmp
= unsafeLiftIO $ writeIORef memoRef (MemoNew memoCmp)
newMemoWith :: GenHaxl u a -> GenHaxl u (MemoVar u a)
newMemoWith memoCmp = do
memoVar <- newMemo
prepareMemo memoVar memoCmp
return memoVar
runMemo :: MemoVar u a -> GenHaxl u a
runMemo memoVar@(MemoVar memoRef) = GenHaxl $ \env rID ->
readIORef memoRef >>= \case
MemoEmpty -> raise $ CriticalError "Attempting to run empty memo."
MemoDone result -> done result
MemoNew cont -> runContToMemo cont env rID
MemoInProgress rID' cont
| rID' == rID -> return (Blocked $ Cont retryMemo)
| otherwise -> runContToMemo cont env rID
where
retryMemo = runMemo memoVar
runContToMemo cont env rID = do
result <- unHaxl cont env rID
case result of
Done a -> finalize (Right a)
Throw e -> finalize (Left e)
Blocked c -> do
writeIORef memoRef (MemoInProgress rID (toHaxl c))
return (Blocked $ Cont retryMemo)
finalize r = writeIORef memoRef (MemoDone r) >> done r
newMemo1 :: GenHaxl u (MemoVar1 u a b)
newMemo1 = unsafeLiftIO $ MemoVar1 <$> newIORef MemoEmpty1
newMemoWith1 :: (a -> GenHaxl u b) -> GenHaxl u (MemoVar1 u a b)
newMemoWith1 f = newMemo1 >>= \r -> prepareMemo1 r f >> return r
prepareMemo1 :: MemoVar1 u a b -> (a -> GenHaxl u b) -> GenHaxl u ()
prepareMemo1 (MemoVar1 r) f
= unsafeLiftIO $ writeIORef r (MemoTbl1 (f, HashMap.empty))
runMemo1 :: (Eq a, Hashable a) => MemoVar1 u a b -> a -> GenHaxl u b
runMemo1 (MemoVar1 r) k = unsafeLiftIO (readIORef r) >>= \case
MemoEmpty1 -> throw $ CriticalError "Attempting to run empty memo."
MemoTbl1 (f, h) -> case HashMap.lookup k h of
Nothing -> do
x <- newMemoWith (f k)
unsafeLiftIO $ writeIORef r (MemoTbl1 (f, HashMap.insert k x h))
runMemo x
Just v -> runMemo v
newMemo2 :: GenHaxl u (MemoVar2 u a b c)
newMemo2 = unsafeLiftIO $ MemoVar2 <$> newIORef MemoEmpty2
newMemoWith2 :: (a -> b -> GenHaxl u c) -> GenHaxl u (MemoVar2 u a b c)
newMemoWith2 f = newMemo2 >>= \r -> prepareMemo2 r f >> return r
prepareMemo2 :: MemoVar2 u a b c -> (a -> b -> GenHaxl u c) -> GenHaxl u ()
prepareMemo2 (MemoVar2 r) f
= unsafeLiftIO $ writeIORef r (MemoTbl2 (f, HashMap.empty))
runMemo2 :: (Eq a, Hashable a, Eq b, Hashable b)
=> MemoVar2 u a b c
-> a -> b -> GenHaxl u c
runMemo2 (MemoVar2 r) k1 k2 = unsafeLiftIO (readIORef r) >>= \case
MemoEmpty2 -> throw $ CriticalError "Attempting to run empty memo."
MemoTbl2 (f, h1) -> case HashMap.lookup k1 h1 of
Nothing -> do
v <- newMemoWith (f k1 k2)
unsafeLiftIO $ writeIORef r
(MemoTbl2 (f, HashMap.insert k1 (HashMap.singleton k2 v) h1))
runMemo v
Just h2 -> case HashMap.lookup k2 h2 of
Nothing -> do
v <- newMemoWith (f k1 k2)
unsafeLiftIO $ writeIORef r
(MemoTbl2 (f, HashMap.insert k1 (HashMap.insert k2 v h2) h1))
runMemo v
Just v -> runMemo v
infixr 5 `pAnd`
infixr 4 `pOr`
pOr :: GenHaxl u Bool -> GenHaxl u Bool -> GenHaxl u Bool
GenHaxl a `pOr` GenHaxl b = GenHaxl $ \env ref -> do
ra <- a env ref
case ra of
Done True -> return (Done True)
Done False -> b env ref
Throw _ -> return ra
Blocked a' -> do
rb <- b env ref
case rb of
Done True -> return (Blocked (Cont (return True)))
Done False -> return ra
Throw e -> return (Blocked (Cont (throw e)))
Blocked b' -> return (Blocked (Cont (toHaxl a' `pOr` toHaxl b')))
pAnd :: GenHaxl u Bool -> GenHaxl u Bool -> GenHaxl u Bool
GenHaxl a `pAnd` GenHaxl b = GenHaxl $ \env ref -> do
ra <- a env ref
case ra of
Done False -> return (Done False)
Done True -> b env ref
Throw _ -> return ra
Blocked a' -> do
rb <- b env ref
case rb of
Done False -> return (Blocked (Cont (return False)))
Done True -> return ra
Throw _ -> return rb
Blocked b' -> return (Blocked (Cont (toHaxl a' `pAnd` toHaxl b')))