Skip to content

Commit 3a80bab

Browse files
committed
precompile: fix error / output printing
I counted at least 4 different places where an (overlapping) subset of package IO might be printed. Now try to be slightly more careful about that: progress goes to logio (either io or devnull) while output goes to io always. Additionally, we don't return an extra copy of some output in the error message (since we already printed that). Additionally, we print all output if the requested packages failed, but we print only output from successful package if all requested packages succeeded. If a package was successful, then its output is required to be printed since it will not be regenerated in the future (we don't reprint precompile output on loading since that might be obnoxious for us to implement). If a required package is successful however, none of the failures mattered and they will be regenerated in the future when they are required, so those should be filtered out.
1 parent 4a4c4a9 commit 3a80bab

File tree

4 files changed

+122
-108
lines changed

4 files changed

+122
-108
lines changed

base/precompilation.jl

Lines changed: 94 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ function excluded_circular_deps_explanation(io::IOContext{IO}, ext_to_parent::Di
406406
else
407407
line = "" * "" ^j * " "
408408
end
409-
hascolor = get(io, :color, false)::Bool
409+
hascolor = get(io, :color, false)::Bool # XXX: this output does not go to `io` so this is bad to call here
410410
line = _color_string(line, :light_black, hascolor) * full_name(ext_to_parent, pkg) * "\n"
411411
cycle_str *= line
412412
end
@@ -482,14 +482,17 @@ empty (default), precompiles all project dependencies. When specified,
482482
precompiles only the given packages and their dependencies (unless
483483
`manifest=true`).
484484
485+
!!! note
486+
Errors will only throw when precompiling the top-level dependencies, given that
487+
not all manifest dependencies may be loaded by the top-level dependencies on the given system.
488+
This can be overridden to make errors in all dependencies throw by setting the kwarg `strict` to `true`
489+
485490
# Keyword Arguments
486-
- `internal_call::Bool`: Indicates this is an automatic/internal precompilation call
487-
(e.g., triggered by package loading). When `true`, errors are handled gracefully: in
488-
interactive sessions, errors are stored in `Base.MainInclude.err` instead of throwing;
489-
in non-interactive sessions, errors are printed but not thrown. Default: `false`.
491+
- `internal_call::Bool`: Indicates this is an automatic precompilation call
492+
from somewhere external (e.g. Pkg). Do not use this parameter.
490493
491494
- `strict::Bool`: Controls error reporting scope. When `false` (default), only reports
492-
errors for direct project dependencies.
495+
errors for direct project dependencies. Only relevant when `manifest=true`.
493496
494497
- `warn_loaded::Bool`: When `true` (default), checks for and warns about packages that are
495498
precompiled but already loaded with a different version. Displays a warning that Julia
@@ -564,7 +567,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
564567
timing::Bool,
565568
_from_loading::Bool,
566569
configs::Vector{Config},
567-
_io::IOContext{IO},
570+
io::IOContext{IO},
568571
fancyprint::Bool,
569572
manifest::Bool,
570573
ignore_loaded::Bool)
@@ -602,16 +605,18 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
602605
# suppress precompilation progress messages when precompiling for loading packages, except during interactive sessions
603606
# or when specified by logging heuristics that explicitly require it
604607
# since the complicated IO implemented here can have somewhat disastrous consequences when happening in the background (e.g. #59599)
605-
io = _io
608+
logio = io
606609
logcalls = nothing
607-
if _from_loading && !isinteractive()
608-
io = IOContext{IO}(devnull)
609-
fancyprint = false
610+
if _from_loading
611+
if !isinteractive()
612+
logio = IOContext{IO}(devnull)
613+
fancyprint = false
614+
end
610615
logcalls = isinteractive() ? CoreLogging.Info : CoreLogging.Debug # sync with Base.compilecache
611616
end
612617

613618
nconfigs = length(configs)
614-
hascolor = get(io, :color, false)::Bool
619+
hascolor = get(logio, :color, false)::Bool
615620
color_string(cstr::String, col::Union{Int64, Symbol}) = _color_string(cstr, col, hascolor)
616621

617622
stale_cache = Dict{StaleCacheKey, Bool}()
@@ -901,9 +906,10 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
901906
pkg_liveprinted = Ref{Union{Nothing, PkgId}}(nothing)
902907

