Skip to content

Commit d4e0f36

Browse files
committed
Merge pull request #48513 from JuliaLang/jn/extend-once
ensure extension triggers are only run by the package that satified them
1 parent a83399c commit d4e0f36

File tree

1 file changed

+94
-55
lines changed

1 file changed

+94
-55
lines changed

base/loading.jl

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,9 +1076,9 @@ function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String)
10761076
end
10771077

10781078
function run_package_callbacks(modkey::PkgId)
1079+
run_extension_callbacks(modkey)
10791080
assert_havelock(require_lock)
10801081
unlock(require_lock)
1081-
run_extension_callbacks()
10821082
try
10831083
for callback in package_callbacks
10841084
invokelatest(callback, modkey)
@@ -1099,23 +1099,23 @@ end
10991099
##############
11001100

11011101
mutable struct ExtensionId
1102-
const id::PkgId # Could be symbol?
1103-
const parentid::PkgId
1104-
const triggers::Vector{PkgId} # What packages have to be loaded for the extension to get loaded
1105-
triggered::Bool
1106-
succeeded::Bool
1102+
const id::PkgId
1103+
const parentid::PkgId # just need the name, for printing
1104+
ntriggers::Int # how many more packages must be defined until this is loaded
11071105
end
11081106

1109-
const EXT_DORMITORY = ExtensionId[]
1107+
const EXT_DORMITORY = Dict{PkgId,Vector{ExtensionId}}()
1108+
const EXT_DORMITORY_FAILED = ExtensionId[]
11101109

11111110
function insert_extension_triggers(pkg::PkgId)
11121111
pkg.uuid === nothing && return
1112+
extensions_added = Set{PkgId}()
11131113
for env in load_path()
1114-
insert_extension_triggers(env, pkg)
1114+
insert_extension_triggers!(extensions_added, env, pkg)
11151115
end
11161116
end
11171117

1118-
function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing}
1118+
function insert_extension_triggers!(extensions_added::Set{PkgId}, env::String, pkg::PkgId)::Union{Nothing,Missing}
11191119
project_file = env_project_file(env)
11201120
if project_file isa String
11211121
manifest_file = project_file_manifest_path(project_file)
@@ -1133,7 +1133,7 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi
11331133
extensions === nothing && return
11341134
weakdeps === nothing && return
11351135
if weakdeps isa Dict{String, Any}
1136-
return _insert_extension_triggers(pkg, extensions, weakdeps)
1136+
return _insert_extension_triggers!(extensions_added, pkg, extensions, weakdeps)
11371137
end
11381138

11391139
d_weakdeps = Dict{String, String}()
@@ -1148,70 +1148,97 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi
11481148
d_weakdeps[dep_name] = uuid
11491149
end
11501150
@assert length(d_weakdeps) == length(weakdeps)
1151-
return _insert_extension_triggers(pkg, extensions, d_weakdeps)
1151+
return _insert_extension_triggers!(extensions_added, pkg, extensions, d_weakdeps)
11521152
end
11531153
end
11541154
end
11551155
end
11561156
return nothing
11571157
end
11581158

1159-
function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, <:Any}, weakdeps::Dict{String, <:Any})
1159+
function _insert_extension_triggers!(extensions_added::Set{PkgId}, parent::PkgId, extensions::Dict{String, <:Any}, weakdeps::Dict{String, <:Any})
11601160
for (ext::String, triggers::Union{String, Vector{String}}) in extensions
11611161
triggers isa String && (triggers = [triggers])
1162-
triggers_id = PkgId[]
11631162
id = PkgId(uuid5(parent.uuid, ext), ext)
1163+
# Only add triggers for an extension from one env.
1164+
id in extensions_added && continue
1165+
push!(extensions_added, id)
1166+
gid = ExtensionId(id, parent, 1 + length(triggers))
1167+
trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, parent)
1168+
push!(trigger1, gid)
11641169
for trigger in triggers
11651170
# TODO: Better error message if this lookup fails?
11661171
uuid_trigger = UUID(weakdeps[trigger]::String)
1167-
push!(triggers_id, PkgId(uuid_trigger, trigger))
1172+
trigger_id = PkgId(uuid_trigger, trigger)
1173+
if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id)
1174+
trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
1175+
push!(trigger1, gid)
1176+
else
1177+
gid.ntriggers -= 1
1178+
end
11681179
end
1169-
gid = ExtensionId(id, parent, triggers_id, false, false)
1170-
push!(EXT_DORMITORY, gid)
11711180
end
11721181
end
11731182

