@@ -53,7 +53,6 @@ module Development.IDE.Core.Shake(
53
53
GlobalIdeOptions (.. ),
54
54
HLS. getClientConfig ,
55
55
getPluginConfig ,
56
- garbageCollect ,
57
56
knownTargets ,
58
57
setPriority ,
59
58
ideLogger ,
@@ -74,7 +73,9 @@ module Development.IDE.Core.Shake(
74
73
HieDb ,
75
74
HieDbWriter (.. ),
76
75
VFSHandle (.. ),
77
- addPersistentRule
76
+ addPersistentRule ,
77
+ garbageCollectDirtyKeys ,
78
+ garbageCollectDirtyKeysOlderThan ,
78
79
) where
79
80
80
81
import Control.Concurrent.Async
@@ -94,7 +95,6 @@ import Data.List.Extra (foldl', partition,
94
95
import Data.Map.Strict (Map )
95
96
import qualified Data.Map.Strict as Map
96
97
import Data.Maybe
97
- import qualified Data.Set as Set
98
98
import qualified Data.SortedList as SL
99
99
import qualified Data.Text as T
100
100
import Data.Time
@@ -118,7 +118,11 @@ import Development.IDE.GHC.Compat (NameCache,
118
118
import Development.IDE.GHC.Orphans ()
119
119
import Development.IDE.Graph hiding (ShakeValue )
120
120
import 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 )
122
126
import Development.IDE.Graph.Rule
123
127
import Development.IDE.Types.Action
124
128
import Development.IDE.Types.Diagnostics
@@ -144,7 +148,9 @@ import Language.LSP.Types.Capabilities
144
148
import OpenTelemetry.Eventlog
145
149
146
150
import Control.Exception.Extra hiding (bracket_ )
151
+ import Data.Aeson (toJSON )
147
152
import qualified Data.ByteString.Char8 as BS8
153
+ import Data.Coerce (coerce )
148
154
import Data.Default
149
155
import Data.Foldable (toList )
150
156
import Data.HashSet (HashSet )
@@ -153,6 +159,7 @@ import Data.IORef.Extra (atomicModifyIORef'_,
153
159
atomicModifyIORef_ )
154
160
import Data.String (fromString )
155
161
import Data.Text (pack )
162
+ import Debug.Trace.Flags (userTracingEnabled )
156
163
import qualified Development.IDE.Types.Exports as ExportsMap
157
164
import HieDb.Types
158
165
import Ide.Plugin.Config
@@ -327,10 +334,10 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do
327
334
MaybeT $ pure $ (,del,ver) <$> fromDynamic dv
328
335
case mv of
329
336
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 )
331
338
return Nothing
332
339
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 )
334
341
return $ Just (v,addDelta del $ mappingForVersion allMappings file ver)
335
342
336
343
-- 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
341
348
-- Something already succeeded before, leave it alone
342
349
_ -> old
343
350
344
- case HMap. lookup (file, Key k ) hm of
351
+ case HMap. lookup (toKey k file ) hm of
345
352
Nothing -> readPersistent
346
353
Just (ValueWithDiagnostics v _) -> case v of
347
354
Succeeded ver (fromDynamic -> Just v) -> pure (Just (v, mappingForVersion allMappings file ver))
@@ -356,12 +363,6 @@ lastValue key file = do
356
363
s <- getShakeExtras
357
364
liftIO $ lastValueIO s key file
358
365
359
- valueVersion :: Value v -> Maybe TextDocumentVersion
360
- valueVersion = \ case
361
- Succeeded ver _ -> Just ver
362
- Stale _ ver _ -> Just ver
363
- Failed _ -> Nothing
364
-
365
366
mappingForVersion
366
367
:: HMap. HashMap NormalizedUri (Map TextDocumentVersion (a , PositionMapping ))
367
368
-> NormalizedFilePath
@@ -419,7 +420,7 @@ setValues :: IdeRule k v
419
420
-> Vector FileDiagnostic
420
421
-> IO ()
421
422
setValues 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)
423
424
424
425
425
426
-- | Delete the value stored for a given ide build key
@@ -430,7 +431,7 @@ deleteValue
430
431
-> NormalizedFilePath
431
432
-> IO ()
432
433
deleteValue ShakeExtras {dirtyKeys, state} key file = do
433
- void $ modifyVar' state $ HMap. delete (file, Key key)
434
+ void $ modifyVar' state $ HMap. delete (toKey key file )
434
435
atomicModifyIORef_ dirtyKeys $ HSet. insert (toKey key file)
435
436
436
437
recordDirtyKeys
@@ -454,7 +455,7 @@ getValues ::
454
455
IO (Maybe (Value v , Vector FileDiagnostic ))
455
456
getValues state key file = do
456
457
vs <- readVar state
457
- case HMap. lookup (file, Key key) vs of
458
+ case HMap. lookup (toKey key file ) vs of
458
459
Nothing -> pure Nothing
459
460
Just (ValueWithDiagnostics v diagsV) -> do
460
461
let r = fmap (fromJust . fromDynamic @ v ) v
@@ -543,10 +544,31 @@ shakeOpen lspEnv defaultConfig logger debouncer
543
544
{ optOTMemoryProfiling = IdeOTMemoryProfiling otProfilingEnabled
544
545
, optProgressStyle
545
546
} <- getIdeOptionsIO shakeExtras
546
- startTelemetry otProfilingEnabled logger $ state shakeExtras
547
+
548
+ void $ startTelemetry shakeDb shakeExtras
549
+ startProfilingTelemetry otProfilingEnabled logger $ state shakeExtras
547
550
548
551
return ideState
549
552
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
+
550
572
-- | Must be called in the 'Initialized' handler and only once
551
573
shakeSessionInit :: IdeState -> IO ()
552
574
shakeSessionInit IdeState {.. } = do
@@ -733,20 +755,73 @@ getHiddenDiagnostics IdeState{shakeExtras = ShakeExtras{hiddenDiagnostics}} = do
733
755
val <- readVar hiddenDiagnostics
734
756
return $ getAllDiagnostics val
735
757
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
+ ]
750
825
751
826
-- | Define a new Rule without early cutoff
752
827
define
@@ -921,8 +996,8 @@ defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
921
996
v <- liftIO $ getValues state key file
922
997
case v of
923
998
-- 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
926
1001
when doDiagnostics $
927
1002
updateFileDiagnostics file (Key key) extras $ map (\ (_,y,z) -> (y,z)) $ Vector. toList diags
928
1003
return $ Just $ RunResult ChangedNothing old $ A v
@@ -1128,20 +1203,6 @@ getUriDiagnostics uri ds =
1128
1203
maybe [] getDiagnosticsFromStore $
1129
1204
HMap. lookup uri ds
1130
1205
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
-
1145
1206
updatePositionMapping :: IdeState -> VersionedTextDocumentIdentifier -> List TextDocumentContentChangeEvent -> IO ()
1146
1207
updatePositionMapping IdeState {shakeExtras = ShakeExtras {positionMapping}} VersionedTextDocumentIdentifier {.. } (List changes) = do
1147
1208
modifyVar_ positionMapping $ \ allMappings -> do
0 commit comments