Skip to content

Commit a25475c

Browse files
vtjnashKristofferC
authored andcommitted
add logic to prefer loading modules that are already loaded (#55908)
Iterate over the list of existing loaded modules for PkgId whenever loading a new module for PkgId, so that we will use that existing build_id content if it otherwise passes the other stale_checks. (cherry picked from commit 7e2d803)
1 parent 29e0a3e commit a25475c

File tree

3 files changed

+138
-84
lines changed

3 files changed

+138
-84
lines changed

base/Base.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ function __init__()
605605
empty!(explicit_loaded_modules)
606606
empty!(loaded_precompiles) # If we load a packageimage when building the image this might not be empty
607607
for (mod, key) in module_keys
608-
loaded_precompiles[key => module_build_id(mod)] = mod
608+
push!(get!(Vector{Module}, loaded_precompiles, key), mod)
609609
end
610610
if haskey(ENV, "JULIA_MAX_NUM_PRECOMPILE_FILES")
611611
MAX_NUM_PRECOMPILE_FILES[] = parse(Int, ENV["JULIA_MAX_NUM_PRECOMPILE_FILES"])

base/loading.jl

Lines changed: 103 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,7 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No
12321232
dep = depmods[i]
12331233
dep isa Module && continue
12341234
_, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128}
1235-
dep = loaded_precompiles[depkey => depbuild_id]
1235+
dep = something(maybe_loaded_precompile(depkey, depbuild_id))
12361236
@assert PkgId(dep) == depkey && module_build_id(dep) === depbuild_id
12371237
depmods[i] = dep
12381238
end
@@ -1338,6 +1338,7 @@ end
13381338

13391339
function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String)
13401340
# This function is also used by PkgCacheInspector.jl
1341+
assert_havelock(require_lock)
13411342
restored = sv[1]::Vector{Any}
13421343
for M in restored
13431344
M = M::Module
@@ -1346,7 +1347,7 @@ function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String)
13461347
end
13471348
if parentmodule(M) === M
13481349
push!(loaded_modules_order, M)
1349-
loaded_precompiles[pkg => module_build_id(M)] = M
1350+
push!(get!(Vector{Module}, loaded_precompiles, pkg), M)
13501351
end
13511352
end
13521353