903908
function monitor_std(pkg_config, pipe; single_requested_pkg=false)
904-
pkg, config = pkg_config
909+
local pkg, config = pkg_config
905910
try
906-
liveprinting = false
911+
local liveprinting = false
912+
local thistaskwaiting = false
907913
while !eof(pipe)
908914
local str = readline(pipe, keep=true)
909915
if single_requested_pkg && (liveprinting || !isempty(str))
@@ -916,15 +922,18 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
916922
end
917923
end
918924
write(get!(IOBuffer, std_outputs, pkg_config), str)
919-
if !in(pkg_config, taskwaiting) && occursin("waiting for IO to finish", str)
920-
!fancyprint && @lock print_lock begin
921-
println(io, pkg.name, color_string(" Waiting for background task / IO / timer.", Base.warn_color()))
925+
if thistaskwaiting
926+
if occursin("Waiting for background task / IO / timer", str)
927+
thistaskwaiting = true
928+
!liveprinting && !fancyprint && @lock print_lock begin
929+
println(io, pkg.name, color_string(str, Base.warn_color()))
930+
end
931+
push!(taskwaiting, pkg_config)
922932
end
923-
push!(taskwaiting, pkg_config)
924-
end
925-
if !fancyprint && in(pkg_config, taskwaiting)
926-
@lock print_lock begin
927-
print(io, str)
933+
else
934+
# XXX: don't just re-enable IO for random packages without printing the context for them first
935+
!liveprinting && !fancyprint && @lock print_lock begin
936+
print(io, ansi_cleartoendofline, str)
928937
end
929938
end
930939
end
@@ -940,10 +949,10 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
940949
(isempty(pkg_queue) || interrupted_or_done[]) && return
941950
@lock print_lock begin
942951
if target[] !== nothing
943-
printpkgstyle(io, :Precompiling, target[])
952+
printpkgstyle(logio, :Precompiling, target[])
944953
end
945954
if fancyprint
946-
print(io, ansi_disablecursor)
955+
print(logio, ansi_disablecursor)
947956
end
948957
end
949958
t = Timer(0; interval=1/10)
@@ -957,7 +966,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
957966
n_print_rows = 0
958967
while !printloop_should_exit[]
959968
@lock print_lock begin
960-
term_size = displaysize(io)::Tuple{Int, Int}
969+
term_size = displaysize(logio)::Tuple{Int, Int}
961970
num_deps_show = max(term_size[1] - 3, 2) # show at least 2 deps
962971
pkg_queue_show = if !interrupted_or_done[] && length(pkg_queue) > num_deps_show
963972
last(pkg_queue, num_deps_show)
@@ -974,7 +983,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
974983
# window between print cycles
975984
termwidth = (displaysize(io)::Tuple{Int,Int})[2] - 4
976985
if !final_loop
977-
s = sprint(io -> show_progress(io, bar; termwidth, carriagereturn=false); context=io)
986+
s = sprint(io -> show_progress(io, bar; termwidth, carriagereturn=false); context=logio)
978987
print(iostr, Base._truncate_at_width_or_chars(true, s, termwidth), "\n")
979988
end
980989
for pkg_config in pkg_queue_show
@@ -1015,11 +1024,11 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
10151024
end
10161025
last_length = length(pkg_queue_show)
10171026
n_print_rows = count("\n", str_)
1018-
print(io, str_)
1027+
print(logio, str_)
10191028
printloop_should_exit[] = interrupted_or_done[] && final_loop
10201029
final_loop = interrupted_or_done[] # ensures one more loop to tidy last task after finish
10211030
i += 1
1022-
printloop_should_exit[] || print(io, ansi_moveup(n_print_rows), ansi_movecol1)
1031+
printloop_should_exit[] || print(logio, ansi_moveup(n_print_rows), ansi_movecol1)
10231032
end
10241033
wait(t)
10251034
end
@@ -1029,7 +1038,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
10291038
# Base.display_error(ErrorException(""), Base.catch_backtrace())
10301039
handle_interrupt(err, true) || rethrow()
10311040
finally
1032-
fancyprint && print(io, ansi_enablecursor)
1041+
fancyprint && print(logio, ansi_enablecursor)
10331042
end
10341043
end
10351044

@@ -1081,8 +1090,8 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
10811090
end
10821091
if !circular && is_stale
10831092
Base.acquire(parallel_limiter)
1084-
is_project_dep = pkg in project_deps
10851093
is_serial_dep = pkg in serial_deps
1094+
is_project_dep = pkg in project_deps
10861095

