Skip to content

Commit a0c1326

Browse files
committed
fix loading of repeated/concurrent modules
More followup to fix issues with require. There was an accidental variable reuse (build_id) that caused it to be unable to load cache files in many cases. There was also missing check for a dependency already being loaded, resulting in trying to load it twice. Finally, the start_loading code may drop the require_lock, but the surrounding code was not prepared for that. Now integrate the necessary checks into start_loading, instead of needing to duplicate them before and afterwards. Fixes #53983 Fixes #54940 Closes #55064
1 parent 23dabef commit a0c1326

File tree

1 file changed

+135
-136
lines changed

1 file changed

+135
-136
lines changed

base/loading.jl

+135-136
Original file line numberDiff line numberDiff line change
@@ -1792,10 +1792,7 @@ end
17921792
# search for a precompile cache file to load, after some various checks
17931793
function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128)
17941794
assert_havelock(require_lock)
1795-
loaded = maybe_root_module(modkey)
1796-
if loaded === nothing
1797-
loaded = start_loading(modkey)
1798-
end
1795+
loaded = start_loading(modkey, build_id, false)
17991796
if loaded === nothing
18001797
try
18011798
modpath = locate_package(modkey)
@@ -1898,7 +1895,7 @@ end
18981895
continue
18991896
end
19001897
try
1901-
staledeps, ocachefile, build_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
1898+
staledeps, ocachefile, newbuild_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
19021899
# finish checking staledeps module graph
19031900
for i in 1:length(staledeps)
19041901
dep = staledeps[i]
@@ -1918,7 +1915,7 @@ end
19181915
@goto check_next_path
19191916
@label check_next_dep
19201917
end
1921-
M = get(loaded_precompiles, pkg => build_id, nothing)
1918+
M = get(loaded_precompiles, pkg => newbuild_id, nothing)
19221919
if isa(M, Module)
19231920
stalecheck && register_root_module(M)
19241921
return M
@@ -1931,15 +1928,12 @@ end
19311928
end
19321929
end
19331930
# finish loading module graph into staledeps
1931+
# TODO: call all start_loading calls (in reverse order) before calling any _include_from_serialized, since start_loading will drop the loading lock
19341932
for i in 1:length(staledeps)
19351933
dep = staledeps[i]
19361934
dep isa Module && continue
19371935
modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
1938-
if stalecheck
1939-
dep = maybe_root_module(modkey)
1940-
else
1941-
dep = get(loaded_precompiles, modkey => modbuild_id, nothing)
1942-
end
1936+
dep = start_loading(modkey, modbuild_id, stalecheck)
19431937
while true
19441938
if dep isa Module
19451939
if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id
@@ -1949,7 +1943,6 @@ end
19491943
@goto check_next_path
19501944
end
19511945
end
1952-
dep = start_loading(modkey)
19531946
if dep === nothing
19541947
try
19551948
set_pkgorigin_version_path(modkey, modpath)
@@ -1968,7 +1961,7 @@ end
19681961
end
19691962
staledeps[i] = dep
19701963
end
1971-
restored = get(loaded_precompiles, pkg => build_id, nothing)
1964+
restored = get(loaded_precompiles, pkg => newbuild_id, nothing)
19721965
if !isa(restored, Module)
19731966
restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps)
19741967
end
@@ -1992,11 +1985,17 @@ const package_locks = Dict{PkgId,Pair{Task,Threads.Condition}}()
19921985

19931986
debug_loading_deadlocks::Bool = true # Enable a slightly more expensive, but more complete algorithm that can handle simultaneous tasks.
19941987
# This only triggers if you have multiple tasks trying to load the same package at the same time,
1995-
# so it is unlikely to make a difference normally.
1996-
function start_loading(modkey::PkgId)
1997-
# handle recursive calls to require
1988+
# so it is unlikely to make a performance difference normally.
1989+
function start_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
1990+
# handle recursive and concurrent calls to require
19981991
assert_havelock(require_lock)
19991992
while true
1993+
loaded = stalecheck ? maybe_root_module(modkey) : nothing
1994+
loaded isa Module && return loaded
1995+
if build_id != UInt128(0)
1996+
loaded = get(loaded_precompiles, modkey => build_id, nothing)
1997+
loaded isa Module && return loaded
1998+
end
20001999
loading = get(package_locks, modkey, nothing)
20012000
if loading === nothing
20022001
package_locks[modkey] = current_task() => Threads.Condition(require_lock)
@@ -2040,8 +2039,8 @@ function start_loading(modkey::PkgId)
20402039
end
20412040
throw(ConcurrencyViolationError(msg))
20422041
end
2043-
loading = wait(cond)
2044-
loading isa Module && return loading
2042+
loaded = wait(cond)
2043+
loaded isa Module && return loaded
20452044
end
20462045
end
20472046

