Skip to content

Commit

Permalink
Merge branch 'master' into soulomoon/shake-rebouncer
Browse files Browse the repository at this point in the history
  • Loading branch information
soulomoon committed May 10, 2024
2 parents 5d77f61 + 3084c7f commit 89ae61a
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 6 deletions.
7 changes: 3 additions & 4 deletions ghcide/session-loader/Development/IDE/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -615,10 +615,9 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} dir = do
void $ modifyVar' filesMap $ flip HM.union (HM.fromList (map ((,hieYaml) . fst) $ concatMap toFlagsMap all_targets))
-- The VFS doesn't change on cradle edits, re-use the old one.
-- Invalidate all the existing GhcSession build nodes by restarting the Shake session
restartShakeSession VFSUnmodified "new component" [] $ do
key1 <- extendKnownTargets all_targets
key2 <- invalidateShakeCache
return [key1, key2]
invalidateShakeCache
-- The VFS doesn't change on cradle edits, re-use the old one.
restartShakeSession VFSUnmodified "new component" []

-- Typecheck all files in the project on startup
checkProject <- getCheckProject
Expand Down
2 changes: 1 addition & 1 deletion ghcide/src/Development/IDE/Core/FileExists.hs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ getFileExistsMapUntracked = do
FileExistsMapVar v <- getIdeGlobalAction
return v

-- | Modify the global store of file exists and return the keys that need to be mark as dirty
-- | Modify the global store of file exists and return the keys that need to be marked as dirty
modifyFileExists :: IdeState -> [(NormalizedFilePath, FileChangeType)] -> IO [Key]
modifyFileExists state changes = do
FileExistsMapVar var <- getIdeGlobalState state
Expand Down
29 changes: 29 additions & 0 deletions ghcide/src/Development/IDE/Core/Shake.hs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ setValues state key file val diags =


-- | Delete the value stored for a given ide build key
-- and return the key that was deleted.
deleteValue
:: Shake.ShakeValue k
=> ShakeExtras
Expand Down Expand Up @@ -1324,6 +1325,8 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do
(if eq then ChangedRecomputeSame else ChangedRecomputeDiff)
(encodeShakeValue bs)
(A res) $ do
-- this hook needs to be run in the same transaction as the key is marked clean
-- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
setValues state key file res (Vector.fromList diags)
modifyTVar' dirtyKeys (deleteKeySet $ toKey key file)
return res
Expand All @@ -1349,6 +1352,32 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do
-- * creating bogus "file does not exists" diagnostics
| otherwise = useWithoutDependency (GetModificationTime_ False) fp

-- Note [Housekeeping rule cache and dirty key outside of hls-graph]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Hls-graph contains its own internal running state for each key in the shakeDatabase.
-- ShakeExtras contains `state` field (rule result cache) and `dirtyKeys` (keys that became
-- dirty in between build sessions) that is not visible to the hls-graph
-- Essentially, we need to keep the rule cache and dirty key and hls-graph's internal state
-- in sync.

-- 1. A dirty key collected in a session should not be removed from dirty keys in the same session.
-- Since if we clean out the dirty key in the same session,
-- 1.1. we will lose the chance to dirty its reverse dependencies. Since it only happens during session restart.
-- 1.2. a key might be marked as dirty in ShakeExtras while it's being recomputed by hls-graph which could lead to it's premature removal from dirtyKeys.
-- See issue https://github.com/haskell/haskell-language-server/issues/4093 for more details.

-- 2. When a key is marked clean in the hls-graph's internal running
-- state, the rule cache and dirty keys are updated in the same transaction.
-- otherwise, some situations like the following can happen:
-- thread 1: hls-graph session run a key
-- thread 1: defineEarlyCutoff' run the action for the key
-- thread 1: the action is done, rule cache and dirty key are updated
-- thread 2: we restart the hls-graph session, thread 1 is killed, the
-- hls-graph's internal state is not updated.
-- This is problematic with early cut off because we are having a new rule cache matching the
-- old hls-graph's internal state, which might case it's reverse dependency to skip the recomputation.
-- See https://github.com/haskell/haskell-language-server/issues/4194 for more details.

traceA :: A v -> String
traceA (A Failed{}) = "Failed"
traceA (A Stale{}) = "Stale"
Expand Down
3 changes: 2 additions & 1 deletion hls-graph/src/Development/IDE/Graph/Internal/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ data RunResult value = RunResult
,runValue :: value
-- ^ The value to return from 'Development.Shake.Rule.apply'.
,runHook :: STM ()
-- ^ The hook to run after the rule completes.
-- ^ The hook to run at the end of the build in the same transaction
-- when the key is marked as clean.
} deriving Functor

---------------------------------------------------------------------
Expand Down

0 comments on commit 89ae61a

Please sign in to comment.