diff --git a/Haxl/Core/Memo.hs b/Haxl/Core/Memo.hs index 86de03b..743bb0d 100644 --- a/Haxl/Core/Memo.hs +++ b/Haxl/Core/Memo.hs @@ -71,7 +71,8 @@ cachedComputation :: forall req u w a. ( Eq (req a) , Hashable (req a) - , Typeable (req a)) + , Typeable (req a) + , Monoid w) => req a -> GenHaxl u w a -> GenHaxl u w a cachedComputation req haxl = GenHaxl $ \env@Env{..} -> do @@ -101,7 +102,8 @@ preCacheComputation :: forall req u w a. ( Eq (req a) , Hashable (req a) - , Typeable (req a)) + , Typeable (req a) + , Monoid w) => req a -> GenHaxl u w a -> GenHaxl u w a preCacheComputation req haxl = GenHaxl $ \env@Env{..} -> do mbRes <- DataCache.lookup req memoCache @@ -196,7 +198,7 @@ newMemoWith memoCmp = do -- > b <- g -- > return (a + b) -- -runMemo :: MemoVar u w a -> GenHaxl u w a +runMemo :: Monoid w => MemoVar u w a -> GenHaxl u w a runMemo (MemoVar memoRef) = GenHaxl $ \env -> do stored <- readIORef memoRef case stored of @@ -215,7 +217,8 @@ runMemo (MemoVar memoRef) = GenHaxl $ \env -> do unHaxl (getIVarWithWrites ivar) env execMemoNowProfiled - :: Env u w + :: Monoid w + => Env u w -> GenHaxl u w a -> IVar u w a -> CallId @@ -246,9 +249,9 @@ execMemoNowProfiled envOuter cont ivar cid = setAllocationCounter a1 return ret -execMemoNow :: Env u w -> GenHaxl u w a -> IVar u w a -> IO (Result u w a) +execMemoNow :: Monoid w => Env u w -> GenHaxl u w a -> IVar u w a -> IO (Result u w a) execMemoNow env cont ivar = do - wlogs <- newIORef NilWrites + wlogs <- newIORef mempty let !menv = env { writeLogsRef = wlogs } -- use an env with empty writes, so we can memoize the extra @@ -262,13 +265,13 @@ execMemoNow env cont ivar = do throwIO e Right (Done a) -> trace_ "execMemoNow: Done" $ do wt <- readIORef wlogs - putIVar ivar (Ok a wt) env - mbModifyWLRef wt (writeLogsRef env) + putIVar ivar (Ok a (Just wt)) env + modifyIORef' (writeLogsRef env) (<> wt) return (Done a) Right (Throw ex) -> trace_ ("execMemoNow: Throw" ++ show ex) $ do wt <- readIORef wlogs - putIVar ivar (ThrowHaxl ex wt) env - mbModifyWLRef wt (writeLogsRef env) + putIVar ivar (ThrowHaxl ex (Just wt)) env + modifyIORef' (writeLogsRef env) (<> wt) return (Throw ex) Right (Blocked ivar' cont) -> trace_ "execMemoNow: Blocked" $ do -- We "block" this memoized computation in the new environment 'menv', so @@ -305,7 +308,7 @@ prepareMemo1 :: MemoVar1 u w a b -> (a -> GenHaxl u w b) -> GenHaxl u w () prepareMemo1 (MemoVar1 r) f = unsafeLiftIO $ writeIORef r (MemoTbl1 f HashMap.empty) -runMemo1 :: (Eq a, Hashable a) => MemoVar1 u w a b -> a -> GenHaxl u w b +runMemo1 :: (Eq a, Hashable a, Monoid w) => MemoVar1 u w a b -> a -> GenHaxl u w 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 @@ -325,7 +328,7 @@ prepareMemo2 :: MemoVar2 u w a b c -> (a -> b -> GenHaxl u w c) -> GenHaxl u w ( prepareMemo2 (MemoVar2 r) f = unsafeLiftIO $ writeIORef r (MemoTbl2 f HashMap.empty) -runMemo2 :: (Eq a, Hashable a, Eq b, Hashable b) +runMemo2 :: (Eq a, Hashable a, Eq b, Hashable b, Monoid w) => MemoVar2 u w a b c -> a -> b -> GenHaxl u w c runMemo2 (MemoVar2 r) k1 k2 = unsafeLiftIO (readIORef r) >>= \case @@ -353,12 +356,12 @@ runMemo2 (MemoVar2 r) k1 k2 = unsafeLiftIO (readIORef r) >>= \case -- every two calls @memo key haxl@, if they have the same @key@ then -- they compute the same result. memo - :: (Typeable a, Typeable k, Hashable k, Eq k) + :: (Typeable a, Typeable k, Hashable k, Eq k, Monoid w) => k -> GenHaxl u w a -> GenHaxl u w a memo key = cachedComputation (MemoKey key) {-# RULES -"memo/Text" memo = memoText :: (Typeable a) => +"memo/Text" memo = memoText :: (Typeable a, Monoid w) => Text -> GenHaxl u w a -> GenHaxl u w a #-} @@ -367,7 +370,7 @@ memo key = cachedComputation (MemoKey key) -- | Memoize a computation using its location and a Fingerprint. This ensures -- uniqueness across computations. memoUnique - :: (Typeable a, Typeable k, Hashable k, Eq k) + :: (Typeable a, Typeable k, Hashable k, Eq k, Monoid w) => MemoFingerprintKey a -> Text -> k -> GenHaxl u w a -> GenHaxl u w a memoUnique fp label key = withLabel label . memo (fp, key) @@ -394,7 +397,7 @@ deriving instance Eq (MemoTextKey a) instance Hashable (MemoTextKey a) where hashWithSalt s (MemoText t) = hashWithSalt s t -memoText :: (Typeable a) => Text -> GenHaxl u w a -> GenHaxl u w a +memoText :: (Typeable a, Monoid w) => Text -> GenHaxl u w a -> GenHaxl u w a memoText key = withLabel key . cachedComputation (MemoText key) -- | A memo key derived from a 128-bit MD5 hash. Do not use this directly, @@ -424,7 +427,7 @@ instance Hashable (MemoFingerprintKey a) where -- {-# NOINLINE memoFingerprint #-} memoFingerprint - :: Typeable a => MemoFingerprintKey a -> GenHaxl u w a -> GenHaxl u w a + :: (Typeable a, Monoid w) => MemoFingerprintKey a -> GenHaxl u w a -> GenHaxl u w a memoFingerprint key@(MemoFingerprintKey _ _ mnPtr nPtr) = withFingerprintLabel mnPtr nPtr . cachedComputation key @@ -436,7 +439,7 @@ memoFingerprint key@(MemoFingerprintKey _ _ mnPtr nPtr) = -- 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. -memoize :: GenHaxl u w a -> GenHaxl u w (GenHaxl u w a) +memoize :: Monoid w => GenHaxl u w a -> GenHaxl u w (GenHaxl u w a) memoize a = runMemo <$> newMemoWith a -- | Transform a 1-argument function returning a Haxl computation into a @@ -455,7 +458,7 @@ memoize a = runMemo <$> newMemoWith a -- -- The above implementation will not invoke the underlying @friendsOf@ -- repeatedly for duplicate values in @ids@. -memoize1 :: (Eq a, Hashable a) +memoize1 :: (Eq a, Hashable a, Monoid w) => (a -> GenHaxl u w b) -> GenHaxl u w (a -> GenHaxl u w b) memoize1 f = runMemo1 <$> newMemoWith1 f @@ -464,7 +467,7 @@ memoize1 f = runMemo1 <$> newMemoWith1 f -- memoized version of itself. -- -- The 2-ary version of @memoize1@, see its documentation for details. -memoize2 :: (Eq a, Hashable a, Eq b, Hashable b) +memoize2 :: (Eq a, Hashable a, Eq b, Hashable b, Monoid w) => (a -> b -> GenHaxl u w c) -> GenHaxl u w (a -> b -> GenHaxl u w c) memoize2 f = runMemo2 <$> newMemoWith2 f diff --git a/Haxl/Core/Monad.hs b/Haxl/Core/Monad.hs index 77b7b8b..23ddda8 100644 --- a/Haxl/Core/Monad.hs +++ b/Haxl/Core/Monad.hs @@ -140,6 +140,7 @@ import qualified Control.Exception as Exception import Data.Either (rights) import Data.IORef import Data.Int +import Data.Maybe import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty #if __GLASGOW_HASKELL__ < 804 @@ -235,12 +236,12 @@ data Env u w = Env -- become non-empty is how the scheduler blocks waiting for -- data fetches to return. - , writeLogsRef :: {-# UNPACK #-} !(IORef (WriteTree w)) + , writeLogsRef :: {-# UNPACK #-} !(IORef w) -- ^ A log of all writes done as part of this haxl computation. Any -- haxl computation that needs to be memoized runs in its own -- environment so that we can get a hold of those writes and put them -- in the IVar associated with the compuatation. - , writeLogsRefNoMemo :: {-# UNPACK #-} !(IORef (WriteTree w)) + , writeLogsRefNoMemo :: {-# UNPACK #-} !(IORef w) -- ^ This is just a specialized version of @writeLogsRef@, where we put -- logs that user doesn't want memoized. This is a better alternative to -- doing arbitrary IO from a (memoized) Haxl computation. @@ -280,7 +281,7 @@ getMaxCallId c = do -- | 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 :: Monoid w => StateStore -> u -> Caches u w -> IO (Env u w) initEnvWithData states e (dcache, mcache) = do newCid <- max <$> (maybe 0 ((+) 1) <$> getMaxCallId dcache) <*> @@ -293,8 +294,8 @@ initEnvWithData states e (dcache, mcache) = do rq <- newIORef JobNil -- RunQueue sr <- newIORef emptyReqCounts -- SubmittedReqs comps <- newTVarIO [] -- completion queue - wl <- newIORef NilWrites - wlnm <- newIORef NilWrites + wl <- newIORef mempty + wlnm <- newIORef mempty return Env { dataCache = dcache , memoCache = mcache @@ -321,14 +322,14 @@ initEnvWithData states e (dcache, mcache) = do } -- | Initializes an environment with 'StateStore' and an input map. -initEnv :: StateStore -> u -> IO (Env u w) +initEnv :: Monoid w => 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 :: Monoid w => u -> IO (Env u w) emptyEnv = initEnv stateEmpty -- | If you're using the env from a failed Haxl computation in a second Haxl @@ -376,6 +377,12 @@ data WriteTree w | MergeWrites (WriteTree w) (WriteTree w) deriving (Show) +instance Semigroup (WriteTree w) where + (<>) = appendWTs + +instance Monoid (WriteTree w) where + mempty = NilWrites + appendWTs :: WriteTree w -> WriteTree w -> WriteTree w appendWTs NilWrites w = w appendWTs w NilWrites = w @@ -431,20 +438,20 @@ mapWriteTree f (MergeWrites wt1 wt2) = newtype GenHaxl u w a = GenHaxl { unHaxl :: Env u w -> IO (Result u w a) } -tellWrite :: w -> GenHaxl u w () +tellWrite :: w -> GenHaxl u (WriteTree w) () tellWrite = write . SomeWrite -write :: WriteTree w -> GenHaxl u w () +write :: Monoid w => w -> GenHaxl u w () write wt = GenHaxl $ \Env{..} -> do - mbModifyWLRef wt writeLogsRef + modifyIORef' writeLogsRef (<> wt) return $ Done () -tellWriteNoMemo :: w -> GenHaxl u w () +tellWriteNoMemo :: w -> GenHaxl u (WriteTree w) () tellWriteNoMemo = writeNoMemo . SomeWrite -writeNoMemo :: WriteTree w -> GenHaxl u w () +writeNoMemo :: Monoid w => w -> GenHaxl u w () writeNoMemo wt = GenHaxl $ \Env{..} -> do - mbModifyWLRef wt writeLogsRefNoMemo + modifyIORef' writeLogsRefNoMemo (<> wt) return $ Done () @@ -557,15 +564,15 @@ getIVarApply i@IVar{ivarRef = !ref} a = GenHaxl $ \env -> do return (Blocked i (Cont (getIVarApply i a))) -- Another specialised version of getIVar, for efficiency in cachedComputation -getIVarWithWrites :: IVar u w a -> GenHaxl u w a +getIVarWithWrites :: Monoid w => IVar u w a -> GenHaxl u w a getIVarWithWrites i@IVar{ivarRef = !ref} = GenHaxl $ \env@Env{..} -> do e <- readIORef ref case e of IVarFull (Ok a wt) -> do - mbModifyWLRef wt writeLogsRef + modifyIORef' writeLogsRef (<> fromMaybe mempty wt) return (Done a) IVarFull (ThrowHaxl e wt) -> do - mbModifyWLRef wt writeLogsRef + modifyIORef' writeLogsRef (<> fromMaybe mempty wt) raiseFromIVar env i e IVarFull (ThrowIO e) -> throwIO e IVarEmpty _ -> @@ -604,8 +611,8 @@ addJobPanic = error "addJob: not empty" -- 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) + = Ok a (Maybe w) + | ThrowHaxl SomeException (Maybe w) | ThrowIO SomeException -- we get no write logs when an IO exception occurs @@ -615,14 +622,14 @@ done env (ThrowHaxl e _) = raise env e done _ (ThrowIO e) = throwIO e eitherToResultThrowIO :: Either SomeException a -> ResultVal a w -eitherToResultThrowIO (Right a) = Ok a NilWrites +eitherToResultThrowIO (Right a) = Ok a Nothing eitherToResultThrowIO (Left e) - | Just HaxlException{} <- fromException e = ThrowHaxl e NilWrites + | Just HaxlException{} <- fromException e = ThrowHaxl e Nothing | otherwise = ThrowIO e eitherToResult :: Either SomeException a -> ResultVal a w -eitherToResult (Right a) = Ok a NilWrites -eitherToResult (Left e) = ThrowHaxl e NilWrites +eitherToResult (Right a) = Ok a Nothing +eitherToResult (Left e) = ThrowHaxl e Nothing -- ----------------------------------------------------------------------------- @@ -905,7 +912,7 @@ nextCallId env = atomicModifyIORef' (callIdRef env) $ \x -> (x+1,x+1) -- Memoization behavior is unchanged, meaning if a memoized computation is run -- once inside @mapWrites@ and then once without, the writes from the second run -- will NOT be transformed. -mapWrites :: (w -> w) -> GenHaxl u w a -> GenHaxl u w a +mapWrites :: (w -> w) -> GenHaxl u (WriteTree w) a -> GenHaxl u (WriteTree w) a mapWrites f action = GenHaxl $ \curEnv -> do wlogs <- newIORef NilWrites wlogsNoMemo <- newIORef NilWrites diff --git a/Haxl/Core/Run.hs b/Haxl/Core/Run.hs index 8938d02..99d8b1f 100644 --- a/Haxl/Core/Run.hs +++ b/Haxl/Core/Run.hs @@ -22,6 +22,7 @@ import Control.Concurrent.STM import Control.Exception as Exception import Control.Monad import Data.IORef +import Data.Maybe import Text.Printf import Unsafe.Coerce @@ -49,10 +50,10 @@ import qualified Data.HashTable.IO as H -- -- However, multiple 'Env's may share a single 'StateStore', and thereby -- use the same set of datasources. -runHaxl:: forall u w a. Env u w -> GenHaxl u w a -> IO a +runHaxl:: forall u w a. Monoid w => Env u w -> GenHaxl u w a -> IO a runHaxl env haxl = fst <$> runHaxlWithWrites env haxl -runHaxlWithWrites :: forall u w a. Env u w -> GenHaxl u w a -> IO (a, [w]) +runHaxlWithWrites :: forall u w a. Monoid w => Env u w -> GenHaxl u w a -> IO (a, w) runHaxlWithWrites env@Env{..} haxl = do result@IVar{ivarRef = resultRef} <- newIVar -- where to put the final result ifTraceLog <- do @@ -105,10 +106,10 @@ runHaxlWithWrites env@Env{..} haxl = do result (ThrowIO e) Right (Done a) -> do wt <- readIORef writeLogsRef - result (Ok a wt) + result $ Ok a (Just wt) Right (Throw ex) -> do wt <- readIORef writeLogsRef - result (ThrowHaxl ex wt) + result $ ThrowHaxl ex (Just wt) Right (Blocked i fn) -> do addJob env (toHaxl fn) ivar i reschedule env rq @@ -237,13 +238,13 @@ runHaxlWithWrites env@Env{..} haxl = do -- schedule env JobNil haxl result r <- readIORef resultRef - writeIORef writeLogsRef NilWrites + writeIORef writeLogsRef mempty wtNoMemo <- atomicModifyIORef' writeLogsRefNoMemo - (\old_wrts -> (NilWrites , old_wrts)) + (\old_wrts -> (mempty, old_wrts)) case r of IVarEmpty _ -> throwIO (CriticalError "runHaxl: missing result") IVarFull (Ok a wt) -> do - return (a, flattenWT (wt `appendWTs` wtNoMemo)) + return (a, fromMaybe mempty wt <> wtNoMemo) IVarFull (ThrowHaxl e _wt) -> throwIO e -- The written logs are discarded when there's a Haxl exception. We -- can change this behavior if we need to get access to partial logs. diff --git a/haxl.cabal b/haxl.cabal index 4036948..9f49e7f 100644 --- a/haxl.cabal +++ b/haxl.cabal @@ -1,5 +1,5 @@ name: haxl -version: 2.4.0.0 +version: 2.5.0.0 synopsis: A Haskell library for efficient, concurrent, and concise data access. homepage: https://github.com/facebook/Haxl diff --git a/tests/CoreTests.hs b/tests/CoreTests.hs index faa1df1..3df3258 100644 --- a/tests/CoreTests.hs +++ b/tests/CoreTests.hs @@ -33,7 +33,7 @@ testEnv = do let st = stateSet exstate stateEmpty -- Create the Env: - initEnv st () + initEnv st () :: IO (Env () ()) useless :: String -> GenHaxl u w Bool useless _ = throw (NotFound "ha ha") @@ -41,7 +41,7 @@ useless _ = throw (NotFound "ha ha") exceptions :: Assertion exceptions = do - en <- emptyEnv () + en <- emptyEnv () :: IO (Env () ()) a <- runHaxl en $ try (useless "input") assertBool "NotFound -> HaxlException" $ isLeft (a :: Either HaxlException Bool) @@ -132,7 +132,7 @@ exceptions = -- makes the compiler happy. base :: (Exception a) => a -> IO HaxlException base e = do - en <- emptyEnv () + en <- emptyEnv () :: IO (Env () ()) runHaxl en $ throw e `catch` \x -> return x printing :: Assertion @@ -154,21 +154,21 @@ printing = do withEnvTest :: Test withEnvTest = TestLabel "withEnvTest" $ TestCase $ do exstate <- ExampleDataSource.initGlobalState - e <- initEnv (stateSet exstate stateEmpty) False + e <- initEnv (stateSet exstate stateEmpty) False :: IO (Env Bool ()) b <- runHaxl e $ withEnv e { userEnv = True } $ env userEnv assertBool "withEnv1" b - e <- initEnv (stateSet exstate stateEmpty) False + e <- initEnv (stateSet exstate stateEmpty) False :: IO (Env Bool ()) b <- runHaxl e $ withEnv e { userEnv = True } $ do _ <- countAardvarks "aaa" env userEnv assertBool "withEnv2" b - e <- initEnv (stateSet exstate stateEmpty) False + e <- initEnv (stateSet exstate stateEmpty) False :: IO (Env Bool ()) b <- runHaxl e $ withEnv e { userEnv = True } $ do memo ("xxx" :: Text) $ do _ <- countAardvarks "aaa" env userEnv assertBool "withEnv3" b - e <- initEnv (stateSet exstate stateEmpty) False + e <- initEnv (stateSet exstate stateEmpty) False :: IO (Env Bool ()) b <- runHaxl e $ withEnv e { userEnv = True } $ do memo ("yyy" :: Text) $ do diff --git a/tests/DataCacheTest.hs b/tests/DataCacheTest.hs index 8fee771..6b4d8fb 100644 --- a/tests/DataCacheTest.hs +++ b/tests/DataCacheTest.hs @@ -55,8 +55,8 @@ instance Hashable (CacheableReq x) where hashWithSalt s (CacheableInt val) = hashWithSalt s (0::Int, val) -newResult :: a -> IO (IVar u w a) -newResult a = newFullIVar (Ok a NilWrites) +newResult :: Monoid w => a -> IO (IVar u w a) +newResult a = newFullIVar (Ok a mempty) takeResult :: IVar u w a -> IO (ResultVal a w) takeResult IVar{ivarRef = ref} = do @@ -85,13 +85,13 @@ dcSoundnessTest = TestLabel "DataCache soundness" $ TestCase $ do r <- mapM takeResult =<< DataCache.lookup (Req 1) cache assertBool "dcSoundness2" $ case r :: Maybe (ResultVal Int ()) of - Just (Ok 1 NilWrites) -> True + Just (Ok 1 Nothing) -> True _something_else -> False r <- mapM takeResult =<< DataCache.lookup (Req 2) cache assertBool "dcSoundness3" $ case r :: Maybe (ResultVal String ()) of - Just (Ok "hello" NilWrites) -> True + Just (Ok "hello" Nothing) -> True _something_else -> False r <- mapM takeResult =<< DataCache.lookup (Req 2) cache @@ -103,7 +103,7 @@ dcSoundnessTest = TestLabel "DataCache soundness" $ TestCase $ do dcStrictnessTest :: Test dcStrictnessTest = TestLabel "DataCache strictness" $ TestCase $ do - env <- initEnv stateEmpty () + env <- initEnv stateEmpty () :: IO (Env () ()) r <- Control.Exception.try $ runHaxl env $ cachedComputation (Req (error "BOOM")) $ return "OK" assertBool "dcStrictnessTest" $ @@ -145,7 +145,7 @@ dcFallbackTest = TestLabel "DataCache fallback" $ TestList case rbad of Left (NotFound _) -> True _ -> False - assertEqual "write side effects happen" [7] writes + assertEqual "write side effects happen" [7] (flattenWT writes) ] where @@ -159,14 +159,14 @@ dcFallbackTest = TestLabel "DataCache fallback" $ TestList ] return c - addLookup :: Env () Int -> Env () Int + addLookup :: Env () (WriteTree Int) -> Env () (WriteTree Int) addLookup e = e { dataCacheFetchFallback = Just (DataCacheLookup lookup) , flags = (flags e) { report = profilingReportFlags } } lookup :: forall req a . Typeable (req a) => req a - -> IO (Maybe (ResultVal a Int)) + -> IO (Maybe (ResultVal a (WriteTree Int))) lookup r | typeOf r == typeRep (Proxy :: Proxy (TestReq Int)) = -- have to coerce on the way out as results are not Typeable @@ -176,12 +176,12 @@ dcFallbackTest = TestLabel "DataCache fallback" $ TestList return $ unsafeCoerce . doCache <$> cast r | otherwise = return Nothing - doReq :: TestReq Int -> ResultVal Int Int - doReq (Req 999) = ThrowHaxl (toException $ NotFound empty) NilWrites - doReq (Req r) = Ok r NilWrites + doReq :: TestReq Int -> ResultVal Int (WriteTree Int) + doReq (Req 999) = ThrowHaxl (toException $ NotFound empty) Nothing + doReq (Req r) = Ok r Nothing - doCache :: CacheableReq Int -> ResultVal Int Int - doCache (CacheableInt i) = Ok i NilWrites + doCache :: CacheableReq Int -> ResultVal Int (WriteTree Int) + doCache (CacheableInt i) = Ok i Nothing req :: TestReq Int req = Req 1 diff --git a/tests/FullyAsyncTest.hs b/tests/FullyAsyncTest.hs index 373f1d6..47732e4 100644 --- a/tests/FullyAsyncTest.hs +++ b/tests/FullyAsyncTest.hs @@ -20,6 +20,7 @@ import Haxl.Core.Monad (unsafeLiftIO) tests :: Test tests = sleepTest +testEnv :: IO (Env () ()) testEnv = do st <- mkConcurrentIOState env <- initEnv (stateSet st stateEmpty) () diff --git a/tests/MemoizationTests.hs b/tests/MemoizationTests.hs index 8651fe0..384f51d 100644 --- a/tests/MemoizationTests.hs +++ b/tests/MemoizationTests.hs @@ -19,7 +19,7 @@ memoSoundness :: Test memoSoundness = TestCase $ do iEnv <- do exState <- ExampleDataSource.initGlobalState - initEnv (stateSet exState stateEmpty) () + initEnv (stateSet exState stateEmpty) () :: IO (Env () ()) unMemoizedWombats <- runHaxl iEnv $ listWombats 100 diff --git a/tests/MonadAsyncTest.hs b/tests/MonadAsyncTest.hs index 5d142b2..910fa0b 100644 --- a/tests/MonadAsyncTest.hs +++ b/tests/MonadAsyncTest.hs @@ -19,7 +19,7 @@ import Test.HUnit hiding (State) import Control.Concurrent import Control.Exception as Exception import Control.Monad -import Haxl.Core.Monad (unsafeLiftIO) +import Haxl.Core.Monad (unsafeLiftIO, WriteTree) import System.IO.Unsafe import Data.Hashable import Data.IORef @@ -86,7 +86,7 @@ tests = TestList [ TestLabel "exceptionTest" exceptionTest ] -mkTestEnv :: IO (Env () SimpleWrite) +mkTestEnv :: IO (Env () (WriteTree SimpleWrite)) mkTestEnv = do st <- initDataSource initEnv (stateSet st stateEmpty) () diff --git a/tests/MonadBench.hs b/tests/MonadBench.hs index ad619c7..92fb313 100644 --- a/tests/MonadBench.hs +++ b/tests/MonadBench.hs @@ -22,20 +22,21 @@ import Haxl.Prelude as Haxl import Prelude() import Haxl.Core +import Haxl.Core.Monad (WriteTree) import Haxl.Core.Util import ExampleDataSource newtype SimpleWrite = SimpleWrite Text deriving (Eq, Show) -testEnv :: ReportFlags -> IO (Env () SimpleWrite) +testEnv :: ReportFlags -> IO (Env () (WriteTree SimpleWrite)) testEnv report = do exstate <- ExampleDataSource.initGlobalState let st = stateSet exstate stateEmpty env <- initEnv st () return env { flags = (flags env) { report = report } } -type Test = (String, Int, Int -> GenHaxl () SimpleWrite ()) +type Test = (String, Int, Int -> GenHaxl () (WriteTree SimpleWrite) ()) testName :: Test -> String testName (t,_,_) = t @@ -151,19 +152,20 @@ main = do tree :: Int - -> (Int -> GenHaxl () SimpleWrite [Id] -> GenHaxl () SimpleWrite [Id]) - -> GenHaxl () SimpleWrite [Id] + -> (Int -> GenHaxl () (WriteTree SimpleWrite) [Id] + -> GenHaxl () (WriteTree SimpleWrite) [Id]) + -> GenHaxl () (WriteTree SimpleWrite) [Id] tree 0 wrap = wrap 0 $ listWombats 0 tree n wrap = wrap n $ concat <$> Haxl.sequence [ tree (n-1) wrap , listWombats (fromIntegral n), tree (n-1) wrap ] -unionWombats :: GenHaxl () SimpleWrite [Id] +unionWombats :: GenHaxl () (WriteTree SimpleWrite) [Id] unionWombats = foldl List.union [] <$> Haxl.mapM listWombats [1..1000] -unionWombatsTo :: Id -> GenHaxl () SimpleWrite [Id] +unionWombatsTo :: Id -> GenHaxl () (WriteTree SimpleWrite) [Id] unionWombatsTo x = foldl List.union [] <$> Haxl.mapM listWombats [1..x] -unionWombatsFromTo :: Id -> Id -> GenHaxl () SimpleWrite [Id] +unionWombatsFromTo :: Id -> Id -> GenHaxl () (WriteTree SimpleWrite) [Id] unionWombatsFromTo x y = foldl List.union [] <$> Haxl.mapM listWombats [x..y] diff --git a/tests/ParallelTests.hs b/tests/ParallelTests.hs index 58b0708..ff6a679 100644 --- a/tests/ParallelTests.hs +++ b/tests/ParallelTests.hs @@ -10,6 +10,7 @@ import Data.Time.Clock import Test.HUnit +testEnv :: IO (Env () ()) testEnv = do sleepState <- mkConcurrentIOState let st = stateSet sleepState stateEmpty diff --git a/tests/ProfileTests.hs b/tests/ProfileTests.hs index ae44fa3..3342f36 100644 --- a/tests/ProfileTests.hs +++ b/tests/ProfileTests.hs @@ -31,10 +31,12 @@ import qualified Data.HashMap.Strict as KeyMap #endif import Data.Int +import TestTypes import TestUtils import WorkDataSource import SleepDataSource +mkProfilingEnv :: IO HaxlEnv mkProfilingEnv = do env <- makeTestEnv False return env { flags = (flags env) { report = profilingReportFlags } } @@ -137,7 +139,7 @@ exceptions = do -- for correct accounting when relying on allocation limits. threadAlloc :: Integer -> Assertion threadAlloc batches = do - env' <- initEnv (stateSet mkWorkState stateEmpty) () + env' <- initEnv (stateSet mkWorkState stateEmpty) () :: IO (Env () ()) let env = env' { flags = (flags env') { report = setReportFlag ReportFetchStats defaultReportFlags } } a0 <- getAllocationCounter diff --git a/tests/StatsTests.hs b/tests/StatsTests.hs index 44a3929..c9ce33d 100644 --- a/tests/StatsTests.hs +++ b/tests/StatsTests.hs @@ -66,7 +66,7 @@ aggregateBatches = TestCase $ do assertEqual "Grouping works as expected" expectedResultInterspersed aggInterspersedBatch - +testEnv :: IO (Env () ()) testEnv = do -- To use a data source, we need to initialize its state: exstate <- ExampleDataSource.initGlobalState diff --git a/tests/TestExampleDataSource.hs b/tests/TestExampleDataSource.hs index fc46ccd..58caaf0 100644 --- a/tests/TestExampleDataSource.hs +++ b/tests/TestExampleDataSource.hs @@ -24,6 +24,7 @@ import System.FilePath import ExampleDataSource import LoadCache +testEnv :: IO (Env () ()) testEnv = do -- To use a data source, we need to initialize its state: exstate <- ExampleDataSource.initGlobalState diff --git a/tests/WriteTests.hs b/tests/WriteTests.hs index dd7179e..c9279cd 100644 --- a/tests/WriteTests.hs +++ b/tests/WriteTests.hs @@ -15,6 +15,7 @@ module WriteTests (tests) where import Test.HUnit +import Control.Arrow import Control.Concurrent import Data.Either import Data.Foldable @@ -22,7 +23,7 @@ import Data.Hashable import Data.IORef import qualified Data.Text as Text -import Haxl.Core.Monad (mapWrites, flattenWT) +import Haxl.Core.Monad (mapWrites, flattenWT, WriteTree) import Haxl.Core import Haxl.Prelude as Haxl @@ -58,12 +59,12 @@ assertEqualIgnoreOrder :: assertEqualIgnoreOrder msg lhs rhs = assertEqual msg (sort lhs) (sort rhs) -doInnerWrite :: GenHaxl u SimpleWrite Int +doInnerWrite :: GenHaxl u (WriteTree SimpleWrite) Int doInnerWrite = do tellWrite $ SimpleWrite "inner" return 0 -doOuterWrite :: GenHaxl u SimpleWrite Int +doOuterWrite :: GenHaxl u (WriteTree SimpleWrite) Int doOuterWrite = do tellWrite $ SimpleWrite "outer1" @@ -76,19 +77,22 @@ doOuterWrite = do return 1 -doNonMemoWrites :: GenHaxl u SimpleWrite Int +doNonMemoWrites :: GenHaxl u (WriteTree SimpleWrite) Int doNonMemoWrites = do tellWrite $ SimpleWrite "inner" tellWriteNoMemo $ SimpleWrite "inner not memo" return 0 +runHaxlWithWriteList :: Env u (WriteTree w) -> GenHaxl u (WriteTree w) a -> IO (a, [w]) +runHaxlWithWriteList env haxl = second flattenWT <$> runHaxlWithWrites env haxl + writeSoundness :: Test writeSoundness = TestCase $ do let numReps = 4 -- do writes without memoization env1 <- emptyEnv () - (allRes, allWrites) <- runHaxlWithWrites env1 $ + (allRes, allWrites) <- runHaxlWithWriteList env1 $ Haxl.sequence (replicate numReps doInnerWrite) assertBool "Write Soundness 1" $ @@ -98,7 +102,7 @@ writeSoundness = TestCase $ do -- do writes with memoization env2 <- emptyEnv () - (memoRes, memoWrites) <- runHaxlWithWrites env2 $ do + (memoRes, memoWrites) <- runHaxlWithWriteList env2 $ do doWriteMemo <- newMemoWith doInnerWrite let memoizedWrite = runMemo doWriteMemo @@ -111,7 +115,7 @@ writeSoundness = TestCase $ do -- do writes with interleaved memo env3 <- emptyEnv () - (ilRes, ilWrites) <- runHaxlWithWrites env3 $ do + (ilRes, ilWrites) <- runHaxlWithWriteList env3 $ do doWriteMemo <- newMemoWith doInnerWrite let memoizedWrite = runMemo doWriteMemo @@ -124,7 +128,7 @@ writeSoundness = TestCase $ do -- do writes with nested memo env4 <- emptyEnv () - (nestRes, nestWrites) <- runHaxlWithWrites env4 $ do + (nestRes, nestWrites) <- runHaxlWithWriteList env4 $ do doWriteMemo' <- newMemoWith doOuterWrite let memoizedWrite' = runMemo doWriteMemo' @@ -142,7 +146,7 @@ writeSoundness = TestCase $ do -- do both kinds of writes without memoization env5 <- emptyEnv () - (allRes, allWrites) <- runHaxlWithWrites env5 $ + (allRes, allWrites) <- runHaxlWithWriteList env5 $ Haxl.sequence (replicate numReps doNonMemoWrites) assertBool "Write Soundness 9" $ @@ -153,7 +157,7 @@ writeSoundness = TestCase $ do -- do both kinds of writes with memoization env6 <- emptyEnv () - (memoRes, memoWrites) <- runHaxlWithWrites env6 $ do + (memoRes, memoWrites) <- runHaxlWithWriteList env6 $ do doWriteMemo <- newMemoWith doNonMemoWrites let memoizedWrite = runMemo doWriteMemo @@ -168,7 +172,7 @@ writeSoundness = TestCase $ do writeLogsCorrectnessTest :: Test writeLogsCorrectnessTest = TestLabel "writeLogs_correctness" $ TestCase $ do e <- emptyEnv () - (_ , wrts) <- runHaxlWithWrites e doNonMemoWrites + (_ , wrts) <- runHaxlWithWriteList e doNonMemoWrites assertEqualIgnoreOrder "Expected writes" [SimpleWrite "inner", SimpleWrite "inner not memo"] wrts wrtsNoMemo <- readIORef $ writeLogsRefNoMemo e @@ -180,14 +184,14 @@ mapWritesTest :: Test mapWritesTest = TestLabel "mapWrites" $ TestCase $ do let func (SimpleWrite s) = SimpleWrite $ Text.toUpper s env0 <- emptyEnv () - (res0, wrts0) <- runHaxlWithWrites env0 $ mapWrites func doNonMemoWrites + (res0, wrts0) <- runHaxlWithWriteList env0 $ mapWrites func doNonMemoWrites assertEqual "Expected computation result" 0 res0 assertEqualIgnoreOrder "Writes correctly transformed" [SimpleWrite "INNER", SimpleWrite "INNER NOT MEMO"] wrts0 -- Writes should behave the same inside and outside mapWrites env1 <- emptyEnv () - (res1, wrts1) <- runHaxlWithWrites env1 $ do + (res1, wrts1) <- runHaxlWithWriteList env1 $ do outer <- doOuterWrite outerMapped <- mapWrites func doOuterWrite return $ outer == outerMapped @@ -203,7 +207,7 @@ mapWritesTest = TestLabel "mapWrites" $ TestCase $ do -- Memoization behaviour should be unaffected env2 <- emptyEnv () - (_res2, wrts2) <- runHaxlWithWrites env2 $ do + (_res2, wrts2) <- runHaxlWithWriteList env2 $ do writeMemo <- newMemoWith doNonMemoWrites let doWriteMemo = runMemo writeMemo _ <- mapWrites func doWriteMemo @@ -220,7 +224,7 @@ mapWritesTest = TestLabel "mapWrites" $ TestCase $ do -- Same as previous, but the non-mapped computation is run first env3 <- emptyEnv () - (_res3, wrts3) <- runHaxlWithWrites env3 $ do + (_res3, wrts3) <- runHaxlWithWriteList env3 $ do writeMemo <- newMemoWith doNonMemoWrites let doWriteMemo = runMemo writeMemo _ <- doWriteMemo @@ -237,14 +241,14 @@ mapWritesTest = TestLabel "mapWrites" $ TestCase $ do -- inner computation performs no writes env4 <- emptyEnv () - (res4, wrts4) <- runHaxlWithWrites env4 $ + (res4, wrts4) <- runHaxlWithWriteList env4 $ mapWrites func (return (0 :: Int)) assertEqual "No Writes: Expected computation result" 0 res4 assertEqualIgnoreOrder "No writes" [] wrts4 -- inner computation throws an exception env5 <- emptyEnv () - (res5, wrts5) <- runHaxlWithWrites env5 $ mapWrites func $ try $ do + (res5, wrts5) <- runHaxlWithWriteList env5 $ mapWrites func $ try $ do _ <- doNonMemoWrites _ <- throw (NotFound "exception") return 0 @@ -259,7 +263,7 @@ mapWritesTest = TestLabel "mapWrites" $ TestCase $ do -- inner computation calls a datasource env6 <- initEnv (stateSet DSState stateEmpty) () - (res6, wrts6) <- runHaxlWithWrites env6 $ mapWrites func $ do + (res6, wrts6) <- runHaxlWithWriteList env6 $ mapWrites func $ do _ <- doNonMemoWrites dataFetch GetNumber @@ -273,7 +277,7 @@ mapWritesTest = TestLabel "mapWrites" $ TestCase $ do -- inner computation calls a datasource, flipped calls env7 <- initEnv (stateSet DSState stateEmpty) () - (res7, wrts7) <- runHaxlWithWrites env7 $ mapWrites func $ do + (res7, wrts7) <- runHaxlWithWriteList env7 $ mapWrites func $ do df <- dataFetch GetNumber _ <- doNonMemoWrites return df