Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow failures of indirect dependencies during parallel precompillation #2021

Merged
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1498ae0
provide parallel kwarg in precompile to select previous behavior
IanButterworth Sep 15, 2020
0932b35
only throw errors during precomp of top-level deps
IanButterworth Sep 16, 2020
7d54ce8
hide stderr
IanButterworth Sep 16, 2020
80077bc
remove option to revert to old precompile method
IanButterworth Sep 17, 2020
7e9c1b2
use the term "indirect dependency"
IanButterworth Sep 17, 2020
e227bc2
don't ignore stdlib if not loaded
IanButterworth Sep 17, 2020
b842842
rework around single depsmap
IanButterworth Sep 18, 2020
19a7d01
suggestion
IanButterworth Sep 18, 2020
8289dc3
Apply suggestions from code review
IanButterworth Sep 18, 2020
e1af932
switch to Base.is_sysimage
IanButterworth Sep 18, 2020
f84924a
tweak error reporting
IanButterworth Sep 21, 2020
7765612
Trigger CI
IanButterworth Sep 21, 2020
79d976a
Trigger CI
IanButterworth Sep 22, 2020
309044b
Trigger CI
IanButterworth Sep 22, 2020
039f64e
Trigger CI
IanButterworth Sep 22, 2020
8fecd9a
Apply suggestions from code review
IanButterworth Sep 22, 2020
87e540b
Update src/API.jl
IanButterworth Sep 22, 2020
86f6364
Update src/API.jl
tkf Sep 22, 2020
88b5a71
use single tomlcache during sequential locate_package calls
IanButterworth Sep 22, 2020
18c5351
avoid deadlock when __precompile__(false)
IanButterworth Sep 22, 2020
2a5c814
use tomlcache during async is_stale check
IanButterworth Sep 22, 2020
3affc9f
make _is_stale a standalone function
IanButterworth Sep 22, 2020
8cada29
remove unnecessary wait
IanButterworth Sep 22, 2020
17a0934
remove unnecessary type declaration
IanButterworth Sep 23, 2020
6270541
Merge branch 'master' into ib/parallel_precomp_optional
IanButterworth Sep 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 47 additions & 30 deletions src/API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -894,71 +894,88 @@ end

precompile() = precompile(Context())
function precompile(ctx::Context)

function is_stale(paths, sourcepath)
toml_c = Base.TOMLCache()
for path_to_try in paths::Vector{String}
staledeps = Base.stale_cachefile(sourcepath, path_to_try, toml_c)
staledeps === true ? continue : return false
end
return true
end
IanButterworth marked this conversation as resolved.
Show resolved Hide resolved

printpkgstyle(ctx, :Precompiling, "project...")

num_tasks = parse(Int, get(ENV, "JULIA_NUM_PRECOMPILE_TASKS", string(Sys.CPU_THREADS + 1)))
parallel_limiter = Base.Semaphore(num_tasks)

direct_deps = [
Base.PkgId(uuid, name)
for (name, uuid) in ctx.env.project.deps if !Base.in_sysimage(Base.PkgId(uuid, name))
]

man = Pkg.Types.read_manifest(ctx.env.manifest_file)
pkgids = [Base.PkgId(first(dep), last(dep).name) for dep in man if !Pkg.Operations.is_stdlib(first(dep))]
pkg_dep_uuid_lists = [collect(values(last(dep).deps)) for dep in man if !Pkg.Operations.is_stdlib(first(dep))]
filter!.(!is_stdlib, pkg_dep_uuid_lists)
deps_pair_or_nothing = Iterators.map(man) do dep
pkg = Base.PkgId(first(dep), last(dep).name)
Base.in_sysimage(pkg) && return nothing
deps = [Base.PkgId(last(x), first(x)) for x in last(dep).deps]
return pkg => filter!(!Base.in_sysimage, deps)
end
depsmap = Dict(Iterators.filter(!isnothing, deps_pair_or_nothing)) #flat map of each dep and its deps

if ctx.env.pkg !== nothing && isfile( joinpath( dirname(ctx.env.project_file), "src", ctx.env.pkg.name * ".jl") )
push!(pkgids, Base.PkgId(ctx.env.pkg.uuid, ctx.env.pkg.name))
push!(pkg_dep_uuid_lists, collect(keys(ctx.env.project.deps)))
depsmap[Base.PkgId(ctx.env.pkg.uuid, ctx.env.pkg.name)] = [
Base.PkgId(last(x), first(x))
for x in ctx.env.project.deps if !Base.in_sysimage(Base.PkgId(last(x), first(x)))
]
end

was_processed = Dict{Base.UUID,Base.Event}()
was_recompiled = Dict{Base.UUID,Bool}()
for pkgid in pkgids
was_processed[pkgid.uuid] = Base.Event()
was_recompiled[pkgid.uuid] = false
end

function is_stale(paths, sourcepath)
for path_to_try in paths::Vector{String}
staledeps = Base.stale_cachefile(sourcepath, path_to_try, Base.TOMLCache())
staledeps === true && continue
return false
end
return true
was_processed = Dict{Base.PkgId,Base.Event}()
was_recompiled = Dict{Base.PkgId,Bool}()
for pkgid in keys(depsmap)
was_processed[pkgid] = Base.Event()
was_recompiled[pkgid] = false
end

errored = false
@sync for (i, pkg) in pairs(pkgids)
@sync for (pkg, deps) in depsmap
paths = Base.find_all_in_cache_path(pkg)
sourcepath = Base.locate_package(pkg)
sourcepath === nothing && continue
# Heuristic for when precompilation is disabled
occursin(r"\b__precompile__\(\s*false\s*\)", read(sourcepath, String)) && continue
IanButterworth marked this conversation as resolved.
Show resolved Hide resolved

@async begin
for dep_uuid in pkg_dep_uuid_lists[i] # wait for deps to finish
wait(was_processed[dep_uuid])
for dep in deps # wait for deps to finish
wait(was_processed[dep])
end

# skip stale checking and force compilation if any dep was recompiled in this session
any_dep_recompiled = any(map(dep_uuid->was_recompiled[dep_uuid], pkg_dep_uuid_lists[i]))
any_dep_recompiled = any(map(dep->was_recompiled[dep], deps))
if !errored && (any_dep_recompiled || is_stale(paths, sourcepath))
Base.acquire(parallel_limiter)
if errored # catch things queued before error occurred
notify(was_processed[pkg.uuid])
notify(was_processed[pkg])
Base.release(parallel_limiter)
return
end
is_direct_dep = pkg in direct_deps
try
Base.compilecache(pkg, sourcepath)
was_recompiled[pkg.uuid] = true
was_recompiled[pkg] = true
Base.compilecache(pkg, sourcepath, is_direct_dep) # don't print errors from indirect deps
catch err
errored = true
throw(err)
if is_direct_dep # only throw errors for direct dependencies (in Project)
errored = true
throw(err)
else
@warn "Precompilation failed for indirect dependency $(pkg)"
end
finally
notify(was_processed[pkg.uuid])
notify(was_processed[pkg])
Base.release(parallel_limiter)
end
else
notify(was_processed[pkg.uuid])
notify(was_processed[pkg])
end
end
end
Expand Down
5 changes: 4 additions & 1 deletion src/Pkg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ const add = API.add
"""
Pkg.precompile()

Precompile all the dependencies of the project.
Precompile all the dependencies of the project in parallel.
!!! note
Errors will only throw when precompiling the top-level dependencies, given that
not all manifest dependencies may be loaded by the top-level dependencies on the given system.

!!! compat "Julia 1.3"
This function requires at least Julia 1.3. On earlier versions
Expand Down