@@ -1962,90 +1963,102 @@ end
19621963
assert_havelock(require_lock)
19631964
paths = find_all_in_cache_path(pkg, DEPOT_PATH)
19641965
newdeps = PkgId[]
1965-
for path_to_try in paths::Vector{String}
1966-
staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try; reasons, stalecheck)
1967-
if staledeps === true
1968-
continue
1969-
end
1970-
try
1971-
staledeps, ocachefile, newbuild_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
1972-
# finish checking staledeps module graph
1973-
for i in eachindex(staledeps)
1974-
dep = staledeps[i]
1975-
dep isa Module && continue
1976-
modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
1977-
modpaths = find_all_in_cache_path(modkey, DEPOT_PATH)
1978-
for modpath_to_try in modpaths
1979-
modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try; stalecheck)
1980-
if modstaledeps === true
1981-
continue
1982-
end
1983-
modstaledeps, modocachepath, _ = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
1984-
staledeps[i] = (modpath, modkey, modbuild_id, modpath_to_try, modstaledeps, modocachepath)
1985-
@goto check_next_dep
1966+
try_build_ids = UInt128[build_id]
1967+
if build_id == UInt128(0)
1968+
let loaded = get(loaded_precompiles, pkg, nothing)
1969+
if loaded !== nothing
1970+
for mod in loaded # try these in reverse original load order to see if one is already valid
1971+
pushfirst!(try_build_ids, module_build_id(mod))
19861972
end
1987-
@debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache."
1988-
@goto check_next_path
1989-
@label check_next_dep
19901973
end
1991-
M = get(loaded_precompiles, pkg => newbuild_id, nothing)
1992-
if isa(M, Module)
1993-
stalecheck && register_root_module(M)
1994-
return M
1995-
end
1996-
if stalecheck
1997-
try
1998-
touch(path_to_try) # update timestamp of precompilation file
1999-
catch ex # file might be read-only and then we fail to update timestamp, which is fine
2000-
ex isa IOError || rethrow()
2001-
end
1974+
end
1975+
end
1976+
for build_id in try_build_ids
1977+
for path_to_try in paths::Vector{String}
1978+
staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try; reasons, stalecheck)
1979+
if staledeps === true
1980+
continue
20021981
end
2003-
# finish loading module graph into staledeps
2004-
# TODO: call all start_loading calls (in reverse order) before calling any _include_from_serialized, since start_loading will drop the loading lock
2005-
for i in eachindex(staledeps)
2006-
dep = staledeps[i]
2007-
dep isa Module && continue
2008-
modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
2009-
dep = start_loading(modkey, modbuild_id, stalecheck)
2010-
while true
2011-
if dep isa Module
2012-
if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id
2013-
break
2014-
else
2015-
@debug "Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
2016-
@goto check_next_path
1982+
try
1983+
staledeps, ocachefile, newbuild_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
1984+
# finish checking staledeps module graph
1985+
for i in eachindex(staledeps)
1986+
dep = staledeps[i]
1987+
dep isa Module && continue
1988+
modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
1989+
modpaths = find_all_in_cache_path(modkey, DEPOT_PATH)
1990+
for modpath_to_try in modpaths
1991+
modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try; stalecheck)
1992+
if modstaledeps === true
1993+
continue
20171994
end
1995+
modstaledeps, modocachepath, _ = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
1996+
staledeps[i] = (modpath, modkey, modbuild_id, modpath_to_try, modstaledeps, modocachepath)
1997+
@goto check_next_dep
20181998
end
2019-
if dep === nothing
2020-
try
2021-
set_pkgorigin_version_path(modkey, modpath)
2022-
dep = _include_from_serialized(modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
2023-
finally
2024-
end_loading(modkey, dep)
1999+
@debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache."
2000+
@goto check_next_path
2001+
@label check_next_dep
2002+
end
2003+
M = maybe_loaded_precompile(pkg, newbuild_id)
2004+
if isa(M, Module)
2005+
stalecheck && register_root_module(M)
2006+
return M
2007+
end
2008+
if stalecheck
2009+
try
2010+
touch(path_to_try) # update timestamp of precompilation file
2011+
catch ex # file might be read-only and then we fail to update timestamp, which is fine
2012+
ex isa IOError || rethrow()
2013+
end
2014+
end
2015+
# finish loading module graph into staledeps
2016+
# TODO: call all start_loading calls (in reverse order) before calling any _include_from_serialized, since start_loading will drop the loading lock
2017+
for i in eachindex(staledeps)
2018+
dep = staledeps[i]
2019+
dep isa Module && continue
2020+
modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
2021+
dep = start_loading(modkey, modbuild_id, stalecheck)
2022+
while true
2023+
if dep isa Module
2024+
if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id
2025+
break
2026+
else
2027+
@debug "Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
2028+
@goto check_next_path
2029+
end
20252030
end
2026-
if !isa(dep, Module)
2027-
@debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep
2028-
@goto check_next_path
2029-
else
2030-
push!(newdeps, modkey)
2031+
if dep === nothing
2032+
try
2033+
set_pkgorigin_version_path(modkey, modpath)
2034+
dep = _include_from_serialized(modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
2035+
finally
2036+
end_loading(modkey, dep)
2037+
end
2038+
if !isa(dep, Module)
2039+
@debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep
2040+
@goto check_next_path
2041+
else
2042+
push!(newdeps, modkey)
2043+
end
20312044
end
20322045
end
2046+
staledeps[i] = dep
20332047
end
2034-
staledeps[i] = dep
2035-
end
2036-
restored = get(loaded_precompiles, pkg => newbuild_id, nothing)
2037-
if !isa(restored, Module)
2038-
restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps; register = stalecheck)
2039-
end
2040-
isa(restored, Module) && return restored
2041-
@debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored
2042-
@label check_next_path
2043-
finally
2044-
for modkey in newdeps
2045-
insert_extension_triggers(modkey)
2046-
stalecheck && run_package_callbacks(modkey)
2048+
restored = maybe_loaded_precompile(pkg, newbuild_id)
2049+
if !isa(restored, Module)
2050+
restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps; register = stalecheck)
2051+
end
2052+
isa(restored, Module) && return restored
2053+
@debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored
2054+
@label check_next_path
2055+
finally
2056+
for modkey in newdeps
2057+
insert_extension_triggers(modkey)
2058+
stalecheck && run_package_callbacks(modkey)
2059+
end
2060+
empty!(newdeps)
20472061
end
2048-
empty!(newdeps)
20492062
end
20502063
end
20512064
return nothing
@@ -2065,7 +2078,7 @@ function start_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
20652078
loaded = stalecheck ? maybe_root_module(modkey) : nothing
20662079
loaded isa Module && return loaded
20672080
if build_id != UInt128(0)
2068-
loaded = get(loaded_precompiles, modkey => build_id, nothing)
2081+
loaded = maybe_loaded_precompile(modkey, build_id)
20692082
loaded isa Module && return loaded
20702083
end
20712084
loading = get(package_locks, modkey, nothing)
@@ -2394,13 +2407,22 @@ const pkgorigins = Dict{PkgId,PkgOrigin}()
23942407

