@@ -1261,47 +1261,52 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No
1261
1261
assert_havelock (require_lock)
1262
1262
timing_imports = TIMING_IMPORTS[] > 0
1263
1263
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
1269
1269
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
1278
1278
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
1289
1294
1290
- restored = register_restored_modules (sv, pkg, path)
1295
+ restored = register_restored_modules (sv, pkg, path)
1291
1296
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
1300
1307
end
1301
- return M
1302
1308
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." )
1305
1310
1306
1311
finally
1307
1312
timing_imports && cumulative_compile_timing (false )
@@ -2020,13 +2025,46 @@ end
2020
2025
if staledeps === true
2021
2026
continue
2022
2027
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))
2027
2061
dep = staledeps[i]
2028
2062
dep isa Module && continue
2029
2063
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
2030
2068
modpaths = find_all_in_cache_path (modkey, DEPOT_PATH )
2031
2069
for modpath_to_try in modpaths
2032
2070
modstaledeps = stale_cachefile (modkey, modbuild_id, modpath, modpath_to_try; stalecheck)
@@ -2054,37 +2092,22 @@ end
2054
2092
end
2055
2093
end
2056
2094
# 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
2058
2096
for i in eachindex (staledeps)
2059
2097
dep = staledeps[i]
2060
2098
dep isa Module && continue
2061
2099
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)
2086
2110
end
2087
- staledeps[i] = dep
2088
2111
end
2089
2112
restored = maybe_loaded_precompile (pkg, newbuild_id)
2090
2113
if ! isa (restored, Module)
@@ -2094,11 +2117,21 @@ end
2094
2117
@debug " Deserialization checks failed while attempting to load cache from $path_to_try " exception= restored
2095
2118
@label check_next_path
2096
2119
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
2097
2131
for modkey in newdeps
2098
2132
insert_extension_triggers (modkey)
2099
2133
stalecheck && run_package_callbacks (modkey)
2100
2134
end
2101
- empty! (newdeps)
2102
2135
end
2103
2136
end
2104
2137
end
@@ -2111,66 +2144,76 @@ const package_locks = Dict{PkgId,Pair{Task,Threads.Condition}}()
2111
2144
debug_loading_deadlocks:: Bool = true # Enable a slightly more expensive, but more complete algorithm that can handle simultaneous tasks.
2112
2145
# This only triggers if you have multiple tasks trying to load the same package at the same time,
2113
2146
# 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 )
2116
2149
assert_havelock (require_lock)
2117
2150
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)
2120
2155
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
2124
2172
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
2129
2179
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)
2150
2187
end
2151
2188
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
2167
2209
end
2168
- loaded = wait (cond )
2210
+ loaded = wait (loaded )
2169
2211
loaded isa Module && return loaded
2170
2212
end
2171
2213
end
2172
2214
2173
2215
function end_loading (modkey:: PkgId , @nospecialize loaded)
2216
+ assert_havelock (require_lock)
2174
2217
loading = pop! (package_locks, modkey)
2175
2218
notify (loading[2 ], loaded, all= true )
2176
2219
nothing
@@ -2650,6 +2693,7 @@ function _require(pkg::PkgId, env=nothing)
2650
2693
end
2651
2694
2652
2695
# 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
2653
2697
function _require_from_serialized (uuidkey:: PkgId , path:: String , ocachepath:: Union{String, Nothing} , sourcepath:: String )
2654
2698
@lock require_lock begin
2655
2699
set_pkgorigin_version_path (uuidkey, sourcepath)
0 commit comments