7
7
{-# LANGUAGE ScopedTypeVariables #-}
8
8
{-# LANGUAGE TupleSections #-}
9
9
{-# LANGUAGE TypeFamilies #-}
10
+ {-# LANGUAGE ViewPatterns #-}
10
11
11
12
module Development.IDE.Graph.Internal.Database where
12
13
13
14
import Control.Concurrent.Async
14
15
import Control.Concurrent.Extra
15
16
import Control.Exception
16
17
import Control.Monad
18
+ import Control.Monad.Trans.Class (lift )
17
19
import Control.Monad.Trans.Reader
20
+ import qualified Control.Monad.Trans.State.Strict as State
18
21
import Data.Dynamic
19
22
import Data.Either
23
+ import Data.Foldable (traverse_ )
20
24
import Data.IORef.Extra
25
+ import Data.IntSet (IntSet )
26
+ import qualified Data.IntSet as Set
21
27
import Data.Maybe
22
28
import Data.Tuple.Extra
23
29
import qualified Development.IDE.Graph.Internal.Ids as Ids
@@ -36,17 +42,31 @@ newDatabase databaseExtra databaseRules = do
36
42
databaseLock <- newLock
37
43
databaseIds <- newIORef Intern. empty
38
44
databaseValues <- Ids. empty
45
+ databaseReverseDeps <- Ids. empty
46
+ databaseReverseDepsLock <- newLock
39
47
pure Database {.. }
40
48
41
- -- | Increment the step and mark all ids dirty
42
- incDatabase :: Database -> IO ()
43
- incDatabase db = do
44
- modifyIORef' (databaseStep db) $ \ ( Step i) -> Step $ i + 1
49
+ -- | Increment the step and mark dirty
50
+ incDatabase :: Database -> Maybe [ Key ] -> IO ()
51
+ -- all keys are dirty
52
+ incDatabase db Nothing =
45
53
withLock (databaseLock db) $
46
- Ids. forMutate (databaseValues db) $ second $ \ case
54
+ Ids. forMutate (databaseValues db) $ \ _ -> second $ \ case
47
55
Clean x -> Dirty (Just x)
48
56
Dirty x -> Dirty x
49
57
Running _ x -> Dirty x
58
+ -- only some keys are dirty
59
+ incDatabase db (Just kk) = do
60
+ modifyIORef' (databaseStep db) $ \ (Step i) -> Step $ i + 1
61
+ intern <- readIORef (databaseIds db)
62
+ let dirtyIds = mapMaybe (`Intern.lookup` intern) kk
63
+ transitiveDirtyIds <- transitiveDirtySet db dirtyIds
64
+ withLock (databaseLock db) $
65
+ Ids. forMutate (databaseValues db) $ \ i -> \ case
66
+ (k, Running _ x) -> (k, Dirty x)
67
+ (k, Clean x) | i `Set.member` transitiveDirtyIds ->
68
+ (k, Dirty (Just x))
69
+ other -> other
50
70
51
71
52
72
-- | Unwrap and build a list of keys in parallel
@@ -116,17 +136,17 @@ builder db@Database{..} keys = do
116
136
cleanupAsync :: IORef [Async a ] -> IO ()
117
137
cleanupAsync ref = mapConcurrently_ uninterruptibleCancel =<< readIORef ref
118
138
119
-
120
139
-- | Check if we need to run the database.
121
140
check :: Database -> Key -> Id -> Maybe Result -> IO Result
122
141
check db key id result@ (Just me@ Result {resultDeps= Just deps}) = do
123
- res <- builder db $ map Left deps
124
- let dirty = any (\ (_,dep) -> resultBuilt me < resultChanged dep) res
125
- let mode = if dirty then Shake. RunDependenciesChanged else Shake. RunDependenciesSame
142
+ mode <- do
143
+ res <- builder db (map Left deps)
144
+ let dirty = any (\ (_,dep) -> resultBuilt me < resultChanged dep) res
145
+ return $ if dirty then Shake. RunDependenciesChanged else Shake. RunDependenciesSame
146
+ -- If I am not dirty then none of my dependencies are, so they must be unchanged
126
147
spawn db key id mode result
127
148
check db key id result = spawn db key id Shake. RunDependenciesChanged result
128
149
129
-
130
150
-- | Spawn a new computation to run the action.
131
151
spawn :: Database -> Key -> Id -> Shake. RunMode -> Maybe Result -> IO Result
132
152
spawn db@ Database {.. } key id mode result = do
@@ -137,10 +157,11 @@ spawn db@Database{..} key id mode result = do
137
157
deps <- readIORef deps
138
158
let changed = if runChanged == Shake. ChangedRecomputeDiff then built else maybe built resultChanged result
139
159
-- only update the deps when the rule ran with changes
140
- let actual_deps = if runChanged /= Shake. ChangedNothing then deps else previousDeps
160
+ let actualDeps = if runChanged /= Shake. ChangedNothing then deps else previousDeps
141
161
previousDeps= resultDeps =<< result
142
- let res = Result runValue built changed actual_deps runStore
143
- withLock databaseLock $
162
+ let res = Result runValue built changed actualDeps runStore
163
+ withLock databaseLock $ do
164
+ updateReverseDeps id db (fromMaybe [] previousDeps) (fromMaybe [] actualDeps)
144
165
Ids. insert databaseValues id (key, Clean res)
145
166
pure res
146
167
@@ -152,3 +173,41 @@ splitIO act = do
152
173
let act2 = Box <$> act
153
174
let res = unsafePerformIO act2
154
175
(void $ evaluate res, fromBox res)
176
+
177
+ --------------------------------------------------------------------------------
178
+ -- Reverse dependencies
179
+
180
+ -- | Update the reverse dependencies of an Id
181
+ updateReverseDeps
182
+ :: Id -- ^ Id
183
+ -> Database
184
+ -> [Id ] -- ^ Previous direct dependencies of Id
185
+ -> [Id ] -- ^ Current direct dependencies of Id
186
+ -> IO ()
187
+ updateReverseDeps myId db prev new = do
188
+ forM_ prev $ doOne (Set. delete $ idToInt myId)
189
+ forM_ new $ doOne (Set. insert $ idToInt myId)
190
+ where
191
+ doOne f id = withLock (databaseReverseDepsLock db) $ do
192
+ rdeps <- getReverseDependencies db id
193
+ Ids. insert (databaseReverseDeps db) id (f $ fromMaybe mempty rdeps)
194
+
195
+ idToInt :: Id -> Int
196
+ idToInt = id
197
+
198
+ getReverseDependencies :: Database -> Id -> IO (Maybe (IntSet ))
199
+ getReverseDependencies db = Ids. lookup (databaseReverseDeps db)
200
+
201
+ transitiveDirtySet :: Foldable t => Database -> t Id -> IO IntSet
202
+ transitiveDirtySet database = flip State. execStateT Set. empty . traverse_ loop
203
+ where
204
+ loop (idToInt -> x) = do
205
+ seen <- State. get
206
+ if x `Set.member` seen then pure () else do
207
+ State. put (Set. insert x seen)
208
+ next <- lift $ getReverseDependencies database x
209
+ traverse_ loop (maybe mempty Set. toList next)
210
+
211
+
212
+ idFromInt :: Set. Key -> Id
213
+ idFromInt = id
0 commit comments