@@ -53,7 +53,6 @@ module Development.IDE.Core.Shake(
5353 GlobalIdeOptions (.. ),
5454 HLS. getClientConfig ,
5555 getPluginConfig ,
56- garbageCollect ,
5756 knownTargets ,
5857 setPriority ,
5958 ideLogger ,
@@ -74,7 +73,9 @@ module Development.IDE.Core.Shake(
7473 HieDb ,
7574 HieDbWriter (.. ),
7675 VFSHandle (.. ),
77- addPersistentRule
76+ addPersistentRule ,
77+ garbageCollectDirtyKeys ,
78+ garbageCollectDirtyKeysOlderThan ,
7879 ) where
7980
8081import Control.Concurrent.Async
@@ -94,7 +95,6 @@ import Data.List.Extra (foldl', partition,
9495import Data.Map.Strict (Map )
9596import qualified Data.Map.Strict as Map
9697import Data.Maybe
97- import qualified Data.Set as Set
9898import qualified Data.SortedList as SL
9999import qualified Data.Text as T
100100import Data.Time
@@ -118,7 +118,11 @@ import Development.IDE.GHC.Compat (NameCache,
118118import Development.IDE.GHC.Orphans ()
119119import Development.IDE.Graph hiding (ShakeValue )
120120import qualified Development.IDE.Graph as Shake
121- import Development.IDE.Graph.Database
121+ import Development.IDE.Graph.Database (ShakeDatabase ,
122+ shakeGetBuildStep ,
123+ shakeOpenDatabase ,
124+ shakeProfileDatabase ,
125+ shakeRunDatabaseForKeys )
122126import Development.IDE.Graph.Rule
123127import Development.IDE.Types.Action
124128import Development.IDE.Types.Diagnostics
@@ -144,7 +148,9 @@ import Language.LSP.Types.Capabilities
144148import OpenTelemetry.Eventlog
145149
146150import Control.Exception.Extra hiding (bracket_ )
151+ import Data.Aeson (toJSON )
147152import qualified Data.ByteString.Char8 as BS8
153+ import Data.Coerce (coerce )
148154import Data.Default
149155import Data.Foldable (toList )
150156import Data.HashSet (HashSet )
@@ -153,6 +159,7 @@ import Data.IORef.Extra (atomicModifyIORef'_,
153159 atomicModifyIORef_ )
154160import Data.String (fromString )
155161import Data.Text (pack )
162+ import Debug.Trace.Flags (userTracingEnabled )
156163import qualified Development.IDE.Types.Exports as ExportsMap
157164import HieDb.Types
158165import Ide.Plugin.Config
@@ -327,10 +334,10 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do
327334 MaybeT $ pure $ (,del,ver) <$> fromDynamic dv
328335 case mv of
329336 Nothing -> do
330- void $ modifyVar' state $ HMap. alter (alterValue $ Failed True ) (file, Key k )
337+ void $ modifyVar' state $ HMap. alter (alterValue $ Failed True ) (toKey k file )
331338 return Nothing
332339 Just (v,del,ver) -> do
333- void $ modifyVar' state $ HMap. alter (alterValue $ Stale (Just del) ver (toDyn v)) (file, Key k )
340+ void $ modifyVar' state $ HMap. alter (alterValue $ Stale (Just del) ver (toDyn v)) (toKey k file )
334341 return $ Just (v,addDelta del $ mappingForVersion allMappings file ver)
335342
336343 -- We got a new stale value from the persistent rule, insert it in the map without affecting diagnostics
@@ -341,7 +348,7 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do
341348 -- Something already succeeded before, leave it alone
342349 _ -> old
343350
344- case HMap. lookup (file, Key k ) hm of
351+ case HMap. lookup (toKey k file ) hm of
345352 Nothing -> readPersistent
346353 Just (ValueWithDiagnostics v _) -> case v of
347354 Succeeded ver (fromDynamic -> Just v) -> pure (Just (v, mappingForVersion allMappings file ver))
@@ -356,12 +363,6 @@ lastValue key file = do
356363 s <- getShakeExtras
357364 liftIO $ lastValueIO s key file
358365
359- valueVersion :: Value v -> Maybe TextDocumentVersion
360- valueVersion = \ case
361- Succeeded ver _ -> Just ver
362- Stale _ ver _ -> Just ver
363- Failed _ -> Nothing
364-
365366mappingForVersion
366367 :: HMap. HashMap NormalizedUri (Map TextDocumentVersion (a , PositionMapping ))
367368 -> NormalizedFilePath
@@ -419,7 +420,7 @@ setValues :: IdeRule k v
419420 -> Vector FileDiagnostic
420421 -> IO ()
421422setValues state key file val diags =
422- void $ modifyVar' state $ HMap. insert (file, Key key) (ValueWithDiagnostics (fmap toDyn val) diags)
423+ void $ modifyVar' state $ HMap. insert (toKey key file ) (ValueWithDiagnostics (fmap toDyn val) diags)
423424
424425
425426-- | Delete the value stored for a given ide build key
@@ -430,7 +431,7 @@ deleteValue
430431 -> NormalizedFilePath
431432 -> IO ()
432433deleteValue ShakeExtras {dirtyKeys, state} key file = do
433- void $ modifyVar' state $ HMap. delete (file, Key key)
434+ void $ modifyVar' state $ HMap. delete (toKey key file )
434435 atomicModifyIORef_ dirtyKeys $ HSet. insert (toKey key file)
435436
436437recordDirtyKeys
@@ -454,7 +455,7 @@ getValues ::
454455 IO (Maybe (Value v , Vector FileDiagnostic ))
455456getValues state key file = do
456457 vs <- readVar state
457- case HMap. lookup (file, Key key) vs of
458+ case HMap. lookup (toKey key file ) vs of
458459 Nothing -> pure Nothing
459460 Just (ValueWithDiagnostics v diagsV) -> do
460461 let r = fmap (fromJust . fromDynamic @ v ) v
@@ -543,10 +544,31 @@ shakeOpen lspEnv defaultConfig logger debouncer
543544 { optOTMemoryProfiling = IdeOTMemoryProfiling otProfilingEnabled
544545 , optProgressStyle
545546 } <- getIdeOptionsIO shakeExtras
546- startTelemetry otProfilingEnabled logger $ state shakeExtras
547+
548+ void $ startTelemetry shakeDb shakeExtras
549+ startProfilingTelemetry otProfilingEnabled logger $ state shakeExtras
547550
548551 return ideState
549552
553+ startTelemetry :: ShakeDatabase -> ShakeExtras -> IO (Async () )
554+ startTelemetry db extras@ ShakeExtras {.. }
555+ | userTracingEnabled = do
556+ countKeys <- mkValueObserver " cached keys count"
557+ countDirty <- mkValueObserver " dirty keys count"
558+ countBuilds <- mkValueObserver " builds count"
559+ IdeOptions {optCheckParents} <- getIdeOptionsIO extras
560+ checkParents <- optCheckParents
561+ regularly 1 $ do
562+ readVar state >>= observe countKeys . countRelevantKeys checkParents . HMap. keys
563+ readIORef dirtyKeys >>= observe countDirty . countRelevantKeys checkParents . HSet. toList
564+ shakeGetBuildStep db >>= observe countBuilds
565+
566+ | otherwise = async (pure () )
567+ where
568+ regularly :: Seconds -> IO () -> IO (Async () )
569+ regularly delay act = async $ forever (act >> sleep delay)
570+
571+
550572-- | Must be called in the 'Initialized' handler and only once
551573shakeSessionInit :: IdeState -> IO ()
552574shakeSessionInit IdeState {.. } = do
@@ -733,20 +755,73 @@ getHiddenDiagnostics IdeState{shakeExtras = ShakeExtras{hiddenDiagnostics}} = do
733755 val <- readVar hiddenDiagnostics
734756 return $ getAllDiagnostics val
735757
736- -- | Clear the results for all files that do not match the given predicate.
737- garbageCollect :: (NormalizedFilePath -> Bool ) -> Action ()
738- garbageCollect keep = do
739- ShakeExtras {state, diagnostics,hiddenDiagnostics,publishedDiagnostics,positionMapping} <- getShakeExtras
740- liftIO $
741- do newState <- modifyVar' state $ HMap. filterWithKey (\ (file, _) _ -> keep file)
742- void $ modifyVar' diagnostics $ filterDiagnostics keep
743- void $ modifyVar' hiddenDiagnostics $ filterDiagnostics keep
744- void $ modifyVar' publishedDiagnostics $ HMap. filterWithKey (\ uri _ -> keep (fromUri uri))
745- let versionsForFile =
746- HMap. fromListWith Set. union $
747- mapMaybe (\ ((file, _key), ValueWithDiagnostics v _) -> (filePathToUri' file,) . Set. singleton <$> valueVersion v) $
748- HMap. toList newState
749- void $ modifyVar' positionMapping $ filterVersionMap versionsForFile
758+ -- | Find and release old keys from the state Hashmap
759+ -- For the record, there are other state sources that this process does not release:
760+ -- * diagnostics store (normal, hidden and published)
761+ -- * position mapping store
762+ -- * indexing queue
763+ -- * exports map
764+ garbageCollectDirtyKeys :: Action [Key ]
765+ garbageCollectDirtyKeys = do
766+ IdeOptions {optCheckParents} <- getIdeOptions
767+ checkParents <- liftIO optCheckParents
768+ garbageCollectDirtyKeysOlderThan 0 checkParents
769+
770+ garbageCollectDirtyKeysOlderThan :: Int -> CheckParents -> Action [Key ]
771+ garbageCollectDirtyKeysOlderThan maxAge checkParents = otTracedGarbageCollection " dirty GC" $ do
772+ dirtySet <- getDirtySet
773+ garbageCollectKeys " dirty GC" maxAge checkParents dirtySet
774+
775+ garbageCollectKeys :: String -> Int -> CheckParents -> [(Key , Int )] -> Action [Key ]
776+ garbageCollectKeys label maxAge checkParents agedKeys = do
777+ start <- liftIO offsetTime
778+ extras <- getShakeExtras
779+ (n:: Int , garbage ) <- liftIO $ modifyVar (state extras) $ \ vmap ->
780+ evaluate $ foldl' removeDirtyKey (vmap, (0 ,[] )) agedKeys
781+ liftIO $ atomicModifyIORef_ (dirtyKeys extras) $ \ x ->
782+ foldl' (flip HSet. insert) x garbage
783+ t <- liftIO start
784+ when (n> 0 ) $ liftIO $ do
785+ logDebug (logger extras) $ T. pack $
786+ label <> " of " <> show n <> " keys (took " <> showDuration t <> " )"
787+ when (coerce $ ideTesting extras) $ liftIO $ mRunLspT (lspEnv extras) $
788+ LSP. sendNotification (SCustomMethod " ghcide/GC" )
789+ (toJSON $ mapMaybe (fmap showKey . fromKeyType) garbage)
790+ return garbage
791+
792+ where
793+ showKey = show . Q
794+ removeDirtyKey st@ (vmap,(! counter, keys)) (k, age)
795+ | age > maxAge
796+ , Just (kt,_) <- fromKeyType k
797+ , not (kt `HSet.member` preservedKeys checkParents)
798+ , (True , vmap') <- HMap. alterF (\ prev -> (isJust prev, Nothing )) k vmap
799+ = (vmap', (counter+ 1 , k: keys))
800+ | otherwise = st
801+
802+ countRelevantKeys :: CheckParents -> [Key ] -> Int
803+ countRelevantKeys checkParents =
804+ Prelude. length . filter (maybe False (not . (`HSet.member` preservedKeys checkParents) . fst ) . fromKeyType)
805+
806+ preservedKeys :: CheckParents -> HashSet TypeRep
807+ preservedKeys checkParents = HSet. fromList $
808+ -- always preserved
809+ [ typeOf GetFileExists
810+ , typeOf GetModificationTime
811+ , typeOf IsFileOfInterest
812+ , typeOf GhcSessionIO
813+ , typeOf GetClientSettings
814+ , typeOf AddWatchedFile
815+ , typeOf GetKnownTargets
816+ ]
817+ ++ concat
818+ -- preserved if CheckParents is enabled since we need to rebuild the ModuleGraph
819+ [ [ typeOf GetModSummary
820+ , typeOf GetModSummaryWithoutTimestamps
821+ , typeOf GetLocatedImports
822+ ]
823+ | checkParents /= NeverCheck
824+ ]
750825
751826-- | Define a new Rule without early cutoff
752827define
@@ -921,8 +996,8 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
921996 v <- liftIO $ getValues state key file
922997 case v of
923998 -- No changes in the dependencies and we have
924- -- an existing result.
925- Just (v, diags) -> do
999+ -- an existing successful result.
1000+ Just (v@ Succeeded {} , diags) -> do
9261001 when doDiagnostics $
9271002 updateFileDiagnostics file (Key key) extras $ map (\ (_,y,z) -> (y,z)) $ Vector. toList diags
9281003 return $ Just $ RunResult ChangedNothing old $ A v
@@ -1128,20 +1203,6 @@ getUriDiagnostics uri ds =
11281203 maybe [] getDiagnosticsFromStore $
11291204 HMap. lookup uri ds
11301205
1131- filterDiagnostics ::
1132- (NormalizedFilePath -> Bool ) ->
1133- DiagnosticStore ->
1134- DiagnosticStore
1135- filterDiagnostics keep =
1136- HMap. filterWithKey (\ uri _ -> maybe True (keep . toNormalizedFilePath') $ uriToFilePath' $ fromNormalizedUri uri)
1137-
1138- filterVersionMap
1139- :: HMap. HashMap NormalizedUri (Set. Set TextDocumentVersion )
1140- -> HMap. HashMap NormalizedUri (Map TextDocumentVersion a )
1141- -> HMap. HashMap NormalizedUri (Map TextDocumentVersion a )
1142- filterVersionMap =
1143- HMap. intersectionWith $ \ versionsToKeep versionMap -> Map. restrictKeys versionMap versionsToKeep
1144-
11451206updatePositionMapping :: IdeState -> VersionedTextDocumentIdentifier -> List TextDocumentContentChangeEvent -> IO ()
11461207updatePositionMapping IdeState {shakeExtras = ShakeExtras {positionMapping}} VersionedTextDocumentIdentifier {.. } (List changes) = do
11471208 modifyVar_ positionMapping $ \ allMappings -> do
0 commit comments