@@ -14,9 +14,13 @@ import Control.Concurrent.Async
14
14
import Control.Concurrent.Extra
15
15
import Control.Exception
16
16
import Control.Monad
17
+ import Control.Monad.Trans.Class (lift )
17
18
import Control.Monad.Trans.Reader
19
+ import qualified Control.Monad.Trans.State.Strict as State
18
20
import Data.Dynamic
19
21
import Data.Either
22
+ import Data.Foldable (traverse_ )
23
+ import qualified Data.HashSet as HSet
20
24
import Data.IORef.Extra
21
25
import Data.Maybe
22
26
import Data.Tuple.Extra
@@ -36,6 +40,11 @@ newDatabase databaseExtra databaseRules = do
36
40
databaseLock <- newLock
37
41
databaseIds <- newIORef Intern. empty
38
42
databaseValues <- Ids. empty
43
+ reverseDepsClean <- newIORef mempty
44
+ reverseDepsDirty <- newIORef mempty
45
+ reverseDeps <- Ids. empty
46
+ let reverseDepsAllDirty = False
47
+ let databaseReverseDeps = ReverseDeps {.. }
39
48
pure Database {.. }
40
49
41
50
-- | Increment the step and mark all ids dirty
@@ -119,13 +128,19 @@ cleanupAsync ref = mapConcurrently_ uninterruptibleCancel =<< readIORef ref
119
128
-- | Check if we need to run the database.
120
129
check :: Database -> Key -> Id -> Maybe Result -> IO Result
121
130
check db key id result@ (Just me@ Result {resultDeps= Just deps}) = do
122
- res <- builder db $ map Left deps
123
- let dirty = any (\ (_,dep) -> resultBuilt me < resultChanged dep) res
124
- let mode = if dirty then Shake. RunDependenciesChanged else Shake. RunDependenciesSame
131
+ amDirty <- isDirty db id
132
+ mode <- if amDirty
133
+ -- Event if I am dirty, it is still possible that all my dependencies are unchanged
134
+ -- thanks to early cutoff, and therefore we must check to avoid redundant work
135
+ then do
136
+ res <- builder db $ map Left deps
137
+ let dirty = any (\ (_,dep) -> resultBuilt me < resultChanged dep) res
138
+ return $ if dirty then Shake. RunDependenciesChanged else Shake. RunDependenciesSame
139
+ -- If I am not dirty then none of my dependencies are, so they must be unchanged
140
+ else return Shake. RunDependenciesSame
125
141
spawn db key id mode result
126
142
check db key id result = spawn db key id Shake. RunDependenciesChanged result
127
143
128
-
129
144
-- | Spawn a new computation to run the action.
130
145
spawn :: Database -> Key -> Id -> Shake. RunMode -> Maybe Result -> IO Result
131
146
spawn db@ Database {.. } key id mode result = do
@@ -136,10 +151,12 @@ spawn db@Database{..} key id mode result = do
136
151
deps <- readIORef deps
137
152
let changed = if runChanged == Shake. ChangedRecomputeDiff then built else maybe built resultChanged result
138
153
-- only update the deps when the rule ran with changes
139
- let actual_deps = if runChanged /= Shake. ChangedNothing then deps else previousDeps
154
+ let actualDeps = if runChanged /= Shake. ChangedNothing then deps else previousDeps
140
155
previousDeps= resultDeps =<< result
141
- let res = Result runValue built changed actual_deps runStore
142
- withLock databaseLock $
156
+ let res = Result runValue built changed actualDeps runStore
157
+ withLock databaseLock $ do
158
+ unmarkDirty db id
159
+ updateReverseDeps id db (fromMaybe [] previousDeps) (fromMaybe [] actualDeps)
143
160
Ids. insert databaseValues id (key, Clean res)
144
161
pure res
145
162
@@ -151,3 +168,60 @@ splitIO act = do
151
168
let act2 = Box <$> act
152
169
let res = unsafePerformIO act2
153
170
(void $ evaluate res, fromBox res)
171
+
172
+ --------------------------------------------------------------------------------
173
+ -- Reverse dependencies
174
+
175
+ -- | Update the reverse dependencies of an Id
176
+ updateReverseDeps
177
+ :: Id -- ^ Id
178
+ -> Database
179
+ -> [Id ] -- ^ Previous direct dependencies of Id
180
+ -> [Id ] -- ^ Current direct dependencies of Id
181
+ -> IO ()
182
+ updateReverseDeps myId db prev new = do
183
+ forM_ prev $ doOne (HSet. delete myId)
184
+ forM_ new $ doOne (HSet. insert myId)
185
+ where
186
+ doOne f id = do
187
+ rdeps <- getReverseDependencies db id
188
+ Ids. insert (reverseDeps $ databaseReverseDeps db) id (f $ fromMaybe mempty rdeps)
189
+
190
+ getReverseDependencies :: Database -> Id -> IO (Maybe (HSet. HashSet Id ))
191
+ getReverseDependencies db = Ids. lookup (reverseDeps $ databaseReverseDeps db)
192
+
193
+ markDirty :: Database -> HSet. HashSet Id -> IO ()
194
+ markDirty Database {databaseReverseDeps} ids =
195
+ atomicModifyIORef'_ (reverseDepsDirty databaseReverseDeps) $ HSet. union ids
196
+
197
+ unmarkDirty :: Database -> Id -> IO ()
198
+ unmarkDirty Database {databaseReverseDeps} i = do
199
+ atomicModifyIORef'_ (reverseDepsClean databaseReverseDeps) $ HSet. insert i
200
+
201
+ flushDirty :: Database -> IO ()
202
+ flushDirty Database {databaseReverseDeps} = do
203
+ cleanIds <- atomicModifyIORef' (reverseDepsClean databaseReverseDeps) (mempty ,)
204
+ atomicModifyIORef'_ (reverseDepsDirty databaseReverseDeps) (`HSet.difference` cleanIds)
205
+
206
+ isDirty :: Database -> Id -> IO Bool
207
+ isDirty db@ Database {databaseReverseDeps} id
208
+ | reverseDepsAllDirty databaseReverseDeps = pure True
209
+ | otherwise =
210
+ HSet. member id <$> getDirtySet db
211
+
212
+ getDirtySet :: Database -> IO (HSet. HashSet Id )
213
+ getDirtySet db = readIORef (reverseDepsDirty $ databaseReverseDeps db)
214
+
215
+ -- | Transitively expand the dirty set
216
+ updateDirtySet :: Database -> IO ()
217
+ updateDirtySet database = do
218
+ let loop x = do
219
+ seen <- State. get
220
+ if x `HSet.member` seen then pure () else do
221
+ State. put (HSet. insert x seen)
222
+ next <- lift $ getReverseDependencies database x
223
+ traverse_ loop (fromMaybe mempty next)
224
+ ids <- getDirtySet database
225
+ transitive <- flip State. execStateT HSet. empty $ traverse_ loop ids
226
+
227
+ markDirty database transitive
0 commit comments