Skip to content

Commit 9dbdeb4

Browse files
authored
loading: clean up more concurrency issues (#56329)
Guarantee that `__init__` runs before `using` returns. Could be slightly breaking for people that do crazy things inside `__init__`, but just don't do that. Since extensions then probably load after `__init__` (or at least, run their `__init__` after), this is a partial step towards changing things so that extensions are guaranteed to load if using all of their triggers before the corresponding `using` returns Fixes #55556
1 parent 2cdfe06 commit 9dbdeb4

File tree

5 files changed

+70
-97
lines changed

5 files changed

+70
-97
lines changed

base/Base.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -657,8 +657,8 @@ function __init__()
657657
init_active_project()
658658
append!(empty!(_sysimage_modules), keys(loaded_modules))
659659
empty!(loaded_precompiles) # If we load a packageimage when building the image this might not be empty
660-
for (mod, key) in module_keys
661-
push!(get!(Vector{Module}, loaded_precompiles, key), mod)
660+
for mod in loaded_modules_order
661+
push!(get!(Vector{Module}, loaded_precompiles, PkgId(mod)), mod)
662662
end
663663
if haskey(ENV, "JULIA_MAX_NUM_PRECOMPILE_FILES")
664664
MAX_NUM_PRECOMPILE_FILES[] = parse(Int, ENV["JULIA_MAX_NUM_PRECOMPILE_FILES"])

base/loading.jl

+59-76
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,7 @@ See also [`pkgdir`](@ref).
524524
"""
525525
function pathof(m::Module)
526526
@lock require_lock begin
527-
pkgid = get(module_keys, m, nothing)
528-
pkgid === nothing && return nothing
527+
pkgid = PkgId(m)
529528
origin = get(pkgorigins, pkgid, nothing)
530529
origin === nothing && return nothing
531530
path = origin.path
@@ -1652,7 +1651,7 @@ get_extension(parent::Module, ext::Symbol) = get_extension(PkgId(parent), ext)
16521651
function get_extension(parentid::PkgId, ext::Symbol)
16531652
parentid.uuid === nothing && return nothing
16541653
extid = PkgId(uuid5(parentid.uuid, string(ext)), string(ext))
1655-
return get(loaded_modules, extid, nothing)
1654+
return maybe_root_module(extid)
16561655
end
16571656

16581657
# End extensions
@@ -1811,7 +1810,7 @@ function show(io::IO, it::ImageTarget)
18111810
end
18121811

18131812
# should sync with the types of arguments of `stale_cachefile`
1814-
const StaleCacheKey = Tuple{Base.PkgId, UInt128, String, String}
1813+
const StaleCacheKey = Tuple{PkgId, UInt128, String, String}
18151814

18161815
function compilecache_path(pkg::PkgId;
18171816
ignore_loaded::Bool=false,
@@ -2063,7 +2062,7 @@ end
20632062
modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
20642063
# inline a call to start_loading here
20652064
@assert canstart_loading(modkey, modbuild_id, stalecheck) === nothing
2066-
package_locks[modkey] = current_task() => Threads.Condition(require_lock)
2065+
package_locks[modkey] = (current_task(), Threads.Condition(require_lock), modbuild_id)
20672066
startedloading = i
20682067
modpaths = find_all_in_cache_path(modkey, DEPOT_PATH)
20692068
for modpath_to_try in modpaths
@@ -2139,7 +2138,7 @@ end
21392138
end
21402139

21412140
# to synchronize multiple tasks trying to import/using something
2142-
const package_locks = Dict{PkgId,Pair{Task,Threads.Condition}}()
2141+
const package_locks = Dict{PkgId,Tuple{Task,Threads.Condition,UInt128}}()
21432142

21442143
debug_loading_deadlocks::Bool = true # Enable a slightly more expensive, but more complete algorithm that can handle simultaneous tasks.
21452144
# This only triggers if you have multiple tasks trying to load the same package at the same time,
@@ -2148,14 +2147,21 @@ debug_loading_deadlocks::Bool = true # Enable a slightly more expensive, but mor
21482147
function canstart_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
21492148
assert_havelock(require_lock)
21502149
require_lock.reentrancy_cnt == 1 || throw(ConcurrencyViolationError("recursive call to start_loading"))
2151-
loaded = stalecheck ? maybe_root_module(modkey) : nothing
2152-
loaded isa Module && return loaded
2153-
if build_id != UInt128(0)
2150+
loading = get(package_locks, modkey, nothing)
2151+
if loading === nothing
2152+
loaded = stalecheck ? maybe_root_module(modkey) : nothing
2153+
loaded isa Module && return loaded
2154+
if build_id != UInt128(0)
2155+
loaded = maybe_loaded_precompile(modkey, build_id)
2156+
loaded isa Module && return loaded
2157+
end
2158+
return nothing
2159+
end
2160+
if !stalecheck && build_id != UInt128(0) && loading[3] != build_id
2161+
# don't block using an existing specific loaded module on needing a different concurrently loaded one
21542162
loaded = maybe_loaded_precompile(modkey, build_id)
21552163
loaded isa Module && return loaded
21562164
end
2157-
loading = get(package_locks, modkey, nothing)
2158-
loading === nothing && return nothing
21592165
# load already in progress for this module on the task
21602166
task, cond = loading
21612167
deps = String[modkey.name]
@@ -2202,7 +2208,7 @@ function start_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
22022208
while true
22032209
loaded = canstart_loading(modkey, build_id, stalecheck)
22042210
if loaded === nothing
2205-
package_locks[modkey] = current_task() => Threads.Condition(require_lock)
2211+
package_locks[modkey] = (current_task(), Threads.Condition(require_lock), build_id)
22062212
return nothing
22072213
elseif loaded isa Module
22082214
return loaded
@@ -2333,15 +2339,15 @@ For more details regarding code loading, see the manual sections on [modules](@r
23332339
[parallel computing](@ref code-availability).
23342340
"""
23352341
function require(into::Module, mod::Symbol)
2336-
if _require_world_age[] != typemax(UInt)
2337-
Base.invoke_in_world(_require_world_age[], __require, into, mod)
2338-
else
2339-
@invokelatest __require(into, mod)
2342+
world = _require_world_age[]
2343+
if world == typemax(UInt)
2344+
world = get_world_counter()
23402345
end
2346+
return invoke_in_world(world, __require, into, mod)
23412347
end
23422348

23432349
function __require(into::Module, mod::Symbol)
2344-
if into === Base.__toplevel__ && generating_output(#=incremental=#true)
2350+
if into === __toplevel__ && generating_output(#=incremental=#true)
23452351
error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \
23462352
is not allowed during package precompilation.")
23472353
end
@@ -2445,24 +2451,22 @@ function collect_manifest_warnings()
24452451
return msg
24462452
end
24472453

2448-
require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey)
2449-
2450-
function _require_prelocked(uuidkey::PkgId, env=nothing)
2451-
if _require_world_age[] != typemax(UInt)
2452-
Base.invoke_in_world(_require_world_age[], __require_prelocked, uuidkey, env)
2453-
else
2454-
@invokelatest __require_prelocked(uuidkey, env)
2454+
function require(uuidkey::PkgId)
2455+
world = _require_world_age[]
2456+
if world == typemax(UInt)
2457+
world = get_world_counter()
24552458
end
2459+
return invoke_in_world(world, __require, uuidkey)
24562460
end
2457-
2458-
function __require_prelocked(uuidkey::PkgId, env=nothing)
2461+
__require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey)
2462+
function _require_prelocked(uuidkey::PkgId, env=nothing)
24592463
assert_havelock(require_lock)
24602464
m = start_loading(uuidkey, UInt128(0), true)
24612465
if m === nothing
24622466
last = toplevel_load[]
24632467
try
24642468
toplevel_load[] = false
2465-
m = _require(uuidkey, env)
2469+
m = __require_prelocked(uuidkey, env)
24662470
if m === nothing
24672471
error("package `$(uuidkey.name)` did not define the expected \
24682472
module `$(uuidkey.name)`, check for typos in package module name")
@@ -2474,8 +2478,6 @@ function __require_prelocked(uuidkey::PkgId, env=nothing)
24742478
insert_extension_triggers(uuidkey)
24752479
# After successfully loading, notify downstream consumers
24762480
run_package_callbacks(uuidkey)
2477-
else
2478-
newm = root_module(uuidkey)
24792481
end
24802482
return m
24812483
end
@@ -2491,9 +2493,8 @@ const pkgorigins = Dict{PkgId,PkgOrigin}()
24912493
const loaded_modules = Dict{PkgId,Module}() # available to be explicitly loaded
24922494
const loaded_precompiles = Dict{PkgId,Vector{Module}}() # extended (complete) list of modules, available to be loaded
24932495
const loaded_modules_order = Vector{Module}()
2494-
const module_keys = IdDict{Module,PkgId}() # the reverse of loaded_modules
24952496

2496-
root_module_key(m::Module) = @lock require_lock module_keys[m]
2497+
root_module_key(m::Module) = PkgId(m)
24972498

24982499
function maybe_loaded_precompile(key::PkgId, buildid::UInt128)
24992500
@lock require_lock begin
@@ -2527,7 +2528,6 @@ end
25272528
end
25282529
maybe_loaded_precompile(key, module_build_id(m)) === nothing && push!(loaded_modules_order, m)
25292530
loaded_modules[key] = m
2530-
module_keys[m] = key
25312531
end
25322532
nothing
25332533
end
@@ -2544,24 +2544,27 @@ using Base
25442544
end
25452545

25462546
# get a top-level Module from the given key
2547+
# this is similar to `require`, but worse in almost every possible way
25472548
root_module(key::PkgId) = @lock require_lock loaded_modules[key]
25482549
function root_module(where::Module, name::Symbol)
25492550
key = identify_package(where, String(name))
25502551
key isa PkgId || throw(KeyError(name))
25512552
return root_module(key)
25522553
end
2554+
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
25532555
maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, nothing)
25542556

2555-
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
25562557
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
25572558

25582559
# after unreference_module, a subsequent require call will try to load a new copy of it, if stale
25592560
# reload(m) = (unreference_module(m); require(m))
25602561
function unreference_module(key::PkgId)
2562+
@lock require_lock begin
25612563
if haskey(loaded_modules, key)
25622564
m = pop!(loaded_modules, key)
25632565
# need to ensure all modules are GC rooted; will still be referenced
2564-
# in module_keys
2566+
# in loaded_modules_order
2567+
end
25652568
end
25662569
end
25672570

@@ -2582,7 +2585,7 @@ const PKG_PRECOMPILE_HOOK = Ref{Function}()
25822585
disable_parallel_precompile::Bool = false
25832586

25842587
# Returns `nothing` or the new(ish) module
2585-
function _require(pkg::PkgId, env=nothing)
2588+
function __require_prelocked(pkg::PkgId, env)
25862589
assert_havelock(require_lock)
25872590

25882591
# perform the search operation to select the module file require intends to load
@@ -2682,7 +2685,7 @@ function _require(pkg::PkgId, env=nothing)
26822685
unlock(require_lock)
26832686
try
26842687
include(__toplevel__, path)
2685-
loaded = get(loaded_modules, pkg, nothing)
2688+
loaded = maybe_root_module(pkg)
26862689
finally
26872690
lock(require_lock)
26882691
if uuid !== old_uuid
@@ -2755,38 +2758,18 @@ function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}=noth
27552758
@lock require_lock begin
27562759
# the PkgId of the ext, or package if not an ext
27572760
this_uuidkey = ext isa String ? PkgId(uuid5(package_uuidkey.uuid, ext), ext) : package_uuidkey
2758-
newm = maybe_root_module(this_uuidkey)
2759-
if newm isa Module
2760-
return newm
2761-
end
2762-
# first since this is a stdlib, try to look there directly first
27632761
env = Sys.STDLIB
2764-
#sourcepath = ""
2765-
if ext === nothing
2766-
sourcepath = normpath(env, this_uuidkey.name, "src", this_uuidkey.name * ".jl")
2767-
else
2768-
sourcepath = find_ext_path(normpath(joinpath(env, package_uuidkey.name)), ext)
2769-
end
2770-
#mbypath = manifest_uuid_path(env, this_uuidkey)
2771-
#if mbypath isa String && isfile_casesensitive(mbypath)
2772-
# sourcepath = mbypath
2773-
#else
2774-
# # if the user deleted the stdlib folder, we next try using their environment
2775-
# sourcepath = locate_package_env(this_uuidkey)
2776-
# if sourcepath !== nothing
2777-
# sourcepath, env = sourcepath
2778-
# end
2779-
#end
2780-
#if sourcepath === nothing
2781-
# throw(ArgumentError("""
2782-
# Package $(repr("text/plain", this_uuidkey)) is required but does not seem to be installed.
2783-
# """))
2784-
#end
2785-
set_pkgorigin_version_path(this_uuidkey, sourcepath)
2786-
depot_path = append_bundled_depot_path!(empty(DEPOT_PATH))
27872762
newm = start_loading(this_uuidkey, UInt128(0), true)
27882763
newm === nothing || return newm
27892764
try
2765+
# first since this is a stdlib, try to look there directly first
2766+
if ext === nothing
2767+
sourcepath = normpath(env, this_uuidkey.name, "src", this_uuidkey.name * ".jl")
2768+
else
2769+
sourcepath = find_ext_path(normpath(joinpath(env, package_uuidkey.name)), ext)
2770+
end
2771+
depot_path = append_bundled_depot_path!(empty(DEPOT_PATH))
2772+
set_pkgorigin_version_path(this_uuidkey, sourcepath)
27902773
newm = _require_search_from_serialized(this_uuidkey, sourcepath, UInt128(0), false; DEPOT_PATH=depot_path)
27912774
finally
27922775
end_loading(this_uuidkey, newm)
@@ -3968,32 +3951,32 @@ end
39683951
if M !== nothing
39693952
@assert PkgId(M) == req_key && module_build_id(M) === req_build_id
39703953
depmods[i] = M
3971-
elseif root_module_exists(req_key)
3972-
M = root_module(req_key)
3954+
continue
3955+
end
3956+
M = maybe_root_module(req_key)
3957+
if M isa Module
39733958
if PkgId(M) == req_key && module_build_id(M) === req_build_id
39743959
depmods[i] = M
3960+
continue
39753961
elseif M == Core
39763962
@debug "Rejecting cache file $cachefile because it was made with a different julia version"
39773963
record_reason(reasons, "wrong julia version")
39783964
return true # Won't be able to fulfill dependency
39793965
elseif ignore_loaded || !stalecheck
39803966
# Used by Pkg.precompile given that there it's ok to precompile different versions of loaded packages
3981-
@goto locate_branch
39823967
else
39833968
@debug "Rejecting cache file $cachefile because module $req_key is already loaded and incompatible."
39843969
record_reason(reasons, "wrong dep version loaded")
39853970
return true # Won't be able to fulfill dependency
39863971
end
3987-
else
3988-
@label locate_branch
3989-
path = locate_package(req_key) # TODO: add env and/or skip this when stalecheck is false
3990-
if path === nothing
3991-
@debug "Rejecting cache file $cachefile because dependency $req_key not found."
3992-
record_reason(reasons, "dep missing source")
3993-
return true # Won't be able to fulfill dependency
3994-
end
3995-
depmods[i] = (path, req_key, req_build_id)
39963972
end
3973+
path = locate_package(req_key) # TODO: add env and/or skip this when stalecheck is false
3974+
if path === nothing
3975+
@debug "Rejecting cache file $cachefile because dependency $req_key not found."
3976+
record_reason(reasons, "dep missing source")
3977+
return true # Won't be able to fulfill dependency
3978+
end
3979+
depmods[i] = (path, req_key, req_build_id)
39973980
end
39983981

