Skip to content

Commit 967e2f0

Browse files
fattenederKristofferC
authored andcommitted
Avoid repeated precompilation when loading from non-relocatable cachefiles (#53905)
Fixes #53859 (comment), which was actually fixed before in #52346, but #52750 undid that fix. This PR does not directly address #53859, because I can not reproduce it atm. --- The `@depot` resolution logic for `include()` files is adjusted as follows: 1. (new behavior) If the cache is not relocatable because of an absolute path, we ignore that path for the depot search. Recompilation will be triggered by `stale_cachefile()` if that absolute path does not exist. Previously this caused any `@depot` tags to be not resolved and so trigger recompilation. 2. (new behavior) If we can't find a depot for a relocatable path, we still replace it with the depot we found from other files. Recompilation will be triggered by `stale_cachefile()` because the resolved path does not exist. 3. (this behavior is kept) We require that relocatable paths all resolve to the same depot. 4. (new behavior) We no longer use the first matching depot for replacement, but instead we explicitly check that all resolve to the same depot. This has two reasons: - We want to scan all source files anyways in order to provide logs for 1. and 2. above, so the check is free. - It is possible that a depot might be missing source files. Assume that we have two depots on `DEPOT_PATH`, `depot_complete` and `depot_incomplete`. If `DEPOT_PATH=["depot_complete","depot_incomplete"]` then no recompilation shall happen, because `depot_complete` will be picked. If `DEPOT_PATH=["depot_incomplete","depot_complete"]` we trigger recompilation and hopefully a meaningful error about missing files is thrown. If we were to just select the first depot we find, then whether recompilation happens would depend on whether the first relocatable file resolves to `depot_complete` or `depot_incomplete`. (cherry picked from commit d8d3842)
1 parent b0d7ee4 commit 967e2f0

File tree

6 files changed

+106
-16
lines changed

6 files changed

+106
-16
lines changed

base/loading.jl

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3160,31 +3160,71 @@ function parse_cache_header(f::IO, cachefile::AbstractString)
31603160

31613161
includes_srcfiles = CacheHeaderIncludes[]
31623162
includes_depfiles = CacheHeaderIncludes[]
3163-
for (i, inc) in enumerate(includes)
3163+
for inc in includes
31643164
if inc.filename srcfiles
31653165
push!(includes_srcfiles, inc)
31663166
else
31673167
push!(includes_depfiles, inc)
31683168
end
31693169
end
31703170

3171-
# determine depot for @depot replacement for include() files and include_dependency() files separately
3172-
srcfiles_depot = resolve_depot(first(srcfiles))
3173-
if srcfiles_depot === :no_depot_found
3174-
@debug("Unable to resolve @depot tag include() files from cache file $cachefile", srcfiles)
3175-
elseif srcfiles_depot === :not_relocatable
3176-
@debug("include() files from $cachefile are not relocatable", srcfiles)
3177-
else
3171+
3172+
# The @depot resolution logic for include() files:
3173+
# 1. If the cache is not relocatable because of an absolute path,
3174+
# we ignore that path for the depot search.
3175+
# Recompilation will be triggered by stale_cachefile() if that absolute path does not exist.
3176+
# 2. If we can't find a depot for a relocatable path,
3177+
# we still replace it with the depot we found from other files.
3178+
# Recompilation will be triggered by stale_cachefile() because the resolved path does not exist.
3179+
# 3. We require that relocatable paths all resolve to the same depot.
3180+
# 4. We explicitly check that all relocatable paths resolve to the same depot. This has two reasons:
3181+
# - We want to scan all source files in order to provide logs for 1. and 2. above.
3182+
# - It is possible that a depot might be missing source files.
3183+
# Assume that we have two depots on DEPOT_PATH, depot_complete and depot_incomplete.
3184+
# If DEPOT_PATH=["depot_complete","depot_incomplete"] then no recompilation shall happen,
3185+
# because depot_complete will be picked.
3186+
# If DEPOT_PATH=["depot_incomplete","depot_complete"] we trigger recompilation and
3187+
# hopefully a meaningful error about missing files is thrown.
3188+
# If we were to just select the first depot we find, then whether recompilation happens would
3189+
# depend on whether the first relocatable file resolves to depot_complete or depot_incomplete.
3190+
srcdepot = nothing
3191+
any_not_relocatable = false
3192+
any_no_depot_found = false
3193+
multiple_depots_found = false
3194+
for src in srcfiles
3195+
depot = resolve_depot(src)
3196+
if depot === :not_relocatable
3197+
any_not_relocatable = true
3198+
elseif depot === :no_depot_found
3199+
any_no_depot_found = true
3200+
elseif isnothing(srcdepot)
3201+
srcdepot = depot
3202+
elseif depot != srcdepot
3203+
multiple_depots_found = true
3204+
end
3205+
end
3206+
if any_no_depot_found
3207+
@debug("Unable to resolve @depot tag for at least one include() file from cache file $cachefile", srcfiles, _group=:relocatable)
3208+
end
3209+
if any_not_relocatable
3210+
@debug("At least one include() file from $cachefile is not relocatable", srcfiles, _group=:relocatable)
3211+
end
3212+
if multiple_depots_found
3213+
@debug("Some include() files from $cachefile are distributed over multiple depots", srcfiles, _group=:relocatable)
3214+
elseif !isnothing(srcdepot)
31783215
for inc in includes_srcfiles
3179-
inc.filename = restore_depot_path(inc.filename, srcfiles_depot)
3216+
inc.filename = restore_depot_path(inc.filename, srcdepot)
31803217
end
31813218
end
3219+
3220+
# unlike include() files, we allow each relocatable include_dependency() file to resolve
3221+
# to a separate depot, #52161
31823222
for inc in includes_depfiles
31833223
depot = resolve_depot(inc.filename)
31843224
if depot === :no_depot_found
3185-
@debug("Unable to resolve @depot tag for include_dependency() file $(inc.filename) from cache file $cachefile", srcfiles)
3225+
@debug("Unable to resolve @depot tag for include_dependency() file $(inc.filename) from cache file $cachefile", _group=:relocatable)
31863226
elseif depot === :not_relocatable
3187-
@debug("include_dependency() file $(inc.filename) from $cachefile is not relocatable", srcfiles)
3227+
@debug("include_dependency() file $(inc.filename) from $cachefile is not relocatable", _group=:relocatable)
31883228
else
31893229
inc.filename = restore_depot_path(inc.filename, depot)
31903230
end

test/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/libccalltest.*
55
/relocatedepot
66
/RelocationTestPkg2/src/foo.txt
7+
/RelocationTestPkg*/Manifest.toml

test/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ relocatedepot:
4343
@cp -R $(SRCDIR)/RelocationTestPkg1 $(SRCDIR)/relocatedepot
4444
@cp -R $(SRCDIR)/RelocationTestPkg2 $(SRCDIR)/relocatedepot
4545
@cp -R $(SRCDIR)/RelocationTestPkg3 $(SRCDIR)/relocatedepot
46+
@cp -R $(SRCDIR)/RelocationTestPkg4 $(SRCDIR)/relocatedepot
4647
@cd $(SRCDIR) && \
4748
$(call PRINT_JULIA, $(call spawn,RELOCATEDEPOT="" $(JULIA_EXECUTABLE)) --check-bounds=yes --startup-file=no --depwarn=error ./runtests.jl $@)
4849

@@ -55,6 +56,7 @@ revise-relocatedepot: revise-% :
5556
@cp -R $(SRCDIR)/RelocationTestPkg1 $(SRCDIR)/relocatedepot
5657
@cp -R $(SRCDIR)/RelocationTestPkg2 $(SRCDIR)/relocatedepot
5758
@cp -R $(SRCDIR)/RelocationTestPkg3 $(SRCDIR)/relocatedepot
59+
@cp -R $(SRCDIR)/RelocationTestPkg4 $(SRCDIR)/relocatedepot
5860
@cd $(SRCDIR) && \
5961
$(call PRINT_JULIA, $(call spawn,RELOCATEDEPOT="" $(JULIA_EXECUTABLE)) --check-bounds=yes --startup-file=no --depwarn=error ./runtests.jl --revise $*)
6062

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = "RelocationTestPkg4"
2+
uuid = "d423d817-d7e9-49ac-b245-9d9d6db0b429"
3+
version = "0.1.0"
4+
5+
[deps]
6+
RelocationTestPkg1 = "854e1adb-5a97-46bf-a391-1cfe05ac726d"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module RelocationTestPkg4
2+
3+
greet() = print("Hello World!")
4+
5+
end # module RelocationTestPkg4

test/relocatedepot.jl

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ function test_harness(@nospecialize(fn); empty_load_path=true, empty_depot_path=
1717
end
1818
end
1919

20-
# We test relocation with three dummy pkgs:
21-
# - RelocationTestPkg1 - no include_dependency
22-
# - RelocationTestPkg2 - with include_dependency tracked by `mtime`
23-
# - RelocationTestPkg3 - with include_dependency tracked by content
20+
# We test relocation with these dummy pkgs:
21+
# - RelocationTestPkg1 - pkg with no include_dependency
22+
# - RelocationTestPkg2 - pkg with include_dependency tracked by `mtime`
23+
# - RelocationTestPkg3 - pkg with include_dependency tracked by content
24+
# - RelocationTestPkg4 - pkg with no dependencies; will be compiled such that the pkgimage is
25+
# not relocatable, but no repeated recompilation happens upon loading
2426

2527
if !test_relocated_depot
2628

@@ -123,6 +125,27 @@ if !test_relocated_depot
123125
end
124126
end
125127

128+
@testset "precompile RelocationTestPkg4" begin
129+
# test for #52346 and https://github.com/JuliaLang/julia/issues/53859#issuecomment-2027352004
130+
# If a pkgimage is not relocatable, no repeated precompilation should occur.
131+
pkgname = "RelocationTestPkg4"
132+
test_harness(empty_depot_path=false) do
133+
push!(LOAD_PATH, @__DIR__)
134+
# skip this dir to make the pkgimage not relocatable
135+
filter!(DEPOT_PATH) do depot
136+
!startswith(@__DIR__, depot)
137+
end
138+
pkg = Base.identify_package(pkgname)
139+
cachefiles = Base.find_all_in_cache_path(pkg)
140+
rm.(cachefiles, force=true)
141+
@test Base.isprecompiled(pkg) == false
142+
@test Base.isrelocatable(pkg) == false # because not precompiled
143+
Base.require(pkg)
144+
@test Base.isprecompiled(pkg, ignore_loaded=true) == true
145+
@test Base.isrelocatable(pkg) == false
146+
end
147+
end
148+
126149
@testset "#52161" begin
127150
# Take the src files from two pkgs Example1 and Example2,
128151
# which are each located in depot1 and depot2, respectively, and
@@ -246,7 +269,7 @@ else
246269
pkgname = "RelocationTestPkg3"
247270
test_harness() do
248271
push!(LOAD_PATH, joinpath(@__DIR__, "relocatedepot"))
249-
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot"))
272+
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot")) # required to find src files
250273
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file
251274
pkg = Base.identify_package(pkgname)
252275
@test Base.isprecompiled(pkg) == true
@@ -257,4 +280,17 @@ else
257280
end
258281
end
259282

283+
@testset "load RelocationTestPkg4 from test/relocatedepot" begin
284+
pkgname = "RelocationTestPkg4"
285+
test_harness() do
286+
push!(LOAD_PATH, @__DIR__, "relocatedepot")
287+
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot")) # required to find src files
288+
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file
289+
pkg = Base.identify_package(pkgname)
290+
# precompiled but not relocatable
291+
@test Base.isprecompiled(pkg) == true
292+
@test Base.isrelocatable(pkg) == false
293+
end
294+
end
295+
260296
end

0 commit comments

Comments
 (0)