Skip to content

Commit 75166fe

Browse files
KristofferCnickrobinson251
authored andcommitted
Extensions: make loading of extensions independent of what packages are in the sysimage (JuliaLang#52841)
When triggers of extension are in the sysimage it is easy to end up with cycles in package loading. Say we have a package A with exts BExt and CExt and say that B and C is in the sysimage. - Upon loading A, we will immidiately start to precompile BExt (because the trigger B is "loaded" by virtue of being in the sysimage). - BExt will load A which will cause CExt to start precompiling (again because C is in the sysimage). - CExt will load A which will now cause BExt to start loading and we get a cycle. This is fixed in this PR by instead of looking at what modules are loaded, we look at what modules are actually `require`d and only use that to drive the loading of extensions. Fixes JuliaLang#52132. (cherry picked from commit 08d229f)
1 parent 4c16ff4 commit 75166fe

File tree

6 files changed

+43
-121
lines changed

6 files changed

+43
-121
lines changed

base/Base.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ function __init__()
595595
init_load_path()
596596
init_active_project()
597597
append!(empty!(_sysimage_modules), keys(loaded_modules))
598+
empty!(explicit_loaded_modules)
598599
if haskey(ENV, "JULIA_MAX_NUM_PRECOMPILE_FILES")
599600
MAX_NUM_PRECOMPILE_FILES[] = parse(Int, ENV["JULIA_MAX_NUM_PRECOMPILE_FILES"])
600601
end

base/loading.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,7 +1344,7 @@ function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}
13441344
# TODO: Better error message if this lookup fails?
13451345
uuid_trigger = UUID(totaldeps[trigger]::String)
13461346
trigger_id = PkgId(uuid_trigger, trigger)
1347-
if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id) || (trigger_id == precompilation_target)
1347+
if !haskey(explicit_loaded_modules, trigger_id) || haskey(package_locks, trigger_id) || (trigger_id == precompilation_target)
13481348
trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
13491349
push!(trigger1, gid)
13501350
else
@@ -1891,6 +1891,11 @@ function __require_prelocked(uuidkey::PkgId, env=nothing)
18911891
REPL_MODULE_REF[] = newm
18921892
end
18931893
else
1894+
m = get(loaded_modules, uuidkey, nothing)
1895+
if m !== nothing
1896+
explicit_loaded_modules[uuidkey] = m
1897+
run_package_callbacks(uuidkey)
1898+
end
18941899
newm = root_module(uuidkey)
18951900
end
18961901
return newm
@@ -1905,6 +1910,8 @@ PkgOrigin() = PkgOrigin(nothing, nothing, nothing)
19051910
const pkgorigins = Dict{PkgId,PkgOrigin}()
19061911

19071912
const loaded_modules = Dict{PkgId,Module}()
1913+
# Emptied on Julia start
1914+
const explicit_loaded_modules = Dict{PkgId,Module}()
19081915
const loaded_modules_order = Vector{Module}()
19091916
const module_keys = IdDict{Module,PkgId}() # the reverse
19101917

@@ -1928,6 +1935,7 @@ root_module_key(m::Module) = @lock require_lock module_keys[m]
19281935
end
19291936
push!(loaded_modules_order, m)
19301937
loaded_modules[key] = m
1938+
explicit_loaded_modules[key] = m
19311939
module_keys[m] = key
19321940
end
19331941
nothing
@@ -2110,7 +2118,6 @@ function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Unio
21102118
end
21112119

21122120

2113-
21142121
# relative-path load
21152122

21162123
"""

test/loading.jl

Lines changed: 20 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -795,10 +795,8 @@ end
795795
@testset "`Base.project_names` and friends" begin
796796
# Some functions in Pkg assumes that these tuples have the same length
797797
n = length(Base.project_names)
798+
@test length(Base.manifest_names) == n
798799
@test length(Base.preferences_names) == n
799-
800-
# there are two manifest names per project name
801-
@test length(Base.manifest_names) == 2n
802800
end
803801

804802
@testset "Manifest formats" begin
@@ -827,33 +825,6 @@ end
827825
end
828826
end
829827

830-
@testset "Manifest name preferential loading" begin
831-
mktempdir() do tmp
832-
proj = joinpath(tmp, "Project.toml")
833-
touch(proj)
834-
for man_name in (
835-
"Manifest.toml",
836-
"JuliaManifest.toml",
837-
"Manifest-v$(VERSION.major).$(VERSION.minor).toml",
838-
"JuliaManifest-v$(VERSION.major).$(VERSION.minor).toml"
839-
)
840-
touch(joinpath(tmp, man_name))
841-
man = basename(Base.project_file_manifest_path(proj))
842-
@test man == man_name
843-
end
844-
end
845-
mktempdir() do tmp
846-
# check that another version isn't preferred
847-
proj = joinpath(tmp, "Project.toml")
848-
touch(proj)
849-
touch(joinpath(tmp, "Manifest-v1.5.toml"))
850-
@test Base.project_file_manifest_path(proj) == nothing
851-
touch(joinpath(tmp, "Manifest.toml"))
852-
man = basename(Base.project_file_manifest_path(proj))
853-
@test man == "Manifest.toml"
854-
end
855-
end
856-
857828
@testset "error message loading pkg bad module name" begin
858829
mktempdir() do tmp
859830
old_loadpath = copy(LOAD_PATH)
@@ -1033,16 +1004,6 @@ end
10331004
end
10341005

10351006
@testset "Extensions" begin
1036-
test_ext = """
1037-
function test_ext(parent::Module, ext::Symbol)
1038-
_ext = Base.get_extension(parent, ext)
1039-
_ext isa Module || error("expected extension \$ext to be loaded")
1040-
_pkgdir = pkgdir(_ext)
1041-
_pkgdir == pkgdir(parent) != nothing || error("unexpected extension \$ext pkgdir path: \$_pkgdir")
1042-
_pkgversion = pkgversion(_ext)
1043-
_pkgversion == pkgversion(parent) || error("unexpected extension \$ext version: \$_pkgversion")
1044-
end
1045-
"""
10461007
depot_path = mktempdir()
10471008
try
10481009
proj = joinpath(@__DIR__, "project", "Extensions", "HasDepWithExtensions.jl")
@@ -1053,25 +1014,20 @@ end
10531014
cmd = """
10541015
$load_distr
10551016
begin
1056-
$ew $test_ext
10571017
$ew push!(empty!(DEPOT_PATH), $(repr(depot_path)))
10581018
using HasExtensions
10591019
$ew using HasExtensions
10601020
$ew Base.get_extension(HasExtensions, :Extension) === nothing || error("unexpectedly got an extension")
10611021
$ew HasExtensions.ext_loaded && error("ext_loaded set")
10621022
using HasDepWithExtensions
10631023
$ew using HasDepWithExtensions
1064-
$ew test_ext(HasExtensions, :Extension)
10651024
$ew Base.get_extension(HasExtensions, :Extension).extvar == 1 || error("extvar in Extension not set")
10661025
$ew HasExtensions.ext_loaded || error("ext_loaded not set")
10671026
$ew HasExtensions.ext_folder_loaded && error("ext_folder_loaded set")
10681027
$ew HasDepWithExtensions.do_something() || error("do_something errored")
10691028
using ExtDep2
10701029
$ew using ExtDep2
10711030
$ew HasExtensions.ext_folder_loaded || error("ext_folder_loaded not set")
1072-
using ExtDep3
1073-
$ew using ExtDep3
1074-
$ew HasExtensions.ext_dep_loaded || error("ext_dep_loaded not set")
10751031
end
10761032
"""
10771033
return `$(Base.julia_cmd()) $compile --startup-file=no -e $cmd`
@@ -1114,14 +1070,11 @@ end
11141070

11151071
test_ext_proj = """
11161072
begin
1117-
$test_ext
11181073
using HasExtensions
11191074
using ExtDep
1120-
test_ext(HasExtensions, :Extension)
1075+
Base.get_extension(HasExtensions, :Extension) isa Module || error("expected extension to load")
11211076
using ExtDep2
1122-
test_ext(HasExtensions, :ExtensionFolder)
1123-
using ExtDep3
1124-
test_ext(HasExtensions, :ExtensionDep)
1077+
Base.get_extension(HasExtensions, :ExtensionFolder) isa Module || error("expected extension to load")
11251078
end
11261079
"""
11271080
for compile in (`--compiled-modules=no`, ``)
@@ -1131,40 +1084,23 @@ end
11311084
run(cmd_proj_ext)
11321085
end
11331086

1134-
# Extensions for "parent" dependencies
1135-
# (i.e. an `ExtAB` where A depends on / loads B, but B provides the extension)
1136-
1137-
mktempdir() do depot # Parallel pre-compilation
1138-
code = """
1139-
Base.disable_parallel_precompile = false
1140-
using Parent
1141-
Base.get_extension(getfield(Parent, :DepWithParentExt), :ParentExt) isa Module || error("expected extension to load")
1142-
Parent.greet()
1143-
"""
1144-
proj = joinpath(@__DIR__, "project", "Extensions", "Parent.jl")
1145-
cmd = `$(Base.julia_cmd()) --startup-file=no -e $code`
1146-
cmd = addenv(cmd,
1147-
"JULIA_LOAD_PATH" => proj,
1148-
"JULIA_DEPOT_PATH" => depot * Base.Filesystem.pathsep(),
1149-
)
1150-
@test occursin("Hello parent!", String(read(cmd)))
1151-
end
1152-
mktempdir() do depot # Serial pre-compilation
1153-
code = """
1154-
Base.disable_parallel_precompile = true
1155-
using Parent
1156-
Base.get_extension(getfield(Parent, :DepWithParentExt), :ParentExt) isa Module || error("expected extension to load")
1157-
Parent.greet()
1158-
"""
1159-
proj = joinpath(@__DIR__, "project", "Extensions", "Parent.jl")
1160-
cmd = `$(Base.julia_cmd()) --startup-file=no -e $code`
1161-
cmd = addenv(cmd,
1162-
"JULIA_LOAD_PATH" => proj,
1163-
"JULIA_DEPOT_PATH" => depot * Base.Filesystem.pathsep(),
1164-
)
1165-
@test occursin("Hello parent!", String(read(cmd)))
1166-
end
1167-
1087+
# Sysimage extensions
1088+
# The test below requires that LinearAlgebra is in the sysimage and that it has not been loaded yet.
1089+
# if it gets moved out, this test will need to be updated.
1090+
# We run this test in a new process so we are not vulnerable to a previous test having loaded LinearAlgebra
1091+
sysimg_ext_test_code = """
1092+
uuid_key = Base.PkgId(Base.UUID("37e2e46d-f89d-539d-b4ee-838fcccc9c8e"), "LinearAlgebra")
1093+
Base.in_sysimage(uuid_key) || error("LinearAlgebra not in sysimage")
1094+
haskey(Base.explicit_loaded_modules, uuid_key) && error("LinearAlgebra already loaded")
1095+
using HasExtensions
1096+
Base.get_extension(HasExtensions, :LinearAlgebraExt) === nothing || error("unexpectedly got an extension")
1097+
using LinearAlgebra
1098+
haskey(Base.explicit_loaded_modules, uuid_key) || error("LinearAlgebra not loaded")
1099+
Base.get_extension(HasExtensions, :LinearAlgebraExt) isa Module || error("expected extension to load")
1100+
"""
1101+
cmd = `$(Base.julia_cmd()) --startup-file=no -e $sysimg_ext_test_code`
1102+
cmd = addenv(cmd, "JULIA_LOAD_PATH" => join([proj, "@stdlib"], sep))
1103+
run(cmd)
11681104
finally
11691105
try
11701106
rm(depot_path, force=true, recursive=true)
@@ -1265,28 +1201,3 @@ end
12651201
@testset "Upgradable stdlibs" begin
12661202
@test success(`$(Base.julia_cmd()) --startup-file=no -e 'using DelimitedFiles'`)
12671203
end
1268-
1269-
@testset "Fallback for stdlib deps if manifest deps aren't found" begin
1270-
mktempdir() do depot
1271-
# This manifest has a LibGit2 entry that is missing LibGit2_jll, which should be
1272-
# handled by falling back to the stdlib Project.toml for dependency truth.
1273-
badmanifest_test_dir = joinpath(@__DIR__, "project", "deps", "BadStdlibDeps.jl")
1274-
@test success(addenv(
1275-
`$(Base.julia_cmd()) --project=$badmanifest_test_dir --startup-file=no -e 'using LibGit2'`,
1276-
"JULIA_DEPOT_PATH" => depot * Base.Filesystem.pathsep(),
1277-
))
1278-
end
1279-
end
1280-
1281-
@testset "extension path computation name collision" begin
1282-
old_load_path = copy(LOAD_PATH)
1283-
try
1284-
empty!(LOAD_PATH)
1285-
push!(LOAD_PATH, joinpath(@__DIR__, "project", "Extensions", "ExtNameCollision_A"))
1286-
push!(LOAD_PATH, joinpath(@__DIR__, "project", "Extensions", "ExtNameCollision_B"))
1287-
ext_B = Base.PkgId(Base.uuid5(Base.identify_package("ExtNameCollision_B").uuid, "REPLExt"), "REPLExt")
1288-
@test Base.locate_package(ext_B) == joinpath(@__DIR__, "project", "Extensions", "ExtNameCollision_B", "ext", "REPLExt.jl")
1289-
finally
1290-
copy!(LOAD_PATH, old_load_path)
1291-
end
1292-
end

test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# This file is machine-generated - editing it directly is not advised
22

3-
julia_version = "1.10.6"
3+
julia_version = "1.10.0"
44
manifest_format = "2.0"
5-
project_hash = "eed4f16fdd2e22799229e480394c255a569eb19c"
5+
project_hash = "d523b3401f72a1ed34b7b43749fd2655c6b78542"
66

77
[[deps.ExtDep]]
88
deps = ["SomePackage"]
@@ -15,22 +15,20 @@ path = "../ExtDep2"
1515
uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
1616
version = "0.1.0"
1717

18-
[[deps.ExtDep3]]
19-
path = "../ExtDep3.jl"
20-
uuid = "a5541f1e-a556-4fdc-af15-097880d743a1"
21-
version = "0.1.0"
22-
2318
[[deps.HasExtensions]]
24-
deps = ["ExtDep3"]
2519
path = "../HasExtensions.jl"
2620
uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
2721
version = "0.1.0"
28-
weakdeps = ["ExtDep", "ExtDep2"]
2922

3023
[deps.HasExtensions.extensions]
3124
Extension = "ExtDep"
32-
ExtensionDep = "ExtDep3"
3325
ExtensionFolder = ["ExtDep", "ExtDep2"]
26+
LinearAlgebraExt = "LinearAlgebra"
27+
28+
[deps.HasExtensions.weakdeps]
29+
ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c"
30+
ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
31+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
3432

3533
[[deps.SomePackage]]
3634
path = "../SomePackage"

test/project/Extensions/HasExtensions.jl/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ ExtDep3 = "a5541f1e-a556-4fdc-af15-097880d743a1"
88
[weakdeps]
99
ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c"
1010
ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
11+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1112

1213
[extensions]
1314
Extension = "ExtDep"
1415
ExtensionDep = "ExtDep3"
1516
ExtensionFolder = ["ExtDep", "ExtDep2"]
17+
LinearAlgebraExt = "LinearAlgebra"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module LinearAlgebraExt
2+
3+
end

0 commit comments

Comments
 (0)