@@ -1261,47 +1261,52 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No
12611261 assert_havelock (require_lock)
12621262 timing_imports = TIMING_IMPORTS[] > 0
12631263 try
1264- if timing_imports
1265- t_before = time_ns ()
1266- cumulative_compile_timing (true )
1267- t_comp_before = cumulative_compile_time_ns ()
1268- end
1264+ if timing_imports
1265+ t_before = time_ns ()
1266+ cumulative_compile_timing (true )
1267+ t_comp_before = cumulative_compile_time_ns ()
1268+ end
12691269
1270- for i in eachindex (depmods)
1271- dep = depmods[i]
1272- dep isa Module && continue
1273- _, depkey, depbuild_id = dep:: Tuple{String, PkgId, UInt128}
1274- dep = something (maybe_loaded_precompile (depkey, depbuild_id))
1275- @assert PkgId (dep) == depkey && module_build_id (dep) === depbuild_id
1276- depmods[i] = dep
1277- end
1270+ for i in eachindex (depmods)
1271+ dep = depmods[i]
1272+ dep isa Module && continue
1273+ _, depkey, depbuild_id = dep:: Tuple{String, PkgId, UInt128}
1274+ dep = something (maybe_loaded_precompile (depkey, depbuild_id))
1275+ @assert PkgId (dep) == depkey && module_build_id (dep) === depbuild_id
1276+ depmods[i] = dep
1277+ end
12781278
1279- if ocachepath != = nothing
1280- @debug " Loading object cache file $ocachepath for $(repr (" text/plain" , pkg)) "
1281- sv = ccall (:jl_restore_package_image_from_file , Any, (Cstring, Any, Cint, Cstring, Cint), ocachepath, depmods, false , pkg. name, ignore_native)
1282- else
1283- @debug " Loading cache file $path for $(repr (" text/plain" , pkg)) "
1284- sv = ccall (:jl_restore_incremental , Any, (Cstring, Any, Cint, Cstring), path, depmods, false , pkg. name)
1285- end
1286- if isa (sv, Exception)
1287- return sv
1288- end
1279+ unlock (require_lock) # temporarily _unlock_ during these operations
1280+ sv = try
1281+ if ocachepath != = nothing
1282+ @debug " Loading object cache file $ocachepath for $(repr (" text/plain" , pkg)) "
1283+ ccall (:jl_restore_package_image_from_file , Any, (Cstring, Any, Cint, Cstring, Cint), ocachepath, depmods, false , pkg. name, ignore_native)
1284+ else
1285+ @debug " Loading cache file $path for $(repr (" text/plain" , pkg)) "
1286+ ccall (:jl_restore_incremental , Any, (Cstring, Any, Cint, Cstring), path, depmods, false , pkg. name)
1287+ end
1288+ finally
1289+ lock (require_lock)
1290+ end
1291+ if isa (sv, Exception)
1292+ return sv
1293+ end
12891294
1290- restored = register_restored_modules (sv, pkg, path)
1295+ restored = register_restored_modules (sv, pkg, path)
12911296
1292- for M in restored
1293- M = M:: Module
1294- if parentmodule (M) === M && PkgId (M) == pkg
1295- register && register_root_module (M)
1296- if timing_imports
1297- elapsed_time = time_ns () - t_before
1298- comp_time, recomp_time = cumulative_compile_time_ns () .- t_comp_before
1299- print_time_imports_report (M, elapsed_time, comp_time, recomp_time)
1297+ for M in restored
1298+ M = M:: Module
1299+ if parentmodule (M) === M && PkgId (M) == pkg
1300+ register && register_root_module (M)
1301+ if timing_imports
1302+ elapsed_time = time_ns () - t_before
1303+ comp_time, recomp_time = cumulative_compile_time_ns () .- t_comp_before
1304+ print_time_imports_report (M, elapsed_time, comp_time, recomp_time)
1305+ end
1306+ return M
13001307 end
1301- return M
13021308 end
1303- end
1304- return ErrorException (" Required dependency $(repr (" text/plain" , pkg)) failed to load from a cache file." )
1309+ return ErrorException (" Required dependency $(repr (" text/plain" , pkg)) failed to load from a cache file." )
13051310
13061311 finally
13071312 timing_imports && cumulative_compile_timing (false )
@@ -2020,13 +2025,46 @@ end
20202025 if staledeps === true
20212026 continue
20222027 end
2023- try
2024- staledeps, ocachefile, newbuild_id = staledeps:: Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
2025- # finish checking staledeps module graph
2026- for i in eachindex (staledeps)
2028+ staledeps, ocachefile, newbuild_id = staledeps:: Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
2029+ startedloading = length (staledeps) + 1
2030+ try # any exit from here (goto, break, continue, return) will end_loading
2031+ # finish checking staledeps module graph, while acquiring all start_loading locks
2032+ # so that concurrent require calls won't make any different decisions that might conflict with the decisions here
2033+ # note that start_loading will drop the loading lock if necessary
2034+ let i = 0
2035+ # start_loading here has a deadlock problem if we try to load `A,B,C` and `B,A,D` at the same time:
2036+ # it will claim A,B have a cycle, but really they just have an ambiguous order and need to be batch-acquired rather than singly
2037+ # solve that by making sure we can start_loading everything before allocating each of those and doing all the stale checks
2038+ while i < length (staledeps)
2039+ i += 1
2040+ dep = staledeps[i]
2041+ dep isa Module && continue
2042+ _, modkey, modbuild_id = dep:: Tuple{String, PkgId, UInt128}
2043+ dep = canstart_loading (modkey, modbuild_id, stalecheck)
2044+ if dep isa Module
2045+ if PkgId (dep) == modkey && module_build_id (dep) === modbuild_id
2046+ staledeps[i] = dep
2047+ continue
2048+ else
2049+ @debug " Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
2050+ @goto check_next_path
2051+ end
2052+ continue
2053+ elseif dep === nothing
2054+ continue
2055+ end
2056+ wait (dep) # releases require_lock, so requires restarting this loop
2057+ i = 0
2058+ end
2059+ end
2060+ for i in reverse (eachindex (staledeps))
20272061 dep = staledeps[i]
20282062 dep isa Module && continue
20292063 modpath, modkey, modbuild_id = dep:: Tuple{String, PkgId, UInt128}
2064+ # inline a call to start_loading here
2065+ @assert canstart_loading (modkey, modbuild_id, stalecheck) === nothing
2066+ package_locks[modkey] = current_task () => Threads. Condition (require_lock)
2067+ startedloading = i
20302068 modpaths = find_all_in_cache_path (modkey, DEPOT_PATH )
20312069 for modpath_to_try in modpaths
20322070 modstaledeps = stale_cachefile (modkey, modbuild_id, modpath, modpath_to_try; stalecheck)
@@ -2054,37 +2092,22 @@ end
20542092 end
20552093 end
20562094 # finish loading module graph into staledeps
2057- # TODO : call all start_loading calls (in reverse order) before calling any _include_from_serialized, since start_loading will drop the loading lock
2095+ # n.b. this runs __init__ methods too early, so it is very unwise to have those, as they may see inconsistent loading state, causing them to fail unpredictably here
20582096 for i in eachindex (staledeps)
20592097 dep = staledeps[i]
20602098 dep isa Module && continue
20612099 modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep:: Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
2062- dep = start_loading (modkey, modbuild_id, stalecheck)
2063- while true
2064- if dep isa Module
2065- if PkgId (dep) == modkey && module_build_id (dep) === modbuild_id
2066- break
2067- else
2068- @debug " Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
2069- @goto check_next_path
2070- end
2071- end
2072- if dep === nothing
2073- try
2074- set_pkgorigin_version_path (modkey, modpath)
2075- dep = _include_from_serialized (modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
2076- finally
2077- end_loading (modkey, dep)
2078- end
2079- if ! isa (dep, Module)
2080- @debug " Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath ." exception= dep
2081- @goto check_next_path
2082- else
2083- push! (newdeps, modkey)
2084- end
2085- end
2100+ set_pkgorigin_version_path (modkey, modpath)
2101+ dep = _include_from_serialized (modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
2102+ if ! isa (dep, Module)
2103+ @debug " Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath ." exception= dep
2104+ @goto check_next_path
2105+ else
2106+ startedloading = i + 1
2107+ end_loading (modkey, dep)
2108+ staledeps[i] = dep
2109+ push! (newdeps, modkey)
20862110 end
2087- staledeps[i] = dep
20882111 end
20892112 restored = maybe_loaded_precompile (pkg, newbuild_id)
20902113 if ! isa (restored, Module)
@@ -2094,11 +2117,21 @@ end
20942117 @debug " Deserialization checks failed while attempting to load cache from $path_to_try " exception= restored
20952118 @label check_next_path
20962119 finally
2120+ # cancel all start_loading locks that were taken but not fulfilled before failing
2121+ for i in startedloading: length (staledeps)
2122+ dep = staledeps[i]
2123+ dep isa Module && continue
2124+ if dep isa Tuple{String, PkgId, UInt128}
2125+ _, modkey, _ = dep
2126+ else
2127+ _, modkey, _ = dep:: Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
2128+ end
2129+ end_loading (modkey, nothing )
2130+ end
20972131 for modkey in newdeps
20982132 insert_extension_triggers (modkey)
20992133 stalecheck && run_package_callbacks (modkey)
21002134 end
2101- empty! (newdeps)
21022135 end
21032136 end
21042137 end
@@ -2111,66 +2144,76 @@ const package_locks = Dict{PkgId,Pair{Task,Threads.Condition}}()
21112144debug_loading_deadlocks:: Bool = true # Enable a slightly more expensive, but more complete algorithm that can handle simultaneous tasks.
21122145 # This only triggers if you have multiple tasks trying to load the same package at the same time,
21132146 # so it is unlikely to make a performance difference normally.
2114- function start_loading (modkey :: PkgId , build_id :: UInt128 , stalecheck :: Bool )
2115- # handle recursive and concurrent calls to require
2147+
2148+ function canstart_loading (modkey :: PkgId , build_id :: UInt128 , stalecheck :: Bool )
21162149 assert_havelock (require_lock)
21172150 require_lock. reentrancy_cnt == 1 || throw (ConcurrencyViolationError (" recursive call to start_loading" ))
2118- while true
2119- loaded = stalecheck ? maybe_root_module (modkey) : nothing
2151+ loaded = stalecheck ? maybe_root_module (modkey) : nothing
2152+ loaded isa Module && return loaded
2153+ if build_id != UInt128 (0 )
2154+ loaded = maybe_loaded_precompile (modkey, build_id)
21202155 loaded isa Module && return loaded
2121- if build_id != UInt128 (0 )
2122- loaded = maybe_loaded_precompile (modkey, build_id)
2123- loaded isa Module && return loaded
2156+ end
2157+ loading = get (package_locks, modkey, nothing )
2158+ loading === nothing && return nothing
2159+ # load already in progress for this module on the task
2160+ task, cond = loading
2161+ deps = String[modkey. name]
2162+ pkgid = modkey
2163+ assert_havelock (cond. lock)
2164+ if debug_loading_deadlocks && current_task () != = task
2165+ waiters = Dict {Task,Pair{Task,PkgId}} () # invert to track waiting tasks => loading tasks
2166+ for each in package_locks
2167+ cond2 = each[2 ][2 ]
2168+ assert_havelock (cond2. lock)
2169+ for waiting in cond2. waitq
2170+ push! (waiters, waiting => (each[2 ][1 ] => each[1 ]))
2171+ end
21242172 end
2125- loading = get (package_locks, modkey, nothing )
2126- if loading === nothing
2127- package_locks[modkey] = current_task () => Threads. Condition (require_lock)
2128- return nothing
2173+ while true
2174+ running = get (waiters, task, nothing )
2175+ running === nothing && break
2176+ task, pkgid = running
2177+ push! (deps, pkgid. name)
2178+ task === current_task () && break
21292179 end
2130- # load already in progress for this module on the task
2131- task, cond = loading
2132- deps = String[modkey. name]
2133- pkgid = modkey
2134- assert_havelock (cond. lock)
2135- if debug_loading_deadlocks && current_task () != = task
2136- waiters = Dict {Task,Pair{Task,PkgId}} () # invert to track waiting tasks => loading tasks
2137- for each in package_locks
2138- cond2 = each[2 ][2 ]
2139- assert_havelock (cond2. lock)
2140- for waiting in cond2. waitq
2141- push! (waiters, waiting => (each[2 ][1 ] => each[1 ]))
2142- end
2143- end
2144- while true
2145- running = get (waiters, task, nothing )
2146- running === nothing && break
2147- task, pkgid = running
2148- push! (deps, pkgid. name)
2149- task === current_task () && break
2180+ end
2181+ if current_task () === task
2182+ others = String[modkey. name] # repeat this to emphasize the cycle here
2183+ for each in package_locks # list the rest of the packages being loaded too
2184+ if each[2 ][1 ] === task
2185+ other = each[1 ]. name
2186+ other == modkey. name || other == pkgid. name || push! (others, other)
21502187 end
21512188 end
2152- if current_task () === task
2153- others = String[modkey. name] # repeat this to emphasize the cycle here
2154- for each in package_locks # list the rest of the packages being loaded too
2155- if each[2 ][1 ] === task
2156- other = each[1 ]. name
2157- other == modkey. name || other == pkgid. name || push! (others, other)
2158- end
2159- end
2160- msg = sprint (deps, others) do io, deps, others
2161- print (io, " deadlock detected in loading " )
2162- join (io, deps, " -> " )
2163- print (io, " -> " )
2164- join (io, others, " && " )
2165- end
2166- throw (ConcurrencyViolationError (msg))
2189+ msg = sprint (deps, others) do io, deps, others
2190+ print (io, " deadlock detected in loading " )
2191+ join (io, deps, " -> " )
2192+ print (io, " -> " )
2193+ join (io, others, " && " )
2194+ end
2195+ throw (ConcurrencyViolationError (msg))
2196+ end
2197+ return cond
2198+ end
2199+
2200+ function start_loading (modkey:: PkgId , build_id:: UInt128 , stalecheck:: Bool )
2201+ # handle recursive and concurrent calls to require
2202+ while true
2203+ loaded = canstart_loading (modkey, build_id, stalecheck)
2204+ if loaded === nothing
2205+ package_locks[modkey] = current_task () => Threads. Condition (require_lock)
2206+ return nothing
2207+ elseif loaded isa Module
2208+ return loaded
21672209 end
2168- loaded = wait (cond )
2210+ loaded = wait (loaded )
21692211 loaded isa Module && return loaded
21702212 end
21712213end
21722214
21732215function end_loading (modkey:: PkgId , @nospecialize loaded)
2216+ assert_havelock (require_lock)
21742217 loading = pop! (package_locks, modkey)
21752218 notify (loading[2 ], loaded, all= true )
21762219 nothing
@@ -2650,6 +2693,7 @@ function _require(pkg::PkgId, env=nothing)
26502693end
26512694
26522695# load a serialized file directly, including dependencies (without checking staleness except for immediate conflicts)
2696+ # this does not call start_loading / end_loading, so can lead to some odd behaviors
26532697function _require_from_serialized (uuidkey:: PkgId , path:: String , ocachepath:: Union{String, Nothing} , sourcepath:: String )
26542698 @lock require_lock begin
26552699 set_pkgorigin_version_path (uuidkey, sourcepath)
0 commit comments