Skip to content

Commit 7b70775

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 7b70775

File tree

2 files changed

+141
-142
lines changed

2 files changed

+141
-142
lines changed

base/loading.jl

+132-134
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,28 @@ 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")
2287+
m = start_loading(uuidkey, UInt128(0), true)
2288+
if m === nothing
2289+
last = toplevel_load[]
2290+
try
2291+
toplevel_load[] = false
2292+
m = _require(uuidkey, env)
2293+
if m === 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+
finally
2298+
toplevel_load[] = last
2299+
end_loading(uuidkey, m)
22922300
end
22932301
insert_extension_triggers(uuidkey)
22942302
# After successfully loading, notify downstream consumers
22952303
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
2300-
run_package_callbacks(uuidkey)
2301-
end
2302-
newm = root_module(uuidkey)
2304+
elseif !haskey(explicit_loaded_modules, uuidkey)
2305+
explicit_loaded_modules[uuidkey] = m
2306+
run_package_callbacks(uuidkey)
23032307
end
2304-
return newm
2308+
return m
23052309
end
23062310

23072311
mutable struct PkgOrigin
@@ -2371,6 +2375,8 @@ maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, noth
23712375
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
23722376
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
23732377

2378+
# after unreference_module, a subsequent require call will try to load a new copy of it, if stale
2379+
# reload(m) = (unreference_module(m); require(m))
23742380
function unreference_module(key::PkgId)
23752381
if haskey(explicit_loaded_modules, key)
23762382
m = pop!(explicit_loaded_modules, key)
@@ -2401,119 +2407,110 @@ disable_parallel_precompile::Bool = false
24012407
# Returns `nothing` or the new(ish) module
24022408
function _require(pkg::PkgId, env=nothing)
24032409
assert_havelock(require_lock)
2404-
loaded = start_loading(pkg)
2405-
loaded === nothing || return loaded
24062410

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

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

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__)
2497+
# just load the file normally via include
2498+
# for unknown dependencies
2499+
uuid = pkg.uuid
2500+
uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
2501+
old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
2502+
if uuid !== old_uuid
2503+
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
2504+
end
2505+
unlock(require_lock)
2506+
try
2507+
include(__toplevel__, path)
2508+
loaded = get(loaded_modules, pkg, nothing)
2509+
finally
2510+
lock(require_lock)
25012511
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
2512+
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
25132513
end
2514-
finally
2515-
toplevel_load[] = last
2516-
end_loading(pkg, loaded)
25172514
end
25182515
return loaded
25192516
end
@@ -2536,8 +2533,9 @@ function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}=noth
25362533
@lock require_lock begin
25372534
# the PkgId of the ext, or package if not an ext
25382535
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]
2536+
newm = maybe_root_module(this_uuidkey)
2537+
if newm isa Module
2538+
return newm
25412539
end
25422540
# first since this is a stdlib, try to look there directly first
25432541
env = Sys.STDLIB
@@ -2564,7 +2562,7 @@ function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}=noth
25642562
#end
25652563
set_pkgorigin_version_path(this_uuidkey, sourcepath)
25662564
depot_path = append_bundled_depot_path!(empty(DEPOT_PATH))
2567-
newm = start_loading(this_uuidkey)
2565+
newm = start_loading(this_uuidkey, UInt128(0), true)
25682566
newm === nothing || return newm
25692567
try
25702568
newm = _require_search_from_serialized(this_uuidkey, sourcepath, UInt128(0), false; DEPOT_PATH=depot_path)

0 commit comments

Comments
 (0)