10871096
# std monitoring
10881097
std_pipe = Base.link_pipe!(Pipe(); reader_supports_async=true, writer_supports_async=true)
@@ -1092,7 +1101,7 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
10921101
name = describe_pkg(pkg, is_project_dep, is_serial_dep, flags, cacheflags)
10931102
@lock print_lock begin
10941103
if !fancyprint && isempty(pkg_queue)
1095-
printpkgstyle(io, :Precompiling, something(target[], "packages..."))
1104+
printpkgstyle(logio, :Precompiling, something(target[], "packages..."))
10961105
end
10971106
end
10981107
push!(pkg_queue, pkg_config)
@@ -1141,11 +1150,11 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
11411150
if ret isa Exception
11421151
push!(precomperr_deps, pkg_config)
11431152
!fancyprint && @lock print_lock begin
1144-
println(io, _timing_string(t), color_string(" ? ", Base.warn_color()), name)
1153+
println(logio, _timing_string(t), color_string(" ? ", Base.warn_color()), name)
11451154
end
11461155
else
11471156
!fancyprint && @lock print_lock begin
1148-
println(io, _timing_string(t), color_string("", loaded ? Base.warn_color() : :green), name)
1157+
println(logio, _timing_string(t), color_string("", loaded ? Base.warn_color() : :green), name)
11491158
end
11501159
if ret !== nothing
11511160
was_recompiled[pkg_config] = true
@@ -1161,11 +1170,9 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
11611170
close(std_pipe.in) # close pipe to end the std output monitor
11621171
wait(t_monitor)
11631172
if err isa ErrorException || (err isa ArgumentError && startswith(err.msg, "Invalid header in cache file"))
1164-
errmsg = String(take!(get(IOBuffer, std_outputs, pkg_config)))
1165-
delete!(std_outputs, pkg_config) # so it's not shown as warnings, given error report
1166-
failed_deps[pkg_config] = (strict || is_project_dep) ? string(sprint(showerror, err), "\n", strip(errmsg)) : ""
1173+
failed_deps[pkg_config] = sprint(showerror, err)
11671174
!fancyprint && @lock print_lock begin
1168-
println(io, " "^12, color_string("", Base.error_color()), name)
1175+
println(logio, " "^12, color_string("", Base.error_color()), name)
11691176
end
11701177
else
11711178
rethrow()
@@ -1213,9 +1220,25 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
12131220
quick_exit = any(t -> !istaskdone(t) || istaskfailed(t), tasks) || interrupted[] # all should have finished (to avoid memory corruption)
12141221
seconds_elapsed = round(Int, (time_ns() - time_start) / 1e9)
12151222
ndeps = count(values(was_recompiled))
1216-
if ndeps > 0 || !isempty(failed_deps) || (quick_exit && !isempty(std_outputs))
1217-
str = sprint(context=io) do iostr
1218-
if !quick_exit
1223+
# Determine if any of failures were a requested package
1224+
requested_errs = false
1225+
for ((dep, config), err) in failed_deps
1226+
if dep in requested_pkgids
1227+
requested_errs = true
1228+
break
1229+
end
1230+
end
1231+
# if every requested package succeeded, filter away output from failed packages
1232+
# since it didn't contribute to the overall success and can be regenerated if that package is later required
1233+
if !strict && requested_errs
1234+
for (pkg_config, err) in failed_deps
1235+
delete!(std_outputs, pkg_config)
1236+
end
1237+
empty!(failed_deps)
1238+
end
1239+
if ndeps > 0 || !isempty(failed_deps)
1240+
if !quick_exit
1241+
logstr = sprint(context=logio) do iostr
12191242
if fancyprint # replace the progress bar
12201243
what = isempty(requested_pkgids) ? "packages finished." : "$(join((p.name for p in requested_pkgids), ", ", " and ")) finished."
12211244
printpkgstyle(iostr, :Precompiling, what)
@@ -1248,10 +1271,17 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
12481271
)
12491272
end
12501273
end
1274+
@lock print_lock begin
1275+
println(logio, logstr)
1276+
end
1277+
end
1278+
end
1279+
if !isempty(std_outputs)
1280+
str = sprint(context=io) do iostr
12511281
# show any stderr output, even if Pkg.precompile has been interrupted (quick_exit=true), given user may be
1252-
# interrupting a hanging precompile job with stderr output. julia#48371
1282+
# interrupting a hanging precompile job with stderr output.
12531283
let std_outputs = Tuple{PkgConfig,SubString{String}}[(pkg_config, strip(String(take!(io)))) for (pkg_config,io) in std_outputs]
1254-
filter!(kv -> !isempty(last(kv)), std_outputs)
1284+
filter!(!isemptylast, std_outputs)
12551285
if !isempty(std_outputs)
12561286
local plural1 = length(std_outputs) == 1 ? "y" : "ies"
12571287
local plural2 = length(std_outputs) == 1 ? "" : "s"
@@ -1269,49 +1299,32 @@ function _precompilepkgs(pkgs::Union{Vector{String}, Vector{PkgId}},
12691299
end
12701300
end
12711301
end
1272-
@lock print_lock begin
1302+
isempty(str) || @lock print_lock begin
12731303
println(io, str)
12741304
end
1275-
if interrupted[]
1276-
# done cleanup, now ensure caller aborts too
1277-
throw(InterruptException())
1278-
end
1279-
quick_exit && return Vector{String}[]
1305+
end
1306+
# Done cleanup and sub-process output, now ensure caller aborts too with the right error
1307+
if interrupted[]
1308+
throw(InterruptException())
1309+
end
1310+
# Fail noisily now with failed_deps if any.
1311+
# Include all messages from compilecache since any might be relevant in the failure.
1312+
if !isempty(failed_deps)
12801313
err_str = IOBuffer()
1281-
n_direct_errs = 0
1282-
for (pkg_config, err) in failed_deps
1283-
dep, config = pkg_config
1284-
if strict || (dep in project_deps)
1285-
print(err_str, "\n", dep.name, " ")
1286-
for cfg in config[1]
1287-
print(err_str, cfg, " ")
1288-
end
1289-
print(err_str, "\n\n", err)
1290-
n_direct_errs > 0 && write(err_str, "\n")
1291-
n_direct_errs += 1
1292-
end
1314+
for ((dep, config), err) in failed_deps
1315+
write(err_str, "\n")
1316+
print(err_str, "\n", dep.name, " ")
1317+
join(err_str, config[1], " ")
1318+
print(err_str, "\n", err)
12931319
end
1294-
if position(err_str) > 0
1295-
skip(err_str, -1)
1296-
truncate(err_str, position(err_str))
1297-
pluralde = n_direct_errs == 1 ? "y" : "ies"
1298-
direct = strict ? "" : "direct "
1299-
err_msg = "The following $n_direct_errs $(direct)dependenc$(pluralde) failed to precompile:\n$(String(take!(err_str)))"
1300-
if internal_call # aka. decide which untested code path to run that does some unsafe behavior
1301-
if isinteractive() # XXX: this test is incorrect
1302-
plural1 = length(failed_deps) == 1 ? "y" : "ies"
1303-
println(io, " ", color_string("$(length(failed_deps))", Base.error_color()), " dependenc$(plural1) errored.")
1304-
println(io, " For a report of the errors see `julia> err`. To retry use `pkg> precompile`")
1305-
setglobal!(Base.MainInclude, :err, PkgPrecompileError(err_msg)) # XXX: this call is dangerous
1306-
else
1307-
# auto-precompilation shouldn't throw but if the user can't easily access the
1308-
# error messages, just show them
1309-
print(io, "\n", err_msg)
1310-
end
1311-
else # XXX: crashing is wrong
1312-
println(io)
1313-
throw(PkgPrecompileError(err_msg))
1314-
end
1320+
n_errs = length(failed_deps)
1321+
pluraled = n_errs == 1 ? "" : "s"
1322+
err_msg = "The following $n_errs package$(pluraled) failed to precompile:$(String(take!(err_str)))\n"
1323+
if internal_call
1324+
# Pkg does not implement correct error handling, so this sometimes handles them instead
1325+
print(io, err_msg)
1326+
else
1327+
throw(PkgPrecompileError(err_msg))
13151328
end
13161329
end
13171330
return collect(String, Iterators.flatten((v for (pkgid, v) in cachepath_cache if pkgid in requested_pkgids)))

doc/src/devdocs/precompile_hang.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ If you follow the advice and hit `Ctrl-C`, you might see
1717
1818
1 dependency had warnings during precompilation:
1919
┌ Test1 [ac89d554-e2ba-40bc-bc5c-de68b658c982]
20-
│ [pid 2745] waiting for IO to finish:
20+
│ [pid 2745] Waiting for background task / IO / timer to finish:
2121
│ Handle type uv_handle_t->data
2222
│ timer 0x55580decd1e0->0x7f94c3a4c340
2323
```

src/jl_uv.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ static void wait_empty_func(uv_timer_t *t)
6868
uv_unref((uv_handle_t*)&signal_async);
6969
if (!uv_loop_alive(t->loop))
7070
return;
71-
jl_safe_printf("\n[pid %zd] waiting for IO to finish:\n"
71+
jl_safe_printf("\n[pid %zd] Waiting for background task / IO / timer to finish:\n"
7272
" Handle type uv_handle_t->data\n",
7373
(size_t)uv_os_getpid());
7474
uv_walk(jl_io_loop, walk_print_cb, NULL);

0 commit comments

Comments
 (0)