@@ -2138,6 +2137,7 @@ order to throw an error if Julia attempts to precompile it.
21382137
end
21392138

21402139
# require always works in Main scope and loads files from node 1
2140+
# XXX: (this is deprecated, but still used by Distributed)
21412141
const toplevel_load = Ref(true)
21422142

21432143
const _require_world_age = Ref{UInt}(typemax(UInt))
@@ -2284,24 +2284,29 @@ end
22842284

22852285
function __require_prelocked(uuidkey::PkgId, env=nothing)
22862286
assert_havelock(require_lock)
2287-
if !root_module_exists(uuidkey)
2288-
newm = _require(uuidkey, env)
2289-
if newm === nothing
2290-
error("package `$(uuidkey.name)` did not define the expected \
2291-
module `$(uuidkey.name)`, check for typos in package module name")
2292-
end
2293-
insert_extension_triggers(uuidkey)
2294-
# After successfully loading, notify downstream consumers
2295-
run_package_callbacks(uuidkey)
2296-
else
2297-
m = get(loaded_modules, uuidkey, nothing)
2298-
if m !== nothing && !haskey(explicit_loaded_modules, uuidkey)
2299-
explicit_loaded_modules[uuidkey] = m
2287+
m = start_loading(uuidkey, UInt128(0), true)
2288+
if m === nothing
2289+
last = toplevel_load[]
2290+
try
2291+
toplevel_load[] = false
2292+
newm = _require(uuidkey, env)
2293+
if newm === nothing
2294+
error("package `$(uuidkey.name)` did not define the expected \
2295+
module `$(uuidkey.name)`, check for typos in package module name")
2296+
end
2297+
m = newm::Module
2298+
insert_extension_triggers(uuidkey)
2299+
# After successfully loading, notify downstream consumers
23002300
run_package_callbacks(uuidkey)
2301+
finally
2302+
toplevel_load[] = last
2303+
end_loading(uuidkey, m)
23012304
end
2302-
newm = root_module(uuidkey)
2305+
elseif !haskey(explicit_loaded_modules, uuidkey)
2306+
explicit_loaded_modules[uuidkey] = m
2307+
run_package_callbacks(uuidkey)
23032308
end
2304-
return newm
2309+
return m
23052310
end
23062311

23072312
mutable struct PkgOrigin
@@ -2371,6 +2376,8 @@ maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, noth
23712376
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
23722377
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
23732378

2379+
# after unreference_module, a subsequent require call will try to load a new copy of it, if stale
2380+
# reload(m) = (unreference_module(m); require(m))
23742381
function unreference_module(key::PkgId)
23752382
if haskey(explicit_loaded_modules, key)
23762383
m = pop!(explicit_loaded_modules, key)
@@ -2401,119 +2408,110 @@ disable_parallel_precompile::Bool = false
24012408
# Returns `nothing` or the new(ish) module
24022409
function _require(pkg::PkgId, env=nothing)
24032410
assert_havelock(require_lock)
2404-
loaded = start_loading(pkg)
2405-
loaded === nothing || return loaded
24062411

2407-
last = toplevel_load[]
2408-
try
2409-
toplevel_load[] = false
2410-
# perform the search operation to select the module file require intends to load
2411-
path = locate_package(pkg, env)
2412-
if path === nothing
2413-
throw(ArgumentError("""
2414-
Package $(repr("text/plain", pkg)) is required but does not seem to be installed:
2415-
- Run `Pkg.instantiate()` to install all recorded dependencies.
2416-
"""))
2417-
end
2418-
set_pkgorigin_version_path(pkg, path)
2419-
2420-
parallel_precompile_attempted = false # being safe to avoid getting stuck in a precompilepkgs loop
2421-
reasons = Dict{String,Int}()
2422-
# attempt to load the module file via the precompile cache locations
2423-
if JLOptions().use_compiled_modules != 0
2424-
@label load_from_cache
2425-
loaded = _require_search_from_serialized(pkg, path, UInt128(0), true; reasons)
2426-
if loaded isa Module
2427-
return loaded
2428-
end
2429-
end
2430-
2431-
if JLOptions().use_compiled_modules == 3
2432-
error("Precompiled image $pkg not available with flags $(CacheFlags())")
2433-
end
2434-
2435-
# if the module being required was supposed to have a particular version
2436-
# but it was not handled by the precompile loader, complain
2437-
for (concrete_pkg, concrete_build_id) in _concrete_dependencies
2438-
if pkg == concrete_pkg
2439-
@warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache.
2440-
This may mean $(repr("text/plain", pkg)) does not support precompilation but is imported by a module that does."""
2441-
if JLOptions().incremental != 0
2442-
# during incremental precompilation, this should be fail-fast
2443-
throw(PrecompilableError())
2444-
end
2412+
# perform the search operation to select the module file require intends to load
2413+
path = locate_package(pkg, env)
2414+
if path === nothing
2415+
throw(ArgumentError("""
2416+
Package $(repr("text/plain", pkg)) is required but does not seem to be installed:
2417+
- Run `Pkg.instantiate()` to install all recorded dependencies.
2418+
"""))
2419+
end
2420+
set_pkgorigin_version_path(pkg, path)
2421+
2422+
parallel_precompile_attempted = false # being safe to avoid getting stuck in a precompilepkgs loop
2423+
reasons = Dict{String,Int}()
2424+
# attempt to load the module file via the precompile cache locations
2425+
if JLOptions().use_compiled_modules != 0
2426+
@label load_from_cache
2427+
loaded = _require_search_from_serialized(pkg, path, UInt128(0), true; reasons)
2428+
if loaded isa Module
2429+
return loaded
2430+
end
2431+
end
2432+
2433+
if JLOptions().use_compiled_modules == 3
2434+
error("Precompiled image $pkg not available with flags $(CacheFlags())")
2435+
end
2436+
2437+
# if the module being required was supposed to have a particular version
2438+
# but it was not handled by the precompile loader, complain
2439+
for (concrete_pkg, concrete_build_id) in _concrete_dependencies
2440+
if pkg == concrete_pkg
2441+
@warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache.
2442+
This may mean $(repr("text/plain", pkg)) does not support precompilation but is imported by a module that does."""
2443+
if JLOptions().incremental != 0
2444+
# during incremental precompilation, this should be fail-fast
2445+
throw(PrecompilableError())
24452446
end
24462447
end
2448+
end
24472449

2448-
if JLOptions().use_compiled_modules == 1
2449-
if !generating_output(#=incremental=#false)
2450-
project = active_project()
2451-
if !generating_output() && !parallel_precompile_attempted && !disable_parallel_precompile && @isdefined(Precompilation) && project !== nothing &&
2452-
isfile(project) && project_file_manifest_path(project) !== nothing
2453-
parallel_precompile_attempted = true
2454-
unlock(require_lock)
2455-
try
2456-
Precompilation.precompilepkgs([pkg.name]; _from_loading=true)
2457-
finally
2458-
lock(require_lock)
2459-
end
2460-
@goto load_from_cache
2461-
end
2462-
# spawn off a new incremental pre-compile task for recursive `require` calls
2463-
loaded = maybe_cachefile_lock(pkg, path) do
2464-
# double-check the search now that we have lock
2465-
m = _require_search_from_serialized(pkg, path, UInt128(0), true)
2466-
m isa Module && return m
2467-
return compilecache(pkg, path; reasons)
2450+
if JLOptions().use_compiled_modules == 1
2451+
if !generating_output(#=incremental=#false)
2452+
project = active_project()
2453+
if !generating_output() && !parallel_precompile_attempted && !disable_parallel_precompile && @isdefined(Precompilation) && project !== nothing &&
2454+
isfile(project) && project_file_manifest_path(project) !== nothing
2455+
parallel_precompile_attempted = true
2456+
unlock(require_lock)
2457+
try
2458+
Precompilation.precompilepkgs([pkg.name]; _from_loading=true)
2459+
finally
2460+
lock(require_lock)
24682461
end
2469-
loaded isa Module && return loaded
2470-
if isnothing(loaded) # maybe_cachefile_lock returns nothing if it had to wait for another process
2471-
@goto load_from_cache # the new cachefile will have the newest mtime so will come first in the search
2472-
elseif isa(loaded, Exception)
2473-
if precompilableerror(loaded)
2474-
verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
2475-
@logmsg verbosity "Skipping precompilation due to precompilable error. Importing $(repr("text/plain", pkg))." exception=loaded
2476-
else
2477-
@warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
2478-
end
2479-
# fall-through to loading the file locally if not incremental
2462+
@goto load_from_cache
2463+
end
2464+
# spawn off a new incremental pre-compile task for recursive `require` calls
2465+
loaded = maybe_cachefile_lock(pkg, path) do
2466+
# double-check the search now that we have lock
2467+
m = _require_search_from_serialized(pkg, path, UInt128(0), true)
2468+
m isa Module && return m
2469+
return compilecache(pkg, path; reasons)
2470+
end
2471+
loaded isa Module && return loaded
2472+
if isnothing(loaded) # maybe_cachefile_lock returns nothing if it had to wait for another process
2473+
@goto load_from_cache # the new cachefile will have the newest mtime so will come first in the search
2474+
elseif isa(loaded, Exception)
2475+
if precompilableerror(loaded)
2476+
verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
2477+
@logmsg verbosity "Skipping precompilation due to precompilable error. Importing $(repr("text/plain", pkg))." exception=loaded
24802478
else
2481-
cachefile, ocachefile = loaded::Tuple{String, Union{Nothing, String}}
2482-
loaded = _tryrequire_from_serialized(pkg, cachefile, ocachefile)
2483-
if !isa(loaded, Module)
2484-
@warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
2485-
else
2486-
return loaded
2487-
end
2479+
@warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
24882480
end
2489-
if JLOptions().incremental != 0
2490-
# during incremental precompilation, this should be fail-fast
2491-
throw(PrecompilableError())
2481+
# fall-through to loading the file locally if not incremental
2482+
else
2483+
cachefile, ocachefile = loaded::Tuple{String, Union{Nothing, String}}
2484+
loaded = _tryrequire_from_serialized(pkg, cachefile, ocachefile)
2485+
if !isa(loaded, Module)
2486+
@warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
2487+
else
2488+
return loaded
24922489
end
24932490
end
2491+
if JLOptions().incremental != 0
2492+
# during incremental precompilation, this should be fail-fast
2493+
throw(PrecompilableError())
2494+
end
24942495
end
2496+
end
24952497

2496-
# just load the file normally via include
2497-
# for unknown dependencies
2498-
uuid = pkg.uuid
2499-
uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
2500-
old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
2498+
# just load the file normally via include
2499+
# for unknown dependencies
2500+
uuid = pkg.uuid
2501+
uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
2502+
old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
2503+
if uuid !== old_uuid
2504+
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
2505+
end
2506+
unlock(require_lock)
2507+
try
2508+
include(__toplevel__, path)
2509+
loaded = get(loaded_modules, pkg, nothing)
2510+
finally
2511+
lock(require_lock)
25012512
if uuid !== old_uuid
2502-
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
2503-
end
2504-
unlock(require_lock)
2505-
try
2506-
include(__toplevel__, path)
2507-
loaded = get(loaded_modules, pkg, nothing)
2508-
finally
2509-
lock(require_lock)
2510-
if uuid !== old_uuid
2511-
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
2512-
end
2513+
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
25132514
end
2514-
finally
2515-
toplevel_load[] = last
2516-
end_loading(pkg, loaded)
25172515
end
25182516
return loaded
25192517
end
@@ -2536,8 +2534,9 @@ function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}=noth
25362534
@lock require_lock begin
25372535
# the PkgId of the ext, or package if not an ext
25382536
this_uuidkey = ext isa String ? PkgId(uuid5(package_uuidkey.uuid, ext), ext) : package_uuidkey
2539-
if root_module_exists(this_uuidkey)
2540-
return loaded_modules[this_uuidkey]
2537+
newm = maybe_root_module(this_uuidkey)
2538+
if newm isa Module
2539+
return newm
25412540
end
25422541
# first since this is a stdlib, try to look there directly first
25432542
env = Sys.STDLIB
@@ -2564,7 +2563,7 @@ function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}=noth
25642563
#end
25652564
set_pkgorigin_version_path(this_uuidkey, sourcepath)
25662565
depot_path = append_bundled_depot_path!(empty(DEPOT_PATH))
2567-
newm = start_loading(this_uuidkey)
2566+
newm = start_loading(this_uuidkey, UInt128(0), true)
25682567
newm === nothing || return newm
25692568
try
25702569
newm = _require_search_from_serialized(this_uuidkey, sourcepath, UInt128(0), false; DEPOT_PATH=depot_path)

0 commit comments

Comments
 (0)