{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Haxl.Core.Memo
(
cachedComputation
, preCacheComputation
, memo
, memoFingerprint
, MemoFingerprintKey(..)
, memoize, memoize1, memoize2
, memoUnique
, MemoVar
, newMemo
, newMemoWith
, prepareMemo
, runMemo
) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Exception as Exception hiding (throw)
import Data.IORef
import qualified Data.HashMap.Strict as HashMap
import Data.Text (Text)
import Data.Typeable
import Data.Hashable
import Data.Word
import GHC.Prim (Addr#)
import Haxl.Core.Exception
import Haxl.Core.DataCache as DataCache
import Haxl.Core.Flags
import Haxl.Core.Monad
import Haxl.Core.Profile
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 = GenHaxl $ \env@Env{..} -> do
cache <- readIORef memoRef
ifProfiling flags $
modifyIORef' profRef (incrementMemoHitCounterFor profLabel)
case DataCache.lookup req cache of
Just ivar -> unHaxl (getIVar ivar) env
Nothing -> do
ivar <- newIVar
writeIORef memoRef $! DataCache.insertNotShowable req ivar cache
unHaxl (execMemoNow haxl ivar) env
preCacheComputation
:: forall req u a.
( Eq (req a)
, Hashable (req a)
, Typeable (req a))
=> req a -> GenHaxl u a -> GenHaxl u a
preCacheComputation req haxl = GenHaxl $ \env@Env{..} -> do
cache <- readIORef memoRef
ifProfiling flags $
modifyIORef' profRef (incrementMemoHitCounterFor profLabel)
case DataCache.lookup req cache of
Just _ -> return $ Throw $ toException $ InvalidParameter
"preCacheComputation: key is already cached"
Nothing -> do
ivar <- newIVar
writeIORef memoRef $! DataCache.insertNotShowable req ivar cache
unHaxl (execMemoNow haxl ivar) env
newtype MemoVar u a = MemoVar (IORef (MemoStatus u a))
data MemoStatus u a
= MemoEmpty
| MemoReady (GenHaxl u a)
| MemoRun {-# UNPACK #-} !(IVar u a)
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 (MemoReady 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 memoRef) = GenHaxl $ \env -> do
stored <- readIORef memoRef
case stored of
MemoEmpty -> raise $ CriticalError "Attempting to run empty memo."
MemoReady cont -> do
ivar <- newIVar
writeIORef memoRef (MemoRun ivar)
unHaxl (execMemoNow cont ivar) env
MemoRun ivar -> unHaxl (getIVar ivar) env
execMemoNow :: GenHaxl u a -> IVar u a -> GenHaxl u a
execMemoNow cont ivar = GenHaxl $ \env -> do
let !ienv = imperative env
r <- Exception.try $ unHaxl cont ienv
case r of
Left e -> do
rethrowAsyncExceptions e
putIVar ivar (ThrowIO e) env
throwIO e
Right (Done a) -> do
putIVar ivar (Ok a) env
return (Done a)
Right (Throw ex) -> do
putIVar ivar (ThrowHaxl ex) env
return (Throw ex)
Right (Blocked ivar' cont) -> do
addJob env (toHaxl cont) ivar ivar'
return (Blocked ivar (Cont (getIVar ivar)))
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 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)))
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
memo
:: (Typeable a, Typeable k, Hashable k, Eq k)
=> k -> GenHaxl u a -> GenHaxl u a
memo key = cachedComputation (MemoKey key)
{-# RULES
"memo/Text" memo = memoText :: (Typeable a) =>
Text -> GenHaxl u a -> GenHaxl u a
"memoUnique/Text" memoUnique = memoUniqueText :: (Typeable a) =>
MemoFingerprintKey a -> Text -> GenHaxl u a -> GenHaxl u a
#-}
{-# NOINLINE memo #-}
memoUnique
:: (Typeable a, Typeable k, Hashable k, Eq k)
=> MemoFingerprintKey a -> k -> GenHaxl u a -> GenHaxl u a
memoUnique fp key = memo (fp, key)
{-# NOINLINE memoUnique #-}
data MemoKey k a where
MemoKey :: (Typeable k, Hashable k, Eq k) => k -> MemoKey k a
deriving Typeable
deriving instance Eq (MemoKey k a)
instance Hashable (MemoKey k a) where
hashWithSalt s (MemoKey t) = hashWithSalt s t
data MemoTextKey a where
MemoText :: Text -> MemoTextKey a
deriving Typeable
deriving instance Eq (MemoTextKey a)
instance Hashable (MemoTextKey a) where
hashWithSalt s (MemoText t) = hashWithSalt s t
memoText :: (Typeable a) => Text -> GenHaxl u a -> GenHaxl u a
memoText key = withLabel key . cachedComputation (MemoText key)
memoUniqueText
:: (Typeable a)
=> MemoFingerprintKey a
-> Text
-> GenHaxl u a
-> GenHaxl u a
memoUniqueText fp key = withLabel key . memo (fp, key)
data MemoFingerprintKey a where
MemoFingerprintKey
:: {-# UNPACK #-} !Word64
-> {-# UNPACK #-} !Word64
-> Addr# -> Addr#
-> MemoFingerprintKey a
deriving Typeable
deriving instance Eq (MemoFingerprintKey a)
instance Hashable (MemoFingerprintKey a) where
hashWithSalt s (MemoFingerprintKey x _ _ _) =
hashWithSalt s (fromIntegral x :: Int)
{-# NOINLINE memoFingerprint #-}
memoFingerprint
:: Typeable a => MemoFingerprintKey a -> GenHaxl u a -> GenHaxl u a
memoFingerprint key@(MemoFingerprintKey _ _ mnPtr nPtr) =
withFingerprintLabel mnPtr nPtr . cachedComputation key
memoize :: GenHaxl u a -> GenHaxl u (GenHaxl u a)
memoize a = runMemo <$> newMemoWith a
memoize1 :: (Eq a, Hashable a)
=> (a -> GenHaxl u b)
-> GenHaxl u (a -> GenHaxl u b)
memoize1 f = runMemo1 <$> newMemoWith1 f
memoize2 :: (Eq a, Hashable a, Eq b, Hashable b)
=> (a -> b -> GenHaxl u c)
-> GenHaxl u (a -> b -> GenHaxl u c)
memoize2 f = runMemo2 <$> newMemoWith2 f