39993982
# check if this file is going to provide one of our concrete dependencies

base/methodshow.jl

+6-6
Original file line numberDiff line numberDiff line change
@@ -378,16 +378,17 @@ function url(m::Method)
378378
line = m.line
379379
line <= 0 || occursin(r"In\[[0-9]+\]"a, file) && return ""
380380
Sys.iswindows() && (file = replace(file, '\\' => '/'))
381-
libgit2_id = PkgId(UUID((0x76f85450_5226_5b5a,0x8eaa_529ad045b433)), "LibGit2")
382381
if inbase(M)
383382
if isempty(Base.GIT_VERSION_INFO.commit)
384383
# this url will only work if we're on a tagged release
385384
return "https://github.com/JuliaLang/julia/tree/v$VERSION/base/$file#L$line"
386385
else
387386
return "https://github.com/JuliaLang/julia/tree/$(Base.GIT_VERSION_INFO.commit)/base/$file#L$line"
388387
end
389-
elseif root_module_exists(libgit2_id)
390-
LibGit2 = root_module(libgit2_id)
388+
end
389+
libgit2_id = PkgId(UUID((0x76f85450_5226_5b5a,0x8eaa_529ad045b433)), "LibGit2")
390+
LibGit2 = maybe_root_module(libgit2_id)
391+
if LibGit2 isa Module
391392
try
392393
d = dirname(file)
393394
return LibGit2.with(LibGit2.GitRepoExt(d)) do repo
@@ -404,11 +405,10 @@ function url(m::Method)
404405
end
405406
end
406407
catch
407-
return fileurl(file)
408+
# oops, this was a bad idea
408409
end
409-
else
410-
return fileurl(file)
411410
end
411+
return fileurl(file)
412412
end
413413

