99{-# LANGUAGE RecordWildCards #-}
1010{-# LANGUAGE TypeFamilies #-}
1111
12- module Development.IDE.Graph.Internal.Database (compute , newDatabase , incDatabase , build , getDirtySet , getKeysAndVisitAge , AsyncParentKill (.. ), computeToPreserve , getRunTimeRDeps , spawnAsyncWithDbRegistration ) where
12+ module Development.IDE.Graph.Internal.Database (compute , newDatabase , incDatabase , build , getDirtySet , getKeysAndVisitAge , AsyncParentKill (.. ), RuntimeRestartKeys ( .. ), computeToPreserve , getRunTimeRDeps , spawnAsyncWithDbRegistration ) where
1313
1414import Prelude hiding (unzip )
1515
@@ -38,9 +38,8 @@ import qualified StmContainers.Map as SMap
3838import System.Time.Extra (duration )
3939import UnliftIO (MVar , atomically ,
4040 newEmptyMVar , putMVar ,
41- takeMVar )
41+ readMVar )
4242
43- import qualified Data.List as List
4443import qualified UnliftIO.Exception as UE
4544
4645#if MIN_VERSION_base(4,19,0)
@@ -65,12 +64,11 @@ newDatabase dataBaseLogger databaseActionQueue databaseExtra databaseRules = do
6564-- | Increment the step and mark dirty.
6665-- Assumes that the database is not running a build
6766-- only some keys are dirty
68- incDatabase :: Database -> Maybe (([ Key ], [ Key ]) , KeySet ) -> IO KeySet
69- incDatabase db (Just ((_oldKeys, newKeys) , preserves)) = do
67+ incDatabase :: Database -> Maybe (RuntimeRestartKeys , KeySet ) -> IO KeySet
68+ incDatabase db (Just (RuntimeRestartKeys { .. } , preserves)) = do
7069 atomicallyNamed " incDatabase" $ modifyTVar' (databaseStep db) $ \ (Step i) -> Step $ i + 1
71- forM_ newKeys $ \ newKey -> atomically $ SMap. focus updateDirty newKey (databaseValues db)
72- -- only upsweep the keys that are not preserved
73- -- atomically $ writeUpsweepQueue (filter (`notMemberKeySet` preserves) oldkeys ++ newKeys) db
70+ forM_ restartDirtyKeys $ \ newKey -> atomically $ SMap. focus updateDirty newKey (databaseValues db)
71+ -- Only re-enqueue actions that were not preserved across the restart.
7472 return $ preserves
7573
7674-- all keys are dirty
@@ -82,10 +80,29 @@ incDatabase db Nothing = do
8280 SMap. focus updateDirty k (databaseValues db)
8381 return $ mempty
8482
85- computeToPreserve :: Database -> KeySet -> STM (KeySet , ([Key ], [Key ]), Int , [Key ])
86- computeToPreserve db dirtySet = do
87- (oldKeys, newKeys, affected) <- transitiveDirtyListBottomUpDiff db (toListKeySet dirtySet) []
88- pure (affected, (oldKeys, newKeys), length newKeys, [] )
83+ data RuntimeRestartKeys = RuntimeRestartKeys
84+ { restartKillKeys :: ! KeySet
85+ -- ^ Keys used to select running runtime actions to stop before the next
86+ -- session starts. This may include rule keys and delayed-action 'DirectKey's.
87+ , restartDirtyKeys :: ! [Key ]
88+ -- ^ Rule database keys to mark dirty before the next run. In the ghcide
89+ -- restart path this is rule-key-only by construction; the raw hls-graph API
90+ -- does not enforce that invariant by type.
91+ } deriving Show
92+
93+ -- Note [RuntimeRestartKeys]
94+ -- The restart plan intentionally keeps runtime cancellation separate from rule
95+ -- dirtiness. 'restartKillKeys' is consumed by shutdown and may include direct
96+ -- delayed-action keys. 'restartDirtyKeys' is consumed by the rule database and
97+ -- is expected to contain only rule keys that can be marked dirty.
98+ -- For the ghcide restart path, the initial dirty seeds come from rule keys
99+ -- ('toKey'/'toNoFileKey'), so 'restartDirtyKeys' can use the
100+ -- 'databaseRRuntimeDep' closure directly. Direct/root runtime edges are stored
101+ -- separately in 'databaseRRuntimeDepRoot' by 'insertdatabaseRuntimeDep' and are
102+ -- expanded only for 'restartKillKeys'. The raw hls-graph API does not enforce
103+ -- this seed invariant by type.
104+ computeToPreserve :: Database -> KeySet -> STM RuntimeRestartKeys
105+ computeToPreserve = transitiveDirtyKeysBottomUp
89106
90107updateDirty :: Monad m => Focus. Focus KeyDetails m ()
91108updateDirty = Focus. adjust $ \ (KeyDetails status rdeps) ->
@@ -130,7 +147,7 @@ interpreBuildContinue :: Database -> Key -> (Key, BuildContinue) -> IO (Key, Res
130147interpreBuildContinue _db _pk (_kid, BCStop k v) = return (k, v)
131148interpreBuildContinue db _pk (kid, BCContinue Nothing ) = builderOneFinal db emptyStack kid
132149interpreBuildContinue _db _pk (_kid, BCContinue (Just barrier)) =
133- takeMVar barrier >>= either throwIO pure
150+ readMVar barrier >>= either throwIO pure
134151
135152
136153builderOne :: Key -> Database -> Stack -> Key -> IO (Key , BuildContinue )
@@ -324,32 +341,22 @@ getRunTimeRDeps db k = SMap.lookup k (databaseRRuntimeDep db)
324341getDeps :: SMap. Map Key KeySet -> Key -> STM (Maybe KeySet )
325342getDeps m k = SMap. lookup k m
326343
327- -- Edges in the reverse-dependency graph go from a child to its parents.
328- -- We perform a DFS and, after exploring all outgoing edges, cons the node onto
329- -- the accumulator. This yields children-before-parents order directly.
330-
331- -- the lefts are keys that are no longer affected, we can try to mark them clean
332- -- the rights are new affected keys, we need to mark them dirty
333- transitiveDirtyListBottomUpDiff :: Database -> [Key ] -> [Key ] -> STM ([Key ], [Key ], KeySet )
334- transitiveDirtyListBottomUpDiff database seeds allOldKeys = do
335- (newKeys, seen) <- cacheTransitiveDirtyListBottomUpDFSWithRootKey database $ fromListKeySet seeds
336- let oldKeys = filter (`notMemberKeySet` seen) allOldKeys
337- return (oldKeys, newKeys, seen)
338-
339- cacheTransitiveDirtyListBottomUpDFSWithRootKey :: Database -> KeySet -> STM ([Key ], KeySet )
340- cacheTransitiveDirtyListBottomUpDFSWithRootKey db@ Database {.. } seeds = do
341- (newKeys, seen) <- cacheTransitiveDirtyListBottomUpDFS db seeds
342- -- we should put pump root keys back to seen
343- -- for each new key, get its root keys and put them back to seen
344- -- newKeys is for upsweep, databaseRRuntimeDepRoot only add new root keys which is not needed for upsweep
345- -- but seen is for thread filtering, we need to make sure all root keys are in seen
346- (_newKeys, newSeen) <- transitiveDirtyListBottomUpDFS databaseRRuntimeDepRoot seen
344+ transitiveDirtyKeysBottomUp :: Database -> KeySet -> STM RuntimeRestartKeys
345+ transitiveDirtyKeysBottomUp db@ Database {.. } seeds = do
346+ TransitiveDirtyKeys dirtyKeys seen <- cacheTransitiveDirtyListBottomUpDFS db seeds
347+ -- restartDirtyKeys should contain only rule keys. restartKillKeys also needs
348+ -- the root/direct delayed-action keys, so expand through the root dependency
349+ -- map only for the kill set.
350+ TransitiveDirtyKeys _newKeys newSeen <- transitiveDirtyListBottomUpDFS databaseRRuntimeDepRoot seen
347351 let rootKey = newKey " root"
348- return $ (List. delete rootKey newKeys, deleteKeySet rootKey newSeen)
352+ pure RuntimeRestartKeys
353+ { restartDirtyKeys = dirtyKeys
354+ , restartKillKeys = deleteKeySet rootKey newSeen
355+ }
349356
350357
351358
352- cacheTransitiveDirtyListBottomUpDFS :: Database -> KeySet -> STM ([ Key ], KeySet )
359+ cacheTransitiveDirtyListBottomUpDFS :: Database -> KeySet -> STM TransitiveDirtyKeys
353360cacheTransitiveDirtyListBottomUpDFS Database {.. } seeds = do
354361 SMap. lookup seeds databaseTransitiveRRuntimeDepCache >>= \ case
355362 Just v -> return v
@@ -358,22 +365,25 @@ cacheTransitiveDirtyListBottomUpDFS Database{..} seeds = do
358365 SMap. insert r seeds databaseTransitiveRRuntimeDepCache
359366 return r
360367
361- transitiveDirtyListBottomUpDFS :: SMap. Map Key KeySet -> KeySet -> STM ([Key ], KeySet )
368+ -- Edges in the reverse-dependency graph go from a child to its parents.
369+ -- We perform a DFS and, after exploring all outgoing edges, cons the node onto
370+ -- the accumulator. This yields children-before-parents order directly.
371+ transitiveDirtyListBottomUpDFS :: SMap. Map Key KeySet -> KeySet -> STM TransitiveDirtyKeys
362372transitiveDirtyListBottomUpDFS database seeds = do
363- let go1 :: Key -> ([ Key ], KeySet ) -> STM ([ Key ], KeySet )
364- go1 x acc@ (dirties, seen) = do
373+ let go1 :: Key -> TransitiveDirtyKeys -> STM TransitiveDirtyKeys
374+ go1 x acc@ TransitiveDirtyKeys {transitiveDirtySet = seen} = do
365375 if x `memberKeySet` seen
366376 then pure acc
367377 else do
368- let newAcc = (dirties, insertKeySet x seen)
378+ let newAcc = acc{transitiveDirtySet = insertKeySet x seen}
369379 mnext <- getDeps database x
370- (newDirties, newSeen) <- foldrM go1 newAcc (maybe mempty toListKeySet mnext)
371- return (x : newDirties, newSeen)
372- -- if it is root key, we do not add it to the dirty list
373- -- since root key is not up for upsweep
374- -- but it would be in the seen list, so we would kill dirty root key async
380+ childClosure <- foldrM go1 newAcc (maybe mempty toListKeySet mnext)
381+ return childClosure{transitiveDirtyList = x : transitiveDirtyList childClosure}
382+ -- Root keys are filtered out by 'transitiveDirtyKeysBottomUp'
383+ -- for the dirty list, but kept in the set long enough to find
384+ -- runtime roots that need shutdown.
375385 -- traverse all seeds
376- foldrM go1 ([] , mempty ) (toListKeySet seeds)
386+ foldrM go1 (TransitiveDirtyKeys [] mempty ) (toListKeySet seeds)
377387
378388-- | Original spawnRefresh using the general pattern
379389-- inline
0 commit comments