Skip to content

Fix hls-graph: phantom dependencies invoke in branching deps (resolve #3423) #4087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b862c6d
cache semantic lookup
soulomoon Feb 12, 2024
f4458a7
stop propagate failure on visible generated name
soulomoon Feb 12, 2024
03dac25
add test case
soulomoon Feb 12, 2024
069f3ed
Merge branch 'master' into cache-semantic-lookup
soulomoon Feb 12, 2024
4614d82
cleanup
soulomoon Feb 12, 2024
10fa6ee
Merge branch 'master' into cache-semantic-lookup
soulomoon Feb 15, 2024
b4bf796
configure bench to run semantic tokens
soulomoon Feb 15, 2024
3953584
Merge branch 'master' into cache-semantic-lookup
soulomoon Feb 16, 2024
89a263e
try to edit the file and then get result
soulomoon Feb 15, 2024
d09cc33
update bench config
soulomoon Feb 16, 2024
b4a527c
fix config
soulomoon Feb 16, 2024
c8b286a
add back test
soulomoon Feb 16, 2024
fd4ab4d
linearlize the refreshing of dependencies
soulomoon Feb 21, 2024
0e73a5c
fix up hls-graph test
soulomoon Feb 21, 2024
e08f070
keep track of visited keys in `refreshDeps`
soulomoon Feb 21, 2024
cb4a527
through error on `LogTypecheckedFOI`
soulomoon Feb 21, 2024
90ebb96
Revert "through error on `LogTypecheckedFOI`"
soulomoon Feb 22, 2024
37560e9
refactor plugin: fix regex for extracting import suggestions (#4080)
jhrcek Feb 17, 2024
8459019
Bump pre-commit/action from 3.0.0 to 3.0.1 (#4066)
dependabot[bot] Feb 19, 2024
9d33b5a
Add support for fourmolu 0.15 (#4086)
brandonchinn178 Feb 20, 2024
1ede741
Add Method_TextDocumentSemanticTokensFullDelta (#4073)
soulomoon Feb 21, 2024
5126c75
Redundant imports/exports: use range only to determine which code act…
keithfancher Feb 21, 2024
1fd122e
revert cacheLookup
soulomoon Feb 23, 2024
fd812cf
cleanup
soulomoon Feb 23, 2024
e8e88c7
Merge branch 'master' into branching-deps
soulomoon Feb 23, 2024
bbe1be6
update doc
soulomoon Feb 23, 2024
0bd4a20
use strict modifyIORef'
soulomoon Feb 23, 2024
c580ff5
fix doc
soulomoon Feb 23, 2024
888e249
add test to prevent phantom dependencies
soulomoon Feb 23, 2024
aeeb1be
Update ActionSpec.hs
soulomoon Feb 24, 2024
451e7ce
Update Experiments.hs
soulomoon Feb 24, 2024
eee5dc8
Merge branch 'master' into branching-deps
soulomoon Feb 24, 2024
cf37253
rephrase comment
soulomoon Feb 25, 2024
69d1dad
Update config.yaml
soulomoon Feb 25, 2024
95a42f0
recover AlwaysRerunDeps
soulomoon Feb 26, 2024
4a1a52e
rephrase comment
soulomoon Feb 26, 2024
af2bdfb
force KeySet before adding to the ResultDeps
soulomoon Feb 26, 2024
87cfc28
Merge branch 'master' into branching-deps
soulomoon Feb 26, 2024
9b72bf0
use bang pattern to force
soulomoon Feb 26, 2024
c3732b1
Merge branch 'master' into branching-deps
soulomoon Feb 29, 2024
10d0494
Merge branch 'master' into branching-deps
soulomoon Mar 2, 2024
024757c
Merge branch 'master' into branching-deps
soulomoon Mar 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion bench/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ examples:
modules:
- src/Distribution/Simple.hs
- src/Distribution/Types/Module.hs
#- src/Distribution/Simple/Configure.hs
#- src/Distribution/Simple/BuildTarget.hs
extra-args: [] # extra HLS command line args
# Small-sized project with TH
- name: lsp-types
Expand Down Expand Up @@ -94,6 +96,7 @@ experiments:
- "edit-header"
- "edit"
- "hover"
- "semanticTokens"
- "hover after edit"
# - "hover after cradle edit"
- "getDefinition"
Expand Down Expand Up @@ -129,7 +132,7 @@ versions:
# WARNING: Currently bench versions later than e4234a3a5e347db249fccefb8e3fb36f89e8eafb
# will be unable to send plugin configurations to earlier HLS versions. This causes
# all plugins in those versions to always be enabled.
# In addition bench proactively disables all plugins it knows about besides the
# In addition bench proactively disables all plugins it knows about besides the
# ones in the following list. However because it can only disable plugins it
# knows about, any plugins that are in old versions but were removed from HLS
# before the current bench will not be disabled.
Expand Down Expand Up @@ -194,6 +197,7 @@ configurations:
- qualifyImportedNames
- rename
- stylish-haskell
- semanticTokens
# - alternateNumberFormat
# - callHierarchy
# - changeTypeSignature
Expand Down
23 changes: 18 additions & 5 deletions ghcide-bench/src/Experiments.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import Control.Applicative.Combinators (skipManyTill)
import Control.Concurrent.Async (withAsync)
import Control.Exception.Safe (IOException, handleAny,
try)
import Control.Lens (_Just, (&), (.~), (^.))
import Control.Lens (_Just, (&), (.~), (^.),
(^?))
import Control.Lens.Extras (is)
import Control.Monad.Extra (allM, forM, forM_, forever,
unless, void, when,
Expand Down Expand Up @@ -100,7 +101,19 @@ allWithIdentifierPos f docs = case applicableDocs of

experiments :: HasConfig => [Bench]
experiments =
[ ---------------------------------------------------------------------------------------
[
bench "semanticTokens" $ \docs -> do
liftIO $ putStrLn "Starting semanticTokens"
r <- forM docs $ \DocumentPositions{..} -> do
changeDoc doc [charEdit stringLiteralP]
waitForProgressStart
waitForProgressDone
tks <- getSemanticTokens doc
case tks ^? LSP._L of
Just _ -> return True
Nothing -> return False
return $ and r,
---------------------------------------------------------------------------------------
bench "hover" $ allWithIdentifierPos $ \DocumentPositions{..} ->
isJust <$> getHover doc (fromJust identifierP),
---------------------------------------------------------------------------------------
Expand Down Expand Up @@ -316,7 +329,7 @@ versionP = maybeReader $ extract . readP_to_S parseVersion
extract parses = listToMaybe [ res | (res,"") <- parses]

output :: (MonadIO m, HasConfig) => String -> m ()
output = if quiet?config then (\_ -> pure ()) else liftIO . putStrLn
output = if quiet ?config then (\_ -> pure ()) else liftIO . putStrLn

---------------------------------------------------------------------------------------

Expand Down Expand Up @@ -597,7 +610,7 @@ callCommandLogging cmd = do
setup :: HasConfig => IO SetupResult
setup = do
-- when alreadyExists $ removeDirectoryRecursive examplesPath
benchDir <- case exampleDetails(example ?config) of
benchDir <- case exampleDetails (example ?config) of
ExamplePath examplePath -> do
let hieYamlPath = examplePath </> "hie.yaml"
alreadyExists <- doesFileExist hieYamlPath
Expand Down Expand Up @@ -661,7 +674,7 @@ setup = do

whenJust (shakeProfiling ?config) $ createDirectoryIfMissing True

let cleanUp = case exampleDetails(example ?config) of
let cleanUp = case exampleDetails (example ?config) of
ExampleHackage _ -> removeDirectoryRecursive examplesPath
ExampleScript _ _ -> removeDirectoryRecursive examplesPath
ExamplePath _ -> return ()
Expand Down
1 change: 1 addition & 0 deletions hls-graph/src/Development/IDE/Graph/Database.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Development.IDE.Graph.Database(
,shakeGetBuildEdges) where
import Control.Concurrent.STM.Stats (readTVarIO)
import Data.Dynamic
import Data.Foldable (fold)
import Data.Maybe
import Development.IDE.Graph.Classes ()
import Development.IDE.Graph.Internal.Action
Expand Down
6 changes: 4 additions & 2 deletions hls-graph/src/Development/IDE/Graph/Internal/Action.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Development.IDE.Graph.Internal.Action
) where

import Control.Concurrent.Async
import Control.DeepSeq (force)
import Control.Exception
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
Expand All @@ -38,7 +39,7 @@ type ShakeValue a = (Show a, Typeable a, Eq a, Hashable a, NFData a)
alwaysRerun :: Action ()
alwaysRerun = do
ref <- Action $ asks actionDeps
liftIO $ modifyIORef ref (AlwaysRerunDeps mempty <>)
liftIO $ modifyIORef' ref (AlwaysRerunDeps mempty <>)

-- No-op for now
reschedule :: Double -> Action ()
Expand Down Expand Up @@ -120,7 +121,8 @@ apply ks = do
stack <- Action $ asks actionStack
(is, vs) <- liftIO $ build db stack ks
ref <- Action $ asks actionDeps
liftIO $ modifyIORef ref (ResultDeps (fromListKeySet $ toList is) <>)
let !ks = force $ fromListKeySet $ toList is
liftIO $ modifyIORef' ref (ResultDeps [ks] <>)
pure vs

-- | Evaluate a list of keys without recording any dependencies.
Expand Down
51 changes: 33 additions & 18 deletions hls-graph/src/Development/IDE/Graph/Internal/Database.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}

module Development.IDE.Graph.Internal.Database (newDatabase, incDatabase, build, getDirtySet, getKeysAndVisitAge) where

Expand All @@ -25,7 +25,7 @@ import Control.Monad.Trans.Reader
import qualified Control.Monad.Trans.State.Strict as State
import Data.Dynamic
import Data.Either
import Data.Foldable (for_, traverse_)
import Data.Foldable (fold, for_, traverse_)
import Data.IORef.Extra
import Data.List.NonEmpty (unzip)
import Data.Maybe
Expand Down Expand Up @@ -133,26 +133,41 @@ builder db@Database{..} stack keys = withRunInIO $ \(RunInIO run) -> do
waitAll
pure results

isDirty :: Foldable t => Result -> t (a, Result) -> Bool
isDirty me = any (\(_,dep) -> resultBuilt me < resultChanged dep)

-- | Refresh dependencies for a key and compute the key:
-- The refresh the deps linearly(last computed order of the deps for the key).
-- If any of the deps is dirty in the process, we jump to the actual computation of the key
-- and shortcut the refreshing of the rest of the deps.
-- * If no dirty dependencies and we have evaluated the key previously, then we refresh it in the current thread.
-- This assumes that the implementation will be a lookup
-- * Otherwise, we spawn a new thread to refresh the dirty deps (if any) and the key itself
refreshDeps :: KeySet -> Database -> Stack -> Key -> Result -> [KeySet] -> AIO (IO Result)
refreshDeps visited db stack key result = \case
-- no more deps to refresh
[] -> pure $ compute db stack key RunDependenciesSame (Just result)
(dep:deps) -> do
let newVisited = dep <> visited
res <- builder db stack (toListKeySet (dep `differenceKeySet` visited))
case res of
Left res -> if isDirty result res
-- restart the computation if any of the deps are dirty
then asyncWithCleanUp $ liftIO $ compute db stack key RunDependenciesChanged (Just result)
-- else kick the rest of the deps
else refreshDeps newVisited db stack key result deps
Right iores -> asyncWithCleanUp $ liftIO $ do
res <- iores
if isDirty result res
then compute db stack key RunDependenciesChanged (Just result)
else join $ runAIO $ refreshDeps newVisited db stack key result deps

-- | Refresh a key:
-- * If no dirty dependencies and we have evaluated the key previously, then we refresh it in the current thread.
-- This assumes that the implementation will be a lookup
-- * Otherwise, we spawn a new thread to refresh the dirty deps (if any) and the key itself
refresh :: Database -> Stack -> Key -> Maybe Result -> AIO (IO Result)
-- refresh _ st k _ | traceShow ("refresh", st, k) False = undefined
refresh db stack key result = case (addStack key stack, result) of
(Left e, _) -> throw e
(Right stack, Just me@Result{resultDeps = ResultDeps (toListKeySet -> deps)}) -> do
res <- builder db stack deps
let isDirty = any (\(_,dep) -> resultBuilt me < resultChanged dep)
case res of
Left res ->
if isDirty res
then asyncWithCleanUp $ liftIO $ compute db stack key RunDependenciesChanged result
else pure $ compute db stack key RunDependenciesSame result
Right iores -> asyncWithCleanUp $ liftIO $ do
res <- iores
let mode = if isDirty res then RunDependenciesChanged else RunDependenciesSame
compute db stack key mode result
(Right stack, Just me@Result{resultDeps = ResultDeps deps}) -> refreshDeps mempty db stack key me (reverse deps)
(Right stack, _) ->
asyncWithCleanUp $ liftIO $ compute db stack key RunDependenciesChanged result

Expand All @@ -173,7 +188,7 @@ compute db@Database{..} stack key mode result = do
previousDeps= maybe UnknownDeps resultDeps result
let res = Result runValue built' changed built actualDeps execution runStore
case getResultDepsDefault mempty actualDeps of
deps | not(nullKeySet deps)
deps | not (nullKeySet deps)
&& runChanged /= ChangedNothing
-> do
-- IMPORTANT: record the reverse deps **before** marking the key Clean.
Expand Down
2 changes: 1 addition & 1 deletion hls-graph/src/Development/IDE/Graph/Internal/Key.hs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ renderKey :: Key -> Text
renderKey (lookupKeyValue -> KeyValue _ t) = t

newtype KeySet = KeySet IntSet
deriving newtype (Eq, Ord, Semigroup, Monoid)
deriving newtype (Eq, Ord, Semigroup, Monoid, NFData)

instance Show KeySet where
showsPrec p (KeySet is)= showParen (p > 10) $
Expand Down
1 change: 1 addition & 0 deletions hls-graph/src/Development/IDE/Graph/Internal/Profile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Data.Bifunctor
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Char
import Data.Dynamic (toDyn)
import Data.Foldable (fold)
import qualified Data.HashMap.Strict as Map
import Data.List (dropWhileEnd, foldl',
intercalate,
Expand Down
11 changes: 8 additions & 3 deletions hls-graph/src/Development/IDE/Graph/Internal/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Data.Aeson (FromJSON, ToJSON)
import Data.Bifunctor (second)
import qualified Data.ByteString as BS
import Data.Dynamic
import Data.Foldable (fold)
import qualified Data.HashMap.Strict as Map
import Data.IORef
import Data.List (intercalate)
Expand Down Expand Up @@ -144,16 +145,20 @@ data Result = Result {
resultData :: !BS.ByteString
}

data ResultDeps = UnknownDeps | AlwaysRerunDeps !KeySet | ResultDeps !KeySet
-- Notice, invariant to maintain:
-- the ![KeySet] in ResultDeps need to be stored in reverse order,
-- so that we can append to it efficiently, and we need the ordering
-- so we can do a linear dependency refreshing in refreshDeps.
data ResultDeps = UnknownDeps | AlwaysRerunDeps !KeySet | ResultDeps ![KeySet]
deriving (Eq, Show)

getResultDepsDefault :: KeySet -> ResultDeps -> KeySet
getResultDepsDefault _ (ResultDeps ids) = ids
getResultDepsDefault _ (ResultDeps ids) = fold ids
getResultDepsDefault _ (AlwaysRerunDeps ids) = ids
getResultDepsDefault def UnknownDeps = def

mapResultDeps :: (KeySet -> KeySet) -> ResultDeps -> ResultDeps
mapResultDeps f (ResultDeps ids) = ResultDeps $ f ids
mapResultDeps f (ResultDeps ids) = ResultDeps $ fmap f ids
mapResultDeps f (AlwaysRerunDeps ids) = AlwaysRerunDeps $ f ids
mapResultDeps _ UnknownDeps = UnknownDeps

Expand Down
34 changes: 29 additions & 5 deletions hls-graph/test/ActionSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@

module ActionSpec where

import qualified Control.Concurrent as C
import Control.Concurrent.STM
import Development.IDE.Graph (shakeOptions)
import Development.IDE.Graph.Database (shakeNewDatabase,
shakeRunDatabase)
import Development.IDE.Graph (shakeOptions)
import Development.IDE.Graph.Database (shakeNewDatabase,
shakeRunDatabase)
import Development.IDE.Graph.Internal.Database (build, incDatabase)
import Development.IDE.Graph.Internal.Key
import Development.IDE.Graph.Internal.Types
import Development.IDE.Graph.Rule
import Example
import qualified StmContainers.Map as STM
import qualified StmContainers.Map as STM
import Test.Hspec

spec :: Spec
Expand Down Expand Up @@ -40,7 +42,7 @@ spec = do
apply1 theKey
res `shouldBe` [True]
Just (Clean res) <- lookup (newKey theKey) <$> getDatabaseValues theDb
resultDeps res `shouldBe` ResultDeps (singletonKeySet $ newKey (Rule @()))
resultDeps res `shouldBe` ResultDeps [singletonKeySet $ newKey (Rule @())]
it "tracks reverse dependencies" $ do
db@(ShakeDatabase _ _ Database {..}) <- shakeNewDatabase shakeOptions $ do
ruleUnit
Expand All @@ -57,6 +59,28 @@ spec = do
addRule $ \(Rule :: Rule ()) _old _mode -> error "boom"
let res = shakeRunDatabase db $ pure $ apply1 (Rule @())
res `shouldThrow` anyErrorCall
it "computes a rule with branching dependencies does not invoke phantom dependencies #3423" $ do
cond <- C.newMVar True
count <- C.newMVar 0
(ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do
ruleUnit
ruleCond cond
ruleSubBranch count
ruleWithCond
-- build the one with the condition True
-- This should call the SubBranchRule once
-- cond rule would return different results each time
res0 <- build theDb emptyStack [BranchedRule]
snd res0 `shouldBe` [1 :: Int]
incDatabase theDb Nothing
-- build the one with the condition False
-- This should not call the SubBranchRule
res1 <- build theDb emptyStack [BranchedRule]
snd res1 `shouldBe` [2 :: Int]
-- SubBranchRule should be recomputed once before this (when the condition was True)
countRes <- build theDb emptyStack [SubBranchRule]
snd countRes `shouldBe` [1 :: Int]

describe "applyWithoutDependency" $ do
it "does not track dependencies" $ do
db@(ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do
Expand Down
35 changes: 35 additions & 0 deletions hls-graph/test/Example.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
{-# LANGUAGE TypeFamilies #-}
module Example where

import qualified Control.Concurrent as C
import Control.Monad.IO.Class (liftIO)
import Development.IDE.Graph
import Development.IDE.Graph.Classes
import Development.IDE.Graph.Rule
Expand All @@ -27,3 +29,36 @@ ruleBool :: Rules ()
ruleBool = addRule $ \Rule _old _mode -> do
() <- apply1 Rule
return $ RunResult ChangedRecomputeDiff "" True


data CondRule = CondRule
deriving (Eq, Generic, Hashable, NFData, Show, Typeable)
type instance RuleResult CondRule = Bool


ruleCond :: C.MVar Bool -> Rules ()
ruleCond mv = addRule $ \CondRule _old _mode -> do
r <- liftIO $ C.modifyMVar mv $ \x -> return (not x, x)
return $ RunResult ChangedRecomputeDiff "" r

data BranchedRule = BranchedRule
deriving (Eq, Generic, Hashable, NFData, Show, Typeable)
type instance RuleResult BranchedRule = Int

ruleWithCond :: Rules ()
ruleWithCond = addRule $ \BranchedRule _old _mode -> do
r <- apply1 CondRule
if r then do
_ <- apply1 SubBranchRule
return $ RunResult ChangedRecomputeDiff "" (1 :: Int)
else
return $ RunResult ChangedRecomputeDiff "" (2 :: Int)

data SubBranchRule = SubBranchRule
deriving (Eq, Generic, Hashable, NFData, Show, Typeable)
type instance RuleResult SubBranchRule = Int

ruleSubBranch :: C.MVar Int -> Rules ()
ruleSubBranch mv = addRule $ \SubBranchRule _old _mode -> do
r <- liftIO $ C.modifyMVar mv $ \x -> return (x+1, x)
return $ RunResult ChangedRecomputeDiff "" r
2 changes: 1 addition & 1 deletion shake-bench/src/Development/Benchmark/Rules.hs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ benchRules build MkBenchRules{..} = do
++ concat
[[ "-h"
, "-i" <> show i
, "-po" <> outHp
, "-po" <> dropExtension outHp
, "-qg"]
| CheapHeapProfiling i <- [prof]]
++ ["-RTS"]
Expand Down