414414
function show(io::IO, ::MIME"text/html", m::Method)

contrib/generate_precompile.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ precompile(Base.unsafe_string, (Ptr{UInt8},))
3535
precompile(Base.unsafe_string, (Ptr{Int8},))
3636
3737
# loading.jl
38-
precompile(Base.__require_prelocked, (Base.PkgId, Nothing))
39-
precompile(Base._require, (Base.PkgId, Nothing))
38+
precompile(Base.__require, (Module, Symbol))
39+
precompile(Base.__require, (Base.PkgId,))
4040
precompile(Base.indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int))
4141
precompile(Base.indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int, Int))
4242
precompile(Tuple{typeof(Base.Threads.atomic_add!), Base.Threads.Atomic{Int}, Int})

stdlib/REPL/src/Pkg_beforeload.jl

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
## Pkg stuff needed before Pkg has loaded
22

33
const Pkg_pkgid = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
4-
5-
function load_pkg()
6-
REPLExt = Base.require_stdlib(Pkg_pkgid, "REPLExt")
7-
@lock Base.require_lock begin
8-
# require_stdlib does not guarantee that the `__init__` of the package is done when loading is done async
9-
# but we need to wait for the repl mode to be set up
10-
lock = get(Base.package_locks, Base.PkgId(REPLExt), nothing)
11-
lock !== nothing && wait(lock[2])
12-
end
13-
return REPLExt
14-
end
4+
load_pkg() = Base.require_stdlib(Pkg_pkgid, "REPLExt")
155

166
## Below here copied/tweaked from Pkg Types.jl so that the dummy Pkg prompt
177
# can populate the env correctly before Pkg loads

0 commit comments

Comments
 (0)