Skip to content

hls-graph speculatively builds all prior dependencies for a key even if they might have changed #3423

Closed
@wz1000

Description

@wz1000

I ran into this bug while debugging a flaky test case -

A.hs is initially not an FOI, so GetModIface A.hs has two dependencies IsFileOfInterest A.hs and GetModIfaceFromDiskAndIndex A.hs. Then A.hs is opened, turning it into an FOI

getModIfaceRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModIface f -> do
fileOfInterest <- use_ IsFileOfInterest f
res@(_,(_,mhmi)) <- case fileOfInterest of
IsFOI status -> do
-- Never load from disk for files of interest
tmr <- use_ TypeCheck f
linkableType <- getLinkableType f
hsc <- hscEnv <$> use_ GhcSessionDeps f
let compile = fmap ([],) $ use GenerateCore f
se <- getShakeExtras
(diags, !hiFile) <- writeCoreFileIfNeeded se hsc linkableType compile tmr
let fp = hiFileFingerPrint <$> hiFile
hiDiags <- case hiFile of
Just hiFile
| OnDisk <- status
, not (tmrDeferredError tmr) -> liftIO $ writeHiFile se hsc hiFile
_ -> pure []
return (fp, (diags++hiDiags, hiFile))
NotFOI -> do
hiFile <- use GetModIfaceFromDiskAndIndex f
let fp = hiFileFingerPrint <$> hiFile
return (fp, ([], hiFile))
pure res

At this point A.hs should have dependencies IsFileOfInterest A.hs, Typecheck A.hs ... (the IsFOI branch). However, I was seeing GetModIfaceFromDiskAndIndex still getting run for A.hs.

Investigating further, it turns out to be the implementation of refresh in hls-graph:

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

It seems like it speculatively builds all the previous dependencies of the key even if they might have changed. This is wrong as rules can have side effects or require preconditions (GetModIfaceFromDiskAndIndex has both). This also can lead to spurious diagnostics from these pseudo-dependencies.

We should instead build dependencies in the order that they occurred (which we currently do not maintain), and recompute with RunDependenciesChanged if any of them are dirty, short circuiting the evaluation of later dependencies (since they might not be true dependencies). However, this might have a negative impact on performance.

In the above case, that would mean skipping GetModIfaceFromDiskAndIndex after we discover that IsFOI has changed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    performanceIssues about memory consumption, responsiveness, etc.status: in discussionNot actionable, because discussion is still ongoing or there's no decision yettype: bugSomething isn't right: doesn't work as intended, documentation is missing/outdated, etc..

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions