Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 94 additions & 55 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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}()
Expand All @@ -1148,70 +1148,97 @@ 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
end
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
Expand All @@ -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)
Expand Down