Speed up finding dependency cycles

parallelVisit supports mutating the dependency graph while it is being
visited by proceeding until there are no modules with their dependencies
satisfied, and then checking if there are modules that haven't been
visited yet.  If so, it assumes there was a newly introduced dependency
cycle and tries to find it to return as an error.

Finding the dependency cycle could traverse outside of the cycle.
If the dependency cycle occurs near the bottom of the dependency graph,
that traversal could be both long and wide, leading to very long
runtimes.

Memoize traversed modules that were not found to be part of the
dependency cycle to prevent repeated traversals.

Fixes: 186572387
Test: introduce cycle into libc, m nothing
Test: Test_parallelVisit
Change-Id: I38d0749dbedffbe8a39e433d97fbe08486451321
This commit is contained in:
Colin Cross 2021-04-27 15:20:15 -07:00
parent 9021eef07b
commit 9793b0a5e0

View file

@ -2081,6 +2081,11 @@ func parallelVisit(modules []*moduleInfo, order visitOrderer, limit int,
// modules to the modules that would have been unblocked when that module finished, i.e
// the reverse of the visitOrderer.
// In order to reduce duplicated work, once a module has been checked and determined
// not to be part of a cycle add it and everything that depends on it to the checked
// map.
checked := make(map[*moduleInfo]struct{})
var check func(module, end *moduleInfo) []*moduleInfo
check = func(module, end *moduleInfo) []*moduleInfo {
if module.waitingCount == -1 {
@ -2092,6 +2097,10 @@ func parallelVisit(modules []*moduleInfo, order visitOrderer, limit int,
return []*moduleInfo{module}
}
if _, alreadyChecked := checked[module]; alreadyChecked {
return nil
}
for _, dep := range order.propagate(module) {
cycle := check(dep, end)
if cycle != nil {
@ -2105,6 +2114,7 @@ func parallelVisit(modules []*moduleInfo, order visitOrderer, limit int,
}
}
checked[module] = struct{}{}
return nil
}