1174-
function run_extension_callbacks(; force::Bool=false)
1175-
try
1176-
# TODO, if `EXT_DORMITORY` becomes very long, do something smarter
1177-
for extid in EXT_DORMITORY
1178-
extid.succeeded && continue
1179-
!force && extid.triggered && continue
1180-
if all(x -> haskey(Base.loaded_modules, x) && !haskey(package_locks, x), extid.triggers)
1181-
ext_not_allowed_load = nothing
1182-
extid.triggered = true
1183-
# It is possible that some of the triggers were loaded in an environment
1184-
# below the one of the parent. This will cause a load failure when the
1185-
# pkg ext tries to load the triggers. Therefore, check this first
1186-
# before loading the pkg ext.
1187-
for trigger in extid.triggers
1188-
pkgenv = Base.identify_package_env(extid.id, trigger.name)
1189-
if pkgenv === nothing
1190-
ext_not_allowed_load = trigger
1191-
break
1192-
else
1193-
pkg, env = pkgenv
1194-
path = Base.locate_package(pkg, env)
1195-
if path === nothing
1196-
ext_not_allowed_load = trigger
1197-
break
1198-
end
1199-
end
1200-
end
1201-
if ext_not_allowed_load !== nothing
1202-
@debug "Extension $(extid.id.name) of $(extid.parentid.name) not loaded due to \
1203-
$(ext_not_allowed_load.name) loaded in environment lower in load path"
1204-
else
1205-
require(extid.id)
1206-
@debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded"
1183+
function run_extension_callbacks(extid::ExtensionId)
1184+
assert_havelock(require_lock)
1185+
succeeded = try
1186+
_require_prelocked(extid.id)
1187+
@debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded"
1188+
true
1189+
catch
1190+
# Try to continue loading if loading an extension errors
1191+
@error "Error during loading of extension $(extid.id.name) of $(extid.parentid.name)"
1192+
false
1193+
end
1194+
return succeeded
1195+
end
1196+
1197+
function run_extension_callbacks(pkgid::PkgId)
1198+
assert_havelock(require_lock)
1199+
# take ownership of extids that depend on this pkgid
1200+
extids = pop!(EXT_DORMITORY, pkgid, nothing)
1201+
extids === nothing && return
1202+
for extid in extids
1203+
if extid.ntriggers > 0
1204+
# It is possible that pkgid was loaded in an environment
1205+
# below the one of the parent. This will cause a load failure when the
1206+
# pkg ext tries to load the triggers. Therefore, check this first
1207+
# before loading the pkg ext.
1208+
pkgenv = Base.identify_package_env(extid.id, pkgid.name)
1209+
ext_not_allowed_load = false
1210+
if pkgenv === nothing
1211+
ext_not_allowed_load = true
1212+
else
1213+
pkg, env = pkgenv
1214+
path = Base.locate_package(pkg, env)
1215+
if path === nothing
1216+
ext_not_allowed_load = true
12071217
end
1208-
extid.succeeded = true
1218+
end
1219+
if ext_not_allowed_load
1220+
@debug "Extension $(extid.id.name) of $(extid.parentid.name) will not be loaded \
1221+
since $(pkgid.name) loaded in environment lower in load path"
1222+
# indicate extid is expected to fail
1223+
extid.ntriggers *= -1
1224+
else
1225+
# indicate pkgid is loaded
1226+
extid.ntriggers -= 1
12091227
end
12101228
end
1211-
catch
1212-
# Try to continue loading if loading an extension errors
1213-
errs = current_exceptions()
1214-
@error "Error during loading of extension" exception=errs
1229+
if extid.ntriggers < 0
1230+
# indicate pkgid is loaded
1231+
extid.ntriggers += 1
1232+
succeeded = false
1233+
else
1234+
succeeded = true
1235+
end
1236+
if extid.ntriggers == 0
1237+
# actually load extid, now that all dependencies are met,
1238+
# and record the result
1239+
succeeded = succeeded && run_extension_callbacks(extid)
1240+
succeeded || push!(EXT_DORMITORY_FAILED, extid)
1241+
end
12151242
end
12161243
nothing
12171244
end
@@ -1224,7 +1251,19 @@ This is used in cases where the automatic loading of an extension failed
12241251
due to some problem with the extension. Instead of restarting the Julia session,
12251252
the extension can be fixed, and this function run.
12261253
"""
1227-
retry_load_extensions() = run_extension_callbacks(; force=true)
1254+
function retry_load_extensions()
1255+
@lock require_lock begin
1256+
# this copy is desired since run_extension_callbacks will release this lock
1257+
# so this can still mutate the list to drop successful ones
1258+
failed = copy(EXT_DORMITORY_FAILED)
1259+
empty!(EXT_DORMITORY_FAILED)
1260+
filter!(failed) do extid
1261+
return !run_extension_callbacks(extid)
1262+
end
1263+
prepend!(EXT_DORMITORY_FAILED, failed)
1264+
end
1265+
nothing
1266+
end
12281267

12291268
"""
12301269
get_extension(parent::Module, extension::Symbol)

0 commit comments

Comments
 (0)