Skip to content

Commit

Permalink
Prevent extensions from blocking parallel pre-compilation
Browse files Browse the repository at this point in the history
Previously our precompilation code was causing any dependencies of a
package A to wait on all of A's weakdeps to finish pre-compiling,
even if it can't actually load those weakdeps (or the extension itself)

This would lead to a pre-compile ordering like:
```
A        B
 \      / \
  Ext AB   \
     |     /
     C    /
      \  /
       D
```

Here, extension `C` cannot pre-compile in parallel with `Ext {A,B}` and
`B`, because it has to wait for `Ext {A,B}` to finish pre-compiling.
That happens even though `C` has no way to load either of these.

This change updates the pre-compile ordering to be more parallel,
reflecting the true place where `Ext {A,B}` can be loaded:
```
  A       B
 / \     / \
C   Ext AB  |
 \    |    /
  \-- D --/
```

which allows `C` to compile in parallel with `B` and `Ext{A,B}`
  • Loading branch information
topolarity committed Nov 20, 2024
1 parent 8a2abe1 commit ccd969b
Showing 1 changed file with 41 additions and 7 deletions.
48 changes: 41 additions & 7 deletions base/precompilation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -490,14 +490,48 @@ function _precompilepkgs(pkgs::Vector{String},
end
end

# A package depends on an extension if it (indirectly) depends on all extension triggers
function expand_indirect_dependencies(direct_deps)
function visit!(visited, node, all_deps)
if node in visited
return
end
push!(visited, node)
for dep in get(Set{Base.PkgId}, direct_deps, node)
if !(dep in all_deps)
push!(all_deps, dep)
visit!(visited, dep, all_deps)
end
end
end

indirect_deps = Dict{Base.PkgId, Set{Base.PkgId}}()
for package in keys(direct_deps)
# Initialize a set to keep track of all dependencies for 'package'
all_deps = Set{Base.PkgId}()
visited = Set{Base.PkgId}()
visit!(visited, package, all_deps)
# Update direct_deps with the complete set of dependencies for 'package'
indirect_deps[package] = all_deps
end
return indirect_deps
end

# this loop must be run after the full direct_deps map has been populated
for (pkg, pkg_exts) in parent_to_exts
# find any packages that depend on the extension(s)'s deps and replace those deps in their deps list with the extension(s),
# basically injecting the extension into the precompile order in the graph, to avoid race to precompile extensions
for (_pkg, deps) in direct_deps # for each manifest dep
if !in(_pkg, keys(ext_to_parent)) && pkg in deps # if not an extension and depends on pkg
append!(deps, pkg_exts) # add the package extensions to deps
filter!(!isequal(pkg), deps) # remove the pkg from deps
indirect_deps = expand_indirect_dependencies(direct_deps)
for ext in keys(ext_to_parent)
ext_loadable_in_pkg = Dict{Base.PkgId,Bool}()
for pkg in keys(direct_deps)
is_trigger = in(pkg, direct_deps[ext])
is_extension = in(pkg, keys(ext_to_parent))
has_triggers = issubset(direct_deps[ext], indirect_deps[pkg])
ext_loadable_in_pkg[pkg] = !is_extension && has_triggers && !is_trigger
end
for (pkg, ext_loadable) in ext_loadable_in_pkg
if ext_loadable && !any((dep)->ext_loadable_in_pkg[dep], direct_deps[pkg])
# add an edge if the extension is loadable by pkg, and was not loadable in any
# of the pkg's dependencies
push!(direct_deps[pkg], ext)
end
end
end
Expand Down

0 comments on commit ccd969b

Please sign in to comment.