Skip to content

Commit 2bd0588

Browse files
authored
Merge pull request #7516 from haskell/gb/speed-cache-reading
lazily decode cache files for checking invalidation
2 parents 5957ce7 + f8bdd7f commit 2bd0588

File tree

2 files changed

+51
-18
lines changed

2 files changed

+51
-18
lines changed

Cabal/src/Distribution/Utils/Structured.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ module Distribution.Utils.Structured (
6464
containerStructure,
6565
-- * Structure type
6666
Structure (..),
67+
Tag (..),
6768
TypeName,
6869
ConstructorName,
6970
TypeVersion,
@@ -207,7 +208,7 @@ structureBuilder s0 = State.evalState (go s0) Map.empty where
207208
Nothing -> return $ mconcat [ Builder.word8 0, Builder.stringUtf8 (show t) ]
208209
Just acc' -> do
209210
State.put acc'
210-
k
211+
k
211212

212213
goSop :: SopStructure -> State.State (Map.Map String (NonEmpty TypeRep)) Builder.Builder
213214
goSop sop = do

cabal-install/src/Distribution/Client/FileMonitor.hs

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE DeriveGeneric, DeriveFunctor, GeneralizedNewtypeDeriving,
3-
NamedFieldPuns, BangPatterns #-}
3+
NamedFieldPuns, BangPatterns, ScopedTypeVariables #-}
44
{-# OPTIONS_GHC -fno-warn-orphans #-}
55
{-# LANGUAGE ScopedTypeVariables #-}
66

@@ -45,8 +45,10 @@ module Distribution.Client.FileMonitor (
4545

4646
import Prelude ()
4747
import Distribution.Client.Compat.Prelude
48+
import qualified Distribution.Compat.Binary as Binary
4849

4950
import qualified Data.Map.Strict as Map
51+
import Data.Binary.Get (runGetOrFail)
5052
import qualified Data.ByteString.Lazy as BS
5153
import qualified Data.Hashable as Hashable
5254

@@ -62,7 +64,7 @@ import Distribution.Compat.Time
6264
import Distribution.Client.Glob
6365
import Distribution.Simple.Utils (handleDoesNotExist, writeFileAtomic)
6466
import Distribution.Client.Utils (mergeBy, MergeResult(..))
65-
import Distribution.Utils.Structured (structuredDecodeOrFailIO, structuredEncode)
67+
import Distribution.Utils.Structured (structuredEncode, Tag (..))
6668
import System.FilePath
6769
import System.Directory
6870
import System.IO
@@ -434,17 +436,19 @@ checkFileMonitorChanged
434436

435437
handleDoesNotExist (MonitorChanged MonitorFirstRun) $
436438
handleErrorCall (MonitorChanged MonitorCorruptCache) $
437-
readCacheFile monitor
438-
>>= either (\_ -> return (MonitorChanged MonitorCorruptCache))
439-
checkStatusCache
439+
withCacheFile monitor $
440+
either (\_ -> return (MonitorChanged MonitorCorruptCache))
441+
checkStatusCache
440442

441443
where
442-
checkStatusCache :: (MonitorStateFileSet, a, b) -> IO (MonitorChanged a b)
444+
checkStatusCache :: (MonitorStateFileSet, a, Either String b) -> IO (MonitorChanged a b)
443445
checkStatusCache (cachedFileStatus, cachedKey, cachedResult) = do
444446
change <- checkForChanges
445447
case change of
446448
Just reason -> return (MonitorChanged reason)
447-
Nothing -> return (MonitorUnchanged cachedResult monitorFiles)
449+
Nothing -> case cachedResult of
450+
Left _ -> pure (MonitorChanged MonitorCorruptCache)
451+
Right cr -> return (MonitorUnchanged cr monitorFiles)
448452
where monitorFiles = reconstructMonitorFilePaths cachedFileStatus
449453
where
450454
-- In fileMonitorCheckIfOnlyValueChanged mode we want to guarantee that
@@ -479,7 +483,7 @@ checkFileMonitorChanged
479483
= return Nothing
480484

481485
-- Check if any file has changed
482-
checkFileChange :: MonitorStateFileSet -> a -> b -> IO (Maybe (MonitorChangedReason a))
486+
checkFileChange :: MonitorStateFileSet -> a -> Either String b -> IO (Maybe (MonitorChangedReason a))
483487
checkFileChange cachedFileStatus cachedKey cachedResult = do
484488
res <- probeFileSystem root cachedFileStatus
485489
case res of
@@ -492,21 +496,50 @@ checkFileMonitorChanged
492496

493497
-- But we might still want to update the cache
494498
whenCacheChanged cacheStatus $
495-
rewriteCacheFile monitor cachedFileStatus' cachedKey cachedResult
499+
case cachedResult of
500+
Left _ -> pure ()
501+
Right cr -> rewriteCacheFile monitor cachedFileStatus' cachedKey cr
496502

497503
return Nothing
498504

505+
-- | Lazily decode a triple, parsing the first two fields strictly and
506+
-- returning a lazy value containing either the last one or an error.
507+
-- This is helpful for cabal cache files where the first two components
508+
-- contain header data that lets one test if the cache is still valid,
509+
-- and the last (potentially large) component is the cached value itself.
510+
-- This way we can test for cache validity without needing to pay the
511+
-- cost of the decode of stale cache data. This lives here rather than
512+
-- Distribution.Utils.Structured because it depends on a newer version of
513+
-- binary than supported in the Cabal library proper.
514+
structuredDecodeTriple
515+
:: forall a b c. (Structured a, Structured b, Structured c, Binary.Binary a, Binary.Binary b, Binary.Binary c)
516+
=> BS.ByteString -> Either String (a, b, Either String c)
517+
structuredDecodeTriple lbs =
518+
let partialDecode =
519+
(`runGetOrFail` lbs) $ do
520+
(_ :: Tag (a,b,c)) <- Binary.get
521+
(a :: a) <- Binary.get
522+
(b :: b) <- Binary.get
523+
pure (a, b)
524+
cleanEither (Left (_, pos, msg)) = Left ("Data.Binary.Get.runGet at position " ++ show pos ++ ": " ++ msg)
525+
cleanEither (Right (_,_,v)) = Right v
526+
527+
in case partialDecode of
528+
Left (_, pos, msg) -> Left ("Data.Binary.Get.runGet at position " ++ show pos ++ ": " ++ msg)
529+
Right (lbs', _, (x,y)) -> Right (x, y, cleanEither $ runGetOrFail (Binary.get :: Binary.Get c) lbs')
530+
499531
-- | Helper for reading the cache file.
500532
--
501533
-- This determines the type and format of the binary cache file.
502534
--
503-
readCacheFile :: (Binary a, Structured a, Binary b, Structured b)
535+
withCacheFile :: (Binary a, Structured a, Binary b, Structured b)
504536
=> FileMonitor a b
505-
-> IO (Either String (MonitorStateFileSet, a, b))
506-
readCacheFile FileMonitor {fileMonitorCacheFile} =
537+
-> (Either String (MonitorStateFileSet, a, Either String b) -> IO r)
538+
-> IO r
539+
withCacheFile (FileMonitor {fileMonitorCacheFile}) k =
507540
withBinaryFile fileMonitorCacheFile ReadMode $ \hnd -> do
508-
contents <- BS.hGetContents hnd
509-
structuredDecodeOrFailIO contents
541+
contents <- structuredDecodeTriple <$> BS.hGetContents hnd
542+
k contents
510543

511544
-- | Helper for writing the cache file.
512545
--
@@ -989,8 +1022,8 @@ readCacheFileHashes :: (Binary a, Structured a, Binary b, Structured b)
9891022
=> FileMonitor a b -> IO FileHashCache
9901023
readCacheFileHashes monitor =
9911024
handleDoesNotExist Map.empty $
992-
handleErrorCall Map.empty $ do
993-
res <- readCacheFile monitor
1025+
handleErrorCall Map.empty $
1026+
withCacheFile monitor $ \res ->
9941027
case res of
9951028
Left _ -> return Map.empty
9961029
Right (msfs, _, _) -> return (mkFileHashCache msfs)
@@ -1136,4 +1169,3 @@ handleIOException e =
11361169
------------------------------------------------------------------------------
11371170
-- Instances
11381171
--
1139-

0 commit comments

Comments
 (0)