Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve async runtime scaling #946

Merged
merged 18 commits into from
Aug 23, 2023
Merged
Prev Previous commit
Next Next commit
Make logger more testable
  • Loading branch information
jaspervdj committed Aug 12, 2022
commit 5c70f28643c718368a1eafb3d10f801efd5a7ede
78 changes: 41 additions & 37 deletions lib/Hakyll/Core/Logger.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
--------------------------------------------------------------------------------
-- | Produce pretty, thread-safe logs
{-# LANGUAGE Rank2Types #-}
module Hakyll.Core.Logger
( Verbosity (..)
, Logger
Expand All @@ -9,15 +10,19 @@ module Hakyll.Core.Logger
, header
, message
, debug

-- * Testing utilities
, newInMem
) where


--------------------------------------------------------------------------------
import Control.Concurrent (forkIO)
import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
import Control.Monad (forever)
import Control.Concurrent.Chan (newChan, readChan, writeChan)
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
import Control.Monad (forever, when)
import Control.Monad.Trans (MonadIO, liftIO)
import qualified Data.IORef as IORef
import Data.List (intercalate)
import Prelude hiding (error)

Expand All @@ -31,51 +36,34 @@ data Verbosity


--------------------------------------------------------------------------------
-- | Logger structure. Very complicated.
data Logger = Logger
{ loggerChan :: Chan (Maybe String) -- ^ Nothing marks the end
, loggerSync :: MVar () -- ^ Used for sync on quit
, loggerSink :: String -> IO () -- ^ Out sink
, loggerVerbosity :: Verbosity -- ^ Verbosity
{ -- | Flush the logger (blocks until flushed)
flush :: forall m. MonadIO m => m ()
, string :: forall m. MonadIO m => Verbosity -> String -> m ()
}


--------------------------------------------------------------------------------
-- | Create a new logger
new :: Verbosity -> IO Logger
new vbty = do
logger <- Logger <$>
newChan <*> newEmptyMVar <*> pure putStrLn <*> pure vbty
_ <- forkIO $ loggerThread logger
return logger
where
loggerThread logger = forever $ do
msg <- readChan $ loggerChan logger
chan <- newChan
sync <- newEmptyMVar
_ <- forkIO $ forever $ do
msg <- readChan chan
case msg of
-- Stop: sync
Nothing -> putMVar (loggerSync logger) ()
Nothing -> putMVar sync ()
-- Print and continue
Just m -> loggerSink logger m


--------------------------------------------------------------------------------
-- | Flush the logger (blocks until flushed)
flush :: Logger -> IO ()
flush logger = do
writeChan (loggerChan logger) Nothing
() <- takeMVar $ loggerSync logger
return ()


--------------------------------------------------------------------------------
string :: MonadIO m
=> Logger -- ^ Logger
-> Verbosity -- ^ Verbosity of the string
-> String -- ^ Section name
-> m () -- ^ No result
string l v m
| loggerVerbosity l >= v = liftIO $ writeChan (loggerChan l) (Just m)
| otherwise = return ()
Just m -> putStrLn m
return $ Logger
{ flush = liftIO $ do
writeChan chan Nothing
() <- takeMVar sync
return ()
, string = \v m -> when (vbty >= v) $
liftIO $ writeChan chan (Just m)
}


--------------------------------------------------------------------------------
Expand All @@ -101,3 +89,19 @@ debug l m = string l Debug $ " [DEBUG] " ++ indent m
--------------------------------------------------------------------------------
indent :: String -> String
indent = intercalate "\n " . lines


--------------------------------------------------------------------------------
-- | Create a new logger that just stores all the messages, useful for writing
-- tests.
newInMem :: IO (Logger, IO [(Verbosity, String)])
newInMem = do
ref <- IORef.newIORef []
pure
( Logger
{ string = \vbty msg -> liftIO $ IORef.atomicModifyIORef' ref $
\msgs -> ((vbty, msg) : msgs, ())
, flush = pure ()
}
, reverse <$> IORef.readIORef ref
)
6 changes: 3 additions & 3 deletions tests/Hakyll/Core/Runtime/Tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ case02 = do
-- Test that dependency cycles are correctly identified
case03 :: Assertion
case03 = do
logger <- Logger.new Logger.Error
(logger, inMemLog) <- Logger.newInMem
(ec, _) <- run RunModeNormal testConfiguration logger $ do

create ["partial.html.out1"] $ do
Expand All @@ -119,8 +119,8 @@ case03 = do
makeItem example
>>= loadAndApplyTemplate "partial.html" defaultContext


ec @?= ExitFailure 1
inMemLog >>= print

cleanTestEnv

Expand Down Expand Up @@ -221,4 +221,4 @@ case06 = do

ec @?= ExitSuccess

cleanTestEnv
cleanTestEnv