23952408
const explicit_loaded_modules = Dict{PkgId,Module}() # Emptied on Julia start
23962409
const loaded_modules = Dict{PkgId,Module}() # available to be explicitly loaded
2397-
const loaded_precompiles = Dict{Pair{PkgId,UInt128},Module}() # extended (complete) list of modules, available to be loaded
2410+
const loaded_precompiles = Dict{PkgId,Vector{Module}}() # extended (complete) list of modules, available to be loaded
23982411
const loaded_modules_order = Vector{Module}()
23992412
const module_keys = IdDict{Module,PkgId}() # the reverse of loaded_modules
24002413

24012414
is_root_module(m::Module) = @lock require_lock haskey(module_keys, m)
24022415
root_module_key(m::Module) = @lock require_lock module_keys[m]
24032416

2417+
function maybe_loaded_precompile(key::PkgId, buildid::UInt128)
2418+
assert_havelock(require_lock)
2419+
mods = get(loaded_precompiles, key, nothing)
2420+
mods === nothing && return
2421+
for mod in mods
2422+
module_build_id(mod) == buildid && return mod
2423+
end
2424+
end
2425+
24042426
function module_build_id(m::Module)
24052427
hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m)
24062428
return (UInt128(hi) << 64) | lo
@@ -2421,7 +2443,7 @@ end
24212443
end
24222444
end
24232445
end
2424-
haskey(loaded_precompiles, key => module_build_id(m)) || push!(loaded_modules_order, m)
2446+
maybe_loaded_precompile(key, module_build_id(m)) === nothing && push!(loaded_modules_order, m)
24252447
loaded_modules[key] = m
24262448
explicit_loaded_modules[key] = m
24272449
module_keys[m] = key
@@ -3785,8 +3807,8 @@ end
37853807
for i in 1:ndeps
37863808
req_key, req_build_id = required_modules[i]
37873809
# Check if module is already loaded
3788-
if !stalecheck && haskey(loaded_precompiles, req_key => req_build_id)
3789-
M = loaded_precompiles[req_key => req_build_id]
3810+
M = stalecheck ? nothing : maybe_loaded_precompile(req_key, req_build_id)
3811+
if M !== nothing
37903812
@assert PkgId(M) == req_key && module_build_id(M) === req_build_id
37913813
depmods[i] = M
37923814
elseif root_module_exists(req_key)

test/loading.jl

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
original_depot_path = copy(Base.DEPOT_PATH)
4-
53
using Test
64

75
# Tests for @__LINE__ inside and outside of macros
6+
# NOTE: the __LINE__ numbers for these first couple tests are significant, so
7+
# adding any lines here will make those tests fail
88
@test (@__LINE__) == 8
99

1010
macro macro_caller_lineno()
@@ -33,6 +33,9 @@ end
3333
@test @nested_LINE_expansion() == ((@__LINE__() - 4, @__LINE__() - 12), @__LINE__())
3434
@test @nested_LINE_expansion2() == ((@__LINE__() - 5, @__LINE__() - 9), @__LINE__())
3535

36+
original_depot_path = copy(Base.DEPOT_PATH)
37+
include("precompile_utils.jl")
38+
3639
loaded_files = String[]
3740
push!(Base.include_callbacks, (mod::Module, fn::String) -> push!(loaded_files, fn))
3841
include("test_sourcepath.jl")
@@ -1590,3 +1593,32 @@ end
15901593
copy!(LOAD_PATH, old_load_path)
15911594
end
15921595
end
1596+
1597+
@testset "require_stdlib loading duplication" begin
1598+
depot_path = mktempdir()
1599+
oldBase64 = nothing
1600+
try
1601+
push!(empty!(DEPOT_PATH), depot_path)
1602+
Base64_key = Base.PkgId(Base.UUID("2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"), "Base64")
1603+
oldBase64 = Base.unreference_module(Base64_key)
1604+
cc = Base.compilecache(Base64_key)
1605+
@test Base.isprecompiled(Base64_key, cachepaths=String[cc[1]])
1606+
empty!(DEPOT_PATH)
1607+
Base.require_stdlib(Base64_key)
1608+
push!(DEPOT_PATH, depot_path)
1609+
append!(DEPOT_PATH, original_depot_path)
1610+
oldloaded = @lock(Base.require_lock, length(get(Base.loaded_precompiles, Base64_key, Module[])))
1611+
Base.require(Base64_key)
1612+
@test @lock(Base.require_lock, length(get(Base.loaded_precompiles, Base64_key, Module[]))) == oldloaded
1613+
Base.unreference_module(Base64_key)
1614+
empty!(DEPOT_PATH)
1615+
push!(DEPOT_PATH, depot_path)
1616+
Base.require(Base64_key)
1617+
@test @lock(Base.require_lock, length(get(Base.loaded_precompiles, Base64_key, Module[]))) == oldloaded + 1
1618+
Base.unreference_module(Base64_key)
1619+
finally
1620+
oldBase64 === nothing || Base.register_root_module(oldBase64)
1621+
copy!(DEPOT_PATH, original_depot_path)
1622+
rm(depot_path, force=true, recursive=true)
1623+
end
1624+
end

0 commit comments

Comments
 (0)