packages feed

th-lock (empty) → 0.0.4

raw patch · 20 files changed

+399/−0 lines, 20 filesdep +basedep +tastydep +tasty-discover

Dependencies added: base, tasty, tasty-discover, tasty-hunit, tasty-quickcheck, template-haskell, th-lock

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2025 Daniil Iaitskov+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Edward Kmett nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ changelog.md view
@@ -0,0 +1,3 @@+# th-lock changelog++## Version 1.0.0 2025-03-14
+ src/Language/Haskell/TH/Lock.hs view
@@ -0,0 +1,66 @@+-- | 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
+ test/Discovery.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --generated-module=Discovery #-}
+ test/Driver.hs view
@@ -0,0 +1,12 @@+module Driver where++import qualified Discovery+import Test.Tasty++main :: IO ()+main = defaultMain =<< testTree+  where+    testTree :: IO TestTree+    testTree = do+      tests <- Discovery.tests+      pure $ testGroup "haddock-use-refs" [ tests ]
+ test/Tests/Language/Haskell/TH/Lock/ModuleA.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleA where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "A"++unit_check_state :: IO ()+unit_check_state = "A" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleB.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleB where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "B"++unit_check_state :: IO ()+unit_check_state = "B" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleC.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleC where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "C"++unit_check_state :: IO ()+unit_check_state = "C" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleD.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleD where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "D"++unit_check_state :: IO ()+unit_check_state = "D" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleE.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleE where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "E"++unit_check_state :: IO ()+unit_check_state = "E" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleF.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleF where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "F"++unit_check_state :: IO ()+unit_check_state = "F" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleG.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleG where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "G"++unit_check_state :: IO ()+unit_check_state = "G" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleH.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleH where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "H"++unit_check_state :: IO ()+unit_check_state = "H" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleI.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleI where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "I"++unit_check_state :: IO ()+unit_check_state = "I" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleJ.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleJ where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "J"++unit_check_state :: IO ()+unit_check_state = "J" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleX.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleX where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "X"++unit_check_state :: IO ()+unit_check_state = "X" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleY.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleY where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "Y"++unit_check_state :: IO ()+unit_check_state = "Y" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/ModuleZ.hs view
@@ -0,0 +1,12 @@+module Tests.Language.Haskell.TH.Lock.ModuleZ where++import Language.Haskell.TH.Lock+import Test.Tasty.HUnit ((@=?))+import Tests.Language.Haskell.TH.Lock.State++ensureSerialCompilationVerbose++set "Z"++unit_check_state :: IO ()+unit_check_state = "Z" @=? $(get)
+ test/Tests/Language/Haskell/TH/Lock/State.hs view
@@ -0,0 +1,15 @@+module Tests.Language.Haskell.TH.Lock.State where++import Data.IORef+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import System.IO.Unsafe++state :: IORef String+state = unsafePerformIO (newIORef "")++set :: String -> Q [Dec]+set s = runIO (atomicWriteIORef state s) >> pure []++get :: Q Exp+get = lift =<< runIO (readIORef state)
+ th-lock.cabal view
@@ -0,0 +1,116 @@+cabal-version:   3.0+name:            th-lock+version:         0.0.4+license:         BSD-3-Clause+license-file:    LICENSE+category:        Development+author:          Daniil Iaitskov <dyaitskov@gmail.com>+maintainer:      Daniil Iaitskov <dyaitskov@gmail.com>+stability:       experimental+synopsis:        Serialize compilation of modules with TH code modifing shared state+homepage:        https://github.com/yaitskov/th-lock+bug-reports:     https://github.com/yaitskov/th-lock/issues+build-type:      Simple+description:+    Recent GHC versions with option @-j\<N>@ where @N@ greater than 1 can+    build modules concurrently. Usually it is cool thing, because build time+    drops, but there is a drawback in form of problems with TH code+    maintaining a shared state.+    +    I encountered such issue while running tests for+    <https://hackage.haskell.org/package/trace-embrace trace-embrace>+    package. Package TH code has a shared state (immutable configuration+    file loaded once), which is not supposed to change after loading, but+    tests require to cover all cases\/branches and achieving this in the+    scope of a single cabal test-suit leads to inconsistencies.+    +    Spreading tests among multiple test suits cause code duplication and+    looks less elegant.+    +    Compilation of conflicting modules can be serialized with+    <https://hackage.haskell.org/package/th-lock/docs/Language-Haskell-TH-Lock.html#v:ensureSerialCompilationVerbose ensureSerialCompilationVerbose>.+    +    > {-# LANGUAGE TemplateHaskell #-}+    > module ModuleA where+    >+    > import Language.Haskell.TH.Lock+    >+    > ensureSerialCompilationVerbose+    +    ________________________________________________________________________+    +    > {-# LANGUAGE TemplateHaskell #-}+    > module ModuleB where+    >+    > import Language.Haskell.TH.Lock+    >+    > ensureSerialCompilationVerbose++tested-with:+  GHC == 9.10.1+extra-doc-files:+  changelog.md+library+  build-depends:+    base < 5,+    template-haskell < 2.24.0.0,++  exposed-modules:+    Language.Haskell.TH.Lock++  ghc-options: -Wall+  hs-source-dirs: src+  default-language: Haskell2010+  default-extensions:+    FlexibleContexts+    FlexibleInstances+    ImportQualifiedPost+    LambdaCase+    TemplateHaskell++test-suite test+  type: exitcode-stdio-1.0+  main-is: Driver.hs+  other-modules:+    Discovery+    Paths_th_lock+    Tests.Language.Haskell.TH.Lock.State+    Tests.Language.Haskell.TH.Lock.ModuleA+    Tests.Language.Haskell.TH.Lock.ModuleB+    Tests.Language.Haskell.TH.Lock.ModuleC+    Tests.Language.Haskell.TH.Lock.ModuleD+    Tests.Language.Haskell.TH.Lock.ModuleE+    Tests.Language.Haskell.TH.Lock.ModuleF+    Tests.Language.Haskell.TH.Lock.ModuleG+    Tests.Language.Haskell.TH.Lock.ModuleH+    Tests.Language.Haskell.TH.Lock.ModuleI+    Tests.Language.Haskell.TH.Lock.ModuleJ+    Tests.Language.Haskell.TH.Lock.ModuleX+    Tests.Language.Haskell.TH.Lock.ModuleY+    Tests.Language.Haskell.TH.Lock.ModuleZ+  autogen-modules:+    Paths_th_lock+  hs-source-dirs:+    test+  default-extensions:+    FlexibleContexts+    ImportQualifiedPost+    LambdaCase+    OverloadedLabels+    RecordWildCards+    ScopedTypeVariables+    TemplateHaskell+  ghc-options: -Wall -rtsopts -threaded -main-is Driver+  build-depends:+      base+    , tasty+    , tasty-discover+    , tasty-hunit+    , tasty-quickcheck+    , template-haskell+    , th-lock+  default-language: Haskell2010++source-repository head+  type:     git+  location: https://github.com/yaitskov/th-lock.git