Skip to content

Commit 43d81cb

Browse files
committed
Make the cache thread-safe
1 parent e87e034 commit 43d81cb

File tree

3 files changed

+29
-19
lines changed

3 files changed

+29
-19
lines changed

src/Ormolu/Utils/Cabal.hs

+4-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ where
1414
import Control.Exception
1515
import Control.Monad.IO.Class
1616
import Data.ByteString qualified as B
17-
import Data.IORef
1817
import Data.Map.Lazy (Map)
1918
import Data.Map.Lazy qualified as M
2019
import Data.Maybe (maybeToList)
@@ -29,7 +28,7 @@ import Language.Haskell.Extension
2928
import Ormolu.Config
3029
import Ormolu.Exception
3130
import Ormolu.Fixity
32-
import Ormolu.Utils.IO (findClosestFileSatisfying, withIORefCache)
31+
import Ormolu.Utils.IO (Cache, findClosestFileSatisfying, newCache, withCache)
3332
import System.Directory
3433
import System.FilePath
3534
import System.IO.Unsafe (unsafePerformIO)
@@ -101,8 +100,8 @@ data CachedCabalFile = CachedCabalFile
101100
deriving (Show)
102101

103102
-- | Cache ref that stores 'CachedCabalFile' per Cabal file.
104-
cacheRef :: IORef (Map FilePath CachedCabalFile)
105-
cacheRef = unsafePerformIO $ newIORef M.empty
103+
cacheRef :: Cache FilePath CachedCabalFile
104+
cacheRef = unsafePerformIO newCache
106105
{-# NOINLINE cacheRef #-}
107106

108107
-- | Parse 'CabalInfo' from a @.cabal@ file at the given 'FilePath'.
@@ -118,7 +117,7 @@ parseCabalInfo ::
118117
parseCabalInfo cabalFileAsGiven sourceFileAsGiven = liftIO $ do
119118
cabalFile <- makeAbsolute cabalFileAsGiven
120119
sourceFileAbs <- makeAbsolute sourceFileAsGiven
121-
CachedCabalFile {..} <- withIORefCache cacheRef cabalFile $ do
120+
CachedCabalFile {..} <- withCache cacheRef cabalFile $ do
122121
cabalFileBs <- B.readFile cabalFile
123122
genericPackageDescription <-
124123
whenLeft (snd . runParseResult $ parseGenericPackageDescription cabalFileBs) $

src/Ormolu/Utils/Fixity.hs

+4-7
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,15 @@ where
1010
import Control.Exception (throwIO)
1111
import Control.Monad.IO.Class
1212
import Data.Bifunctor (first)
13-
import Data.IORef
1413
import Data.List.NonEmpty (NonEmpty)
15-
import Data.Map.Strict (Map)
16-
import Data.Map.Strict qualified as Map
1714
import Data.Text qualified as T
1815
import Data.Text.IO.Utf8 qualified as T.Utf8
1916
import Distribution.ModuleName (ModuleName)
2017
import Distribution.Types.PackageName (PackageName)
2118
import Ormolu.Exception
2219
import Ormolu.Fixity
2320
import Ormolu.Fixity.Parser
24-
import Ormolu.Utils.IO (findClosestFileSatisfying, withIORefCache)
21+
import Ormolu.Utils.IO (Cache, findClosestFileSatisfying, newCache, withCache)
2522
import System.Directory
2623
import System.IO.Unsafe (unsafePerformIO)
2724
import Text.Megaparsec (errorBundlePretty)
@@ -37,7 +34,7 @@ getDotOrmoluForSourceFile ::
3734
m (FixityOverrides, ModuleReexports)
3835
getDotOrmoluForSourceFile sourceFile =
3936
liftIO (findDotOrmoluFile sourceFile) >>= \case
40-
Just dotOrmoluFile -> liftIO $ withIORefCache cacheRef dotOrmoluFile $ do
37+
Just dotOrmoluFile -> liftIO $ withCache cacheRef dotOrmoluFile $ do
4138
dotOrmoluRelative <- makeRelativeToCurrentDirectory dotOrmoluFile
4239
contents <- T.Utf8.readFile dotOrmoluFile
4340
case parseDotOrmolu dotOrmoluRelative contents of
@@ -58,8 +55,8 @@ findDotOrmoluFile = findClosestFileSatisfying $ \x ->
5855
x == ".ormolu"
5956

6057
-- | Cache ref that maps names of @.ormolu@ files to their contents.
61-
cacheRef :: IORef (Map FilePath (FixityOverrides, ModuleReexports))
62-
cacheRef = unsafePerformIO (newIORef Map.empty)
58+
cacheRef :: Cache FilePath (FixityOverrides, ModuleReexports)
59+
cacheRef = unsafePerformIO newCache
6360
{-# NOINLINE cacheRef #-}
6461

6562
-- | A wrapper around 'parseFixityDeclaration' for parsing individual fixity

src/Ormolu/Utils/IO.hs

+21-7
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
module Ormolu.Utils.IO
55
( findClosestFileSatisfying,
6-
withIORefCache,
6+
Cache,
7+
newCache,
8+
withCache,
79
)
810
where
911

12+
import Control.Concurrent (MVar, modifyMVar_, newMVar, readMVar)
1013
import Control.Exception (catch, throwIO)
1114
import Control.Monad.IO.Class
12-
import Data.IORef
1315
import Data.Map.Lazy (Map)
1416
import Data.Map.Lazy qualified as M
1517
import System.Directory
@@ -48,14 +50,26 @@ findClosestFileSatisfying isRightFile rootOfSearch = liftIO $ do
4850
then pure Nothing
4951
else findClosestFileSatisfying isRightFile parentDir
5052

53+
newtype Cache k v = Cache (MVar (Map k v))
54+
55+
newCache :: (Ord k) => IO (Cache k v)
56+
newCache = do
57+
var <- newMVar mempty
58+
pure (Cache var)
59+
5160
-- | Execute an 'IO' action but only if the given key is not found in the
52-
-- 'IORef' cache.
53-
withIORefCache :: (Ord k) => IORef (Map k v) -> k -> IO v -> IO v
54-
withIORefCache cacheRef k action = do
55-
cache <- readIORef cacheRef
61+
-- cache.
62+
withCache :: (Ord k) => Cache k v -> k -> IO v -> IO v
63+
withCache (Cache cacheVar) k action = do
64+
-- Note we do not use modifyMVar here. The reason is that
65+
-- 'action' may be long-running and we don't want to lock
66+
-- the cache for the whole time. This means that potentially
67+
-- we might end up running the same action twice if they
68+
-- both start before one has populated the cache.
69+
cache <- readMVar cacheVar
5670
case M.lookup k cache of
5771
Just v -> pure v
5872
Nothing -> do
5973
v <- action
60-
modifyIORef' cacheRef (M.insert k v)
74+
modifyMVar_ cacheVar (pure . M.insert k v)
6175
pure v

0 commit comments

Comments
 (0)