diff --git a/base/loading.jl b/base/loading.jl index beaa5e6f73a2b..2de3c9894a3ce 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1076,9 +1076,9 @@ function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String) end function run_package_callbacks(modkey::PkgId) + run_extension_callbacks(modkey) assert_havelock(require_lock) unlock(require_lock) - run_extension_callbacks() try for callback in package_callbacks invokelatest(callback, modkey) @@ -1099,23 +1099,23 @@ end ############## mutable struct ExtensionId - const id::PkgId # Could be symbol? - const parentid::PkgId - const triggers::Vector{PkgId} # What packages have to be loaded for the extension to get loaded - triggered::Bool - succeeded::Bool + const id::PkgId + const parentid::PkgId # just need the name, for printing + ntriggers::Int # how many more packages must be defined until this is loaded end -const EXT_DORMITORY = ExtensionId[] +const EXT_DORMITORY = Dict{PkgId,Vector{ExtensionId}}() +const EXT_DORMITORY_FAILED = ExtensionId[] function insert_extension_triggers(pkg::PkgId) pkg.uuid === nothing && return + extensions_added = Set{PkgId}() for env in load_path() - insert_extension_triggers(env, pkg) + insert_extension_triggers!(extensions_added, env, pkg) end end -function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing} +function insert_extension_triggers!(extensions_added::Set{PkgId}, env::String, pkg::PkgId)::Union{Nothing,Missing} project_file = env_project_file(env) if project_file isa String manifest_file = project_file_manifest_path(project_file) @@ -1133,7 +1133,7 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi extensions === nothing && return weakdeps === nothing && return if weakdeps isa Dict{String, Any} - return _insert_extension_triggers(pkg, extensions, weakdeps) + return _insert_extension_triggers!(extensions_added, pkg, extensions, weakdeps) end d_weakdeps = Dict{String, String}() @@ -1148,7 +1148,7 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi d_weakdeps[dep_name] = uuid end @assert length(d_weakdeps) == length(weakdeps) - return _insert_extension_triggers(pkg, extensions, d_weakdeps) + return _insert_extension_triggers!(extensions_added, pkg, extensions, d_weakdeps) end end end @@ -1156,62 +1156,89 @@ function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missi return nothing end -function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, <:Any}, weakdeps::Dict{String, <:Any}) +function _insert_extension_triggers!(extensions_added::Set{PkgId}, parent::PkgId, extensions::Dict{String, <:Any}, weakdeps::Dict{String, <:Any}) for (ext::String, triggers::Union{String, Vector{String}}) in extensions triggers isa String && (triggers = [triggers]) - triggers_id = PkgId[] id = PkgId(uuid5(parent.uuid, ext), ext) + # Only add triggers for an extension from one env. + id in extensions_added && continue + push!(extensions_added, id) + gid = ExtensionId(id, parent, 1 + length(triggers)) + trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, parent) + push!(trigger1, gid) for trigger in triggers # TODO: Better error message if this lookup fails? uuid_trigger = UUID(weakdeps[trigger]::String) - push!(triggers_id, PkgId(uuid_trigger, trigger)) + trigger_id = PkgId(uuid_trigger, trigger) + if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id) + trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id) + push!(trigger1, gid) + else + gid.ntriggers -= 1 + end end - gid = ExtensionId(id, parent, triggers_id, false, false) - push!(EXT_DORMITORY, gid) end end -function run_extension_callbacks(; force::Bool=false) - try - # TODO, if `EXT_DORMITORY` becomes very long, do something smarter - for extid in EXT_DORMITORY - extid.succeeded && continue - !force && extid.triggered && continue - if all(x -> haskey(Base.loaded_modules, x) && !haskey(package_locks, x), extid.triggers) - ext_not_allowed_load = nothing - extid.triggered = true - # It is possible that some of the triggers were loaded in an environment - # below the one of the parent. This will cause a load failure when the - # pkg ext tries to load the triggers. Therefore, check this first - # before loading the pkg ext. - for trigger in extid.triggers - pkgenv = Base.identify_package_env(extid.id, trigger.name) - if pkgenv === nothing - ext_not_allowed_load = trigger - break - else - pkg, env = pkgenv - path = Base.locate_package(pkg, env) - if path === nothing - ext_not_allowed_load = trigger - break - end - end - end - if ext_not_allowed_load !== nothing - @debug "Extension $(extid.id.name) of $(extid.parentid.name) not loaded due to \ - $(ext_not_allowed_load.name) loaded in environment lower in load path" - else - require(extid.id) - @debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded" +function run_extension_callbacks(extid::ExtensionId) + assert_havelock(require_lock) + succeeded = try + _require_prelocked(extid.id) + @debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded" + true + catch + # Try to continue loading if loading an extension errors + @error "Error during loading of extension $(extid.id.name) of $(extid.parentid.name)" + false + end + return succeeded +end + +function run_extension_callbacks(pkgid::PkgId) + assert_havelock(require_lock) + # take ownership of extids that depend on this pkgid + extids = pop!(EXT_DORMITORY, pkgid, nothing) + extids === nothing && return + for extid in extids + if extid.ntriggers > 0 + # It is possible that pkgid was loaded in an environment + # below the one of the parent. This will cause a load failure when the + # pkg ext tries to load the triggers. Therefore, check this first + # before loading the pkg ext. + pkgenv = Base.identify_package_env(extid.id, pkgid.name) + ext_not_allowed_load = false + if pkgenv === nothing + ext_not_allowed_load = true + else + pkg, env = pkgenv + path = Base.locate_package(pkg, env) + if path === nothing + ext_not_allowed_load = true end - extid.succeeded = true + end + if ext_not_allowed_load + @debug "Extension $(extid.id.name) of $(extid.parentid.name) will not be loaded \ + since $(pkgid.name) loaded in environment lower in load path" + # indicate extid is expected to fail + extid.ntriggers *= -1 + else + # indicate pkgid is loaded + extid.ntriggers -= 1 end end - catch - # Try to continue loading if loading an extension errors - errs = current_exceptions() - @error "Error during loading of extension" exception=errs + if extid.ntriggers < 0 + # indicate pkgid is loaded + extid.ntriggers += 1 + succeeded = false + else + succeeded = true + end + if extid.ntriggers == 0 + # actually load extid, now that all dependencies are met, + # and record the result + succeeded = succeeded && run_extension_callbacks(extid) + succeeded || push!(EXT_DORMITORY_FAILED, extid) + end end nothing end @@ -1224,7 +1251,19 @@ This is used in cases where the automatic loading of an extension failed due to some problem with the extension. Instead of restarting the Julia session, the extension can be fixed, and this function run. """ -retry_load_extensions() = run_extension_callbacks(; force=true) +function retry_load_extensions() + @lock require_lock begin + # this copy is desired since run_extension_callbacks will release this lock + # so this can still mutate the list to drop successful ones + failed = copy(EXT_DORMITORY_FAILED) + empty!(EXT_DORMITORY_FAILED) + filter!(failed) do extid + return !run_extension_callbacks(extid) + end + prepend!(EXT_DORMITORY_FAILED, failed) + end + nothing +end """ get_extension(parent::Module, extension::Symbol)