th-lock-0.0.4: src/Language/Haskell/TH/Lock.hs
-- | Serial Haskell module compilation
module Language.Haskell.TH.Lock
( ensureSerialCompilation
, ensureSerialCompilationQuietly
, ensureSerialCompilationVerbose
) where
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar, tryPutMVar, tryReadMVar, MVar)
import Control.Monad (void)
import Language.Haskell.TH.Syntax
import Prelude hiding (log)
import System.IO.Unsafe (unsafePerformIO)
-- | 'MVar' holds a Haskell module name expecting sequentian
-- compilation and which is currently goes through the compilation
-- process.
moduleCompilationLock :: MVar String
moduleCompilationLock = unsafePerformIO (newEmptyMVar)
-- | Call this function right after import section to prevent
-- concurrent TH code exection for modules with mutable compile time
-- shared state. The function acquires a lock in the scope of GHC
-- process and the lock is released once type checker completes module
-- verification.
ensureSerialCompilation :: (String -> IO ()) -> Q [Dec]
ensureSerialCompilation log = do
m <- loc_module <$> location
go moduleCompilationLock m
addModFinalizer (goAway moduleCompilationLock m)
pure []
where
goAway l m = do
runIO (tryReadMVar l) >>= \case
Just holdingModule ->
if holdingModule == m
then runIO $ do
log $ "Module [" <> m <> "] released lock"
void $ takeMVar l
else
reportError $
"Module [" <> m <> "] attempted to release lock of TH MVar holding by [" <>
holdingModule <> "]"
Nothing ->
reportError $ "Module [" <> m <> "] attempted to release unlocked TH MVar"
go l m = runIO (goIo l m)
goIo l m =
tryPutMVar l m >>= \case
True -> do
log $ "Module [" <> m <> "] acquired TH lock"
False -> tryReadMVar l >>= \case
Just holdingModule -> do
log $ "Module [" <> m <> "] is waiting for [" <> holdingModule <> "]"
putMVar l m
log $ "Module [" <> m <> "] acquired TH lock"
Nothing -> do
log $ "Module [" <> m <> "] retries to acquire TH lock"
goIo l m
-- | 'ensureSerialCompilation' without lock logging
ensureSerialCompilationQuietly :: Q [Dec]
ensureSerialCompilationQuietly = ensureSerialCompilation (void . pure)
-- | 'ensureSerialCompilation' with lock logging
ensureSerialCompilationVerbose :: Q [Dec]
ensureSerialCompilationVerbose = ensureSerialCompilation putStrLn