Skip to content

Commit fcdd78b

Browse files
committed
Prevent extensions from blocking parallel pre-compilation
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}`
1 parent 6fe8e88 commit fcdd78b

File tree

1 file changed

+41
-7
lines changed

1 file changed

+41
-7
lines changed

base/precompilation.jl

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -439,14 +439,48 @@ function precompilepkgs(pkgs::Vector{String}=String[];
439439
append!(project_deps, keys(filter(d->last(d).name in keys(env.project_deps), ext_to_parent)))
440440

441441
@debug "precompile: deps collected"
442+
443+
# An extension effectively depends on another extension if it has all the the
444+
# dependencies of that other extension
445+
function expand_indirect_dependencies(direct_deps)
446+
function visit!(visited, node, all_deps)
447+
if node in visited
448+
return
449+
end
450+
push!(visited, node)
451+
for dep in get(Set{Base.PkgId}, direct_deps, node)
452+
if !(dep in all_deps)
453+
push!(all_deps, dep)
454+
visit!(visited, dep, all_deps)
455+
end
456+
end
457+
end
458+
459+
indirect_deps = Dict{Base.PkgId, Set{Base.PkgId}}()
460+
for package in keys(direct_deps)
461+
# Initialize a set to keep track of all dependencies for 'package'
462+
all_deps = Set{Base.PkgId}()
463+
visited = Set{Base.PkgId}()
464+
visit!(visited, package, all_deps)
465+
# Update direct_deps with the complete set of dependencies for 'package'
466+
indirect_deps[package] = all_deps
467+
end
468+
return indirect_deps
469+
end
470+
442471
# this loop must be run after the full direct_deps map has been populated
443-
for (pkg, pkg_exts) in parent_to_exts
444-
# find any packages that depend on the extension(s)'s deps and replace those deps in their deps list with the extension(s),
445-
# basically injecting the extension into the precompile order in the graph, to avoid race to precompile extensions
446-
for (_pkg, deps) in direct_deps # for each manifest dep
447-
if !in(_pkg, keys(ext_to_parent)) && pkg in deps # if not an extension and depends on pkg
448-
append!(deps, pkg_exts) # add the package extensions to deps
449-
filter!(!isequal(pkg), deps) # remove the pkg from deps
472+
indirect_deps = expand_indirect_dependencies(direct_deps)
473+
for (ext, parent) in ext_to_parent
474+
ext_loadable_in_pkg = Dict{Base.PkgId,Bool}()
475+
for pkg in keys(direct_deps)
476+
pkg_is_extension = in(pkg, keys(ext_to_parent))
477+
ext_loadable_in_pkg[pkg] = !pkg_is_extension && pkg != parent && issubset(direct_deps[ext], indirect_deps[pkg])
478+
end
479+
for (pkg, ext_loadable) in ext_loadable_in_pkg
480+
if ext_loadable && !any((dep)->ext_loadable_in_pkg[dep], direct_deps[pkg])
481+
# add an edge if the extension is loadable by pkg, and was not loadable in any
482+
# of the pkg's dependencies
483+
push!(direct_deps[pkg], ext)
450484
end
451485
end
452486
end

0 commit comments

Comments
 (0)