Skip to content

Commit 322ac35

Browse files
soulomoonmichaelpj
andauthored
Fix resultBuilt(dirty mechanism) in hls-graph (#4238)
* clarify dirty in hls-graph * fix comment * hls-graph add `compute` test * move test to better place * add detailed test * fix comment --------- Co-authored-by: Michael Peyton Jones <[email protected]>
1 parent d839b78 commit 322ac35

File tree

5 files changed

+117
-40
lines changed

5 files changed

+117
-40
lines changed

hls-graph/src/Development/IDE/Graph/Internal/Database.hs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{-# LANGUAGE RecordWildCards #-}
88
{-# LANGUAGE TypeFamilies #-}
99

10-
module Development.IDE.Graph.Internal.Database (newDatabase, incDatabase, build, getDirtySet, getKeysAndVisitAge) where
10+
module Development.IDE.Graph.Internal.Database (compute, newDatabase, incDatabase, build, getDirtySet, getKeysAndVisitAge) where
1111

1212
import Prelude hiding (unzip)
1313

@@ -133,6 +133,9 @@ builder db@Database{..} stack keys = withRunInIO $ \(RunInIO run) -> do
133133
waitAll
134134
pure results
135135

136+
137+
-- | isDirty
138+
-- only dirty when it's build time is older than the changed time of one of its dependencies
136139
isDirty :: Foldable t => Result -> t (a, Result) -> Bool
137140
isDirty me = any (\(_,dep) -> resultBuilt me < resultChanged dep)
138141

@@ -179,14 +182,22 @@ compute db@Database{..} stack key mode result = do
179182
deps <- newIORef UnknownDeps
180183
(execution, RunResult{..}) <-
181184
duration $ runReaderT (fromAction act) $ SAction db deps stack
182-
built <- readTVarIO databaseStep
185+
curStep <- readTVarIO databaseStep
183186
deps <- readIORef deps
184-
let changed = if runChanged == ChangedRecomputeDiff then built else maybe built resultChanged result
185-
built' = if runChanged /= ChangedNothing then built else changed
186-
-- only update the deps when the rule ran with changes
187+
let lastChanged = maybe curStep resultChanged result
188+
let lastBuild = maybe curStep resultBuilt result
189+
-- changed time is always older than or equal to build time
190+
let (changed, built) = case runChanged of
191+
-- some thing changed
192+
ChangedRecomputeDiff -> (curStep, curStep)
193+
-- recomputed is the same
194+
ChangedRecomputeSame -> (lastChanged, curStep)
195+
-- nothing changed
196+
ChangedNothing -> (lastChanged, lastBuild)
197+
let -- only update the deps when the rule ran with changes
187198
actualDeps = if runChanged /= ChangedNothing then deps else previousDeps
188199
previousDeps= maybe UnknownDeps resultDeps result
189-
let res = Result runValue built' changed built actualDeps execution runStore
200+
let res = Result runValue built changed curStep actualDeps execution runStore
190201
case getResultDepsDefault mempty actualDeps of
191202
deps | not (nullKeySet deps)
192203
&& runChanged /= ChangedNothing

hls-graph/src/Development/IDE/Graph/Internal/Types.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ getDatabase = Action $ asks actionDatabase
8484
data ShakeDatabase = ShakeDatabase !Int [Action ()] Database
8585

8686
newtype Step = Step Int
87-
deriving newtype (Eq,Ord,Hashable)
87+
deriving newtype (Eq,Ord,Hashable,Show)
8888

8989
---------------------------------------------------------------------
9090
-- Keys
@@ -187,7 +187,6 @@ instance NFData RunMode where rnf x = x `seq` ()
187187
-- | How the output of a rule has changed.
188188
data RunChanged
189189
= ChangedNothing -- ^ Nothing has changed.
190-
| ChangedStore -- ^ The stored value has changed, but in a way that should be considered identical (used rarely).
191190
| ChangedRecomputeSame -- ^ I recomputed the value and it was the same.
192191
| ChangedRecomputeDiff -- ^ I recomputed the value and it was different.
193192
deriving (Eq,Show,Generic)

hls-graph/test/ActionSpec.hs

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33

44
module ActionSpec where
55

6+
import Control.Concurrent (MVar, readMVar)
67
import qualified Control.Concurrent as C
78
import Control.Concurrent.STM
9+
import Control.Monad.IO.Class (MonadIO (..))
810
import Development.IDE.Graph (shakeOptions)
911
import Development.IDE.Graph.Database (shakeNewDatabase,
10-
shakeRunDatabase)
12+
shakeRunDatabase,
13+
shakeRunDatabaseForKeys)
1114
import Development.IDE.Graph.Internal.Database (build, incDatabase)
1215
import Development.IDE.Graph.Internal.Key
1316
import Development.IDE.Graph.Internal.Types
@@ -16,15 +19,50 @@ import Example
1619
import qualified StmContainers.Map as STM
1720
import Test.Hspec
1821

22+
23+
1924
spec :: Spec
2025
spec = do
26+
describe "apply1" $ it "Test build update, Buggy dirty mechanism in hls-graph #4237" $ do
27+
let ruleStep1 :: MVar Int -> Rules ()
28+
ruleStep1 m = addRule $ \CountRule _old mode -> do
29+
-- depends on ruleSubBranch, it always changed if dirty
30+
_ :: Int <- apply1 SubBranchRule
31+
let r = 1
32+
case mode of
33+
-- it update the built step
34+
RunDependenciesChanged -> do
35+
_ <- liftIO $ C.modifyMVar m $ \x -> return (x+1, x)
36+
return $ RunResult ChangedRecomputeSame "" r (return ())
37+
-- this won't update the built step
38+
RunDependenciesSame ->
39+
return $ RunResult ChangedNothing "" r (return ())
40+
count <- C.newMVar 0
41+
count1 <- C.newMVar 0
42+
db <- shakeNewDatabase shakeOptions $ do
43+
ruleSubBranch count
44+
ruleStep1 count1
45+
-- bootstrapping the database
46+
_ <- shakeRunDatabase db $ pure $ apply1 CountRule -- count = 1
47+
let child = newKey SubBranchRule
48+
let parent = newKey CountRule
49+
-- instruct to RunDependenciesChanged then CountRule should be recomputed
50+
-- result should be changed 0, build 1
51+
_res1 <- shakeRunDatabaseForKeys (Just [child]) db [apply1 CountRule] -- count = 2
52+
-- since child changed = parent build
53+
-- instruct to RunDependenciesSame then CountRule should not be recomputed
54+
-- result should be changed 0, build 1
55+
_res3 <- shakeRunDatabaseForKeys (Just [parent]) db [apply1 CountRule] -- count = 2
56+
-- invariant child changed = parent build should remains after RunDependenciesSame
57+
-- this used to be a bug, with additional computation, see https://github.com/haskell/haskell-language-server/pull/4238
58+
_res3 <- shakeRunDatabaseForKeys (Just [parent]) db [apply1 CountRule] -- count = 2
59+
c1 <- readMVar count1
60+
c1 `shouldBe` 2
2161
describe "apply1" $ do
2262
it "computes a rule with no dependencies" $ do
23-
db <- shakeNewDatabase shakeOptions $ do
24-
ruleUnit
63+
db <- shakeNewDatabase shakeOptions ruleUnit
2564
res <- shakeRunDatabase db $
26-
pure $ do
27-
apply1 (Rule @())
65+
pure $ apply1 (Rule @())
2866
res `shouldBe` [()]
2967
it "computes a rule with one dependency" $ do
3068
db <- shakeNewDatabase shakeOptions $ do
@@ -38,8 +76,7 @@ spec = do
3876
ruleBool
3977
let theKey = Rule @Bool
4078
res <- shakeRunDatabase db $
41-
pure $ do
42-
apply1 theKey
79+
pure $ apply1 theKey
4380
res `shouldBe` [True]
4481
Just (Clean res) <- lookup (newKey theKey) <$> getDatabaseValues theDb
4582
resultDeps res `shouldBe` ResultDeps [singletonKeySet $ newKey (Rule @())]
@@ -49,14 +86,12 @@ spec = do
4986
ruleBool
5087
let theKey = Rule @Bool
5188
res <- shakeRunDatabase db $
52-
pure $ do
53-
apply1 theKey
89+
pure $ apply1 theKey
5490
res `shouldBe` [True]
5591
Just KeyDetails {..} <- atomically $ STM.lookup (newKey (Rule @())) databaseValues
56-
keyReverseDeps `shouldBe` (singletonKeySet $ newKey theKey)
92+
keyReverseDeps `shouldBe` singletonKeySet (newKey theKey)
5793
it "rethrows exceptions" $ do
58-
db <- shakeNewDatabase shakeOptions $ do
59-
addRule $ \(Rule :: Rule ()) _old _mode -> error "boom"
94+
db <- shakeNewDatabase shakeOptions $ addRule $ \(Rule :: Rule ()) _old _mode -> error "boom"
6095
let res = shakeRunDatabase db $ pure $ apply1 (Rule @())
6196
res `shouldThrow` anyErrorCall
6297
it "computes a rule with branching dependencies does not invoke phantom dependencies #3423" $ do
@@ -81,18 +116,16 @@ spec = do
81116
countRes <- build theDb emptyStack [SubBranchRule]
82117
snd countRes `shouldBe` [1 :: Int]
83118

84-
describe "applyWithoutDependency" $ do
85-
it "does not track dependencies" $ do
86-
db@(ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do
87-
ruleUnit
88-
addRule $ \Rule _old _mode -> do
89-
[()] <- applyWithoutDependency [Rule]
90-
return $ RunResult ChangedRecomputeDiff "" True $ return ()
119+
describe "applyWithoutDependency" $ it "does not track dependencies" $ do
120+
db@(ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do
121+
ruleUnit
122+
addRule $ \Rule _old _mode -> do
123+
[()] <- applyWithoutDependency [Rule]
124+
return $ RunResult ChangedRecomputeDiff "" True $ return ()
91125

92-
let theKey = Rule @Bool
93-
res <- shakeRunDatabase db $
94-
pure $ do
95-
applyWithoutDependency [theKey]
96-
res `shouldBe` [[True]]
97-
Just (Clean res) <- lookup (newKey theKey) <$> getDatabaseValues theDb
98-
resultDeps res `shouldBe` UnknownDeps
126+
let theKey = Rule @Bool
127+
res <- shakeRunDatabase db $
128+
pure $ applyWithoutDependency [theKey]
129+
res `shouldBe` [[True]]
130+
Just (Clean res) <- lookup (newKey theKey) <$> getDatabaseValues theDb
131+
resultDeps res `shouldBe` UnknownDeps

hls-graph/test/DatabaseSpec.hs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

33
module DatabaseSpec where
44

5-
import Development.IDE.Graph (shakeOptions)
6-
import Development.IDE.Graph.Database (shakeNewDatabase,
7-
shakeRunDatabase)
8-
import Development.IDE.Graph.Internal.Action (apply1)
9-
import Development.IDE.Graph.Internal.Rules (addRule)
5+
import Development.IDE.Graph (newKey, shakeOptions)
6+
import Development.IDE.Graph.Database (shakeNewDatabase,
7+
shakeRunDatabase)
8+
import Development.IDE.Graph.Internal.Action (apply1)
9+
import Development.IDE.Graph.Internal.Database (compute, incDatabase)
10+
import Development.IDE.Graph.Internal.Rules (addRule)
1011
import Development.IDE.Graph.Internal.Types
1112
import Example
12-
import System.Time.Extra (timeout)
13+
import System.Time.Extra (timeout)
1314
import Test.Hspec
1415

16+
1517
spec :: Spec
1618
spec = do
1719
describe "Evaluation" $ do
@@ -23,3 +25,25 @@ spec = do
2325
return $ RunResult ChangedRecomputeDiff "" () (return ())
2426
let res = shakeRunDatabase db $ pure $ apply1 (Rule @())
2527
timeout 1 res `shouldThrow` \StackException{} -> True
28+
29+
describe "compute" $ do
30+
it "build step and changed step updated correctly" $ do
31+
(ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do
32+
ruleStep
33+
34+
let k = newKey $ Rule @()
35+
-- ChangedRecomputeSame
36+
r1@Result{resultChanged=rc1, resultBuilt=rb1} <- compute theDb emptyStack k RunDependenciesChanged Nothing
37+
incDatabase theDb Nothing
38+
-- ChangedRecomputeSame
39+
r2@Result{resultChanged=rc2, resultBuilt=rb2} <- compute theDb emptyStack k RunDependenciesChanged (Just r1)
40+
incDatabase theDb Nothing
41+
-- changed Nothing
42+
Result{resultChanged=rc3, resultBuilt=rb3} <- compute theDb emptyStack k RunDependenciesSame (Just r2)
43+
rc1 `shouldBe` Step 0
44+
rc2 `shouldBe` Step 0
45+
rc3 `shouldBe` Step 0
46+
47+
rb1 `shouldBe` Step 0
48+
rb2 `shouldBe` Step 1
49+
rb3 `shouldBe` Step 1

hls-graph/test/Example.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ instance Typeable a => Show (Rule a) where
2020

2121
type instance RuleResult (Rule a) = a
2222

23+
ruleStep :: Rules ()
24+
ruleStep = addRule $ \(Rule :: Rule ()) _old mode -> do
25+
case mode of
26+
RunDependenciesChanged -> return $ RunResult ChangedRecomputeSame "" () (return ())
27+
RunDependenciesSame -> return $ RunResult ChangedNothing "" () (return ())
28+
2329
ruleUnit :: Rules ()
2430
ruleUnit = addRule $ \(Rule :: Rule ()) _old _mode -> do
2531
return $ RunResult ChangedRecomputeDiff "" () (return ())
@@ -62,3 +68,7 @@ ruleSubBranch :: C.MVar Int -> Rules ()
6268
ruleSubBranch mv = addRule $ \SubBranchRule _old _mode -> do
6369
r <- liftIO $ C.modifyMVar mv $ \x -> return (x+1, x)
6470
return $ RunResult ChangedRecomputeDiff "" r (return ())
71+
72+
data CountRule = CountRule
73+
deriving (Eq, Generic, Hashable, NFData, Show, Typeable)
74+
type instance RuleResult CountRule = Int

0 commit comments

Comments
 (0)