Skip to content

Commit f851c82

Browse files
vtjnashKristofferC
authored andcommitted
better behaved loading errors (#57727)
3 interrelated fixes here: - Re-word the confusing error message for a circular dependency error. - Calling `using` inside a package was previously declared to be an error, but never really enforced, so allow it explicitly to do what it logically means (when appearing explicitly, such as from having `Requires.@require` in an `__init__` function). Remove the associated warning in the docs, since this gotcha now simply means what users think it would mean. - A missing lock acquire in create_expr_cache lead to a confusing discrepancy in errors related to the previous two fixes between incremental and serial modes. Fixes #56742 (cherry picked from commit 7f8ca29)
1 parent 8cb30fa commit f851c82

File tree

6 files changed

+84
-52
lines changed

6 files changed

+84
-52
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ release-candidate: release testall
177177
@echo 14. Push to Juliaup (https://github.com/JuliaLang/juliaup/wiki/Adding-a-Julia-version)
178178
@echo 15. Announce on mailing lists
179179
@echo 16. Change master to release-0.X in base/version.jl and base/version_git.sh as in 4cb1e20
180+
@echo 17. Move NEWS.md contents to HISTORY.md
180181
@echo
181182

182183
$(build_man1dir)/julia.1: $(JULIAHOME)/doc/man/julia.1 | $(build_man1dir)

NEWS.md

+4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ Language changes
5252
* Errors during `getfield` now raise a new `FieldError` exception type instead of the generic
5353
`ErrorException` ([#54504]).
5454
* Macros in function-signature-position no longer require parentheses. E.g. `function @main(args) ... end` is now permitted, whereas `function (@main)(args) ... end` was required in prior Julia versions.
55+
* Calling `using` on a package name inside of that package of that name (especially relevant
56+
for a submodule) now explicitly uses that package without examining the Manifest and
57+
environment, which is identical to the behavior of `..Name`. This appears to better match
58+
how users expect this to behave in the wild. ([#57727])
5559

5660

5761
Compiler/Runtime improvements

base/loading.jl

+33-22
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,6 @@ function run_module_init(mod::Module, i::Int=1)
14381438
end
14391439

14401440
function run_package_callbacks(modkey::PkgId)
1441-
@assert modkey != precompilation_target
14421441
run_extension_callbacks(modkey)
14431442
assert_havelock(require_lock)
14441443
unlock(require_lock)
@@ -1568,7 +1567,7 @@ function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}
15681567
uuid_trigger = UUID(totaldeps[trigger]::String)
15691568
trigger_id = PkgId(uuid_trigger, trigger)
15701569
push!(trigger_ids, trigger_id)
1571-
if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id) || (trigger_id == precompilation_target)
1570+
if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id)
15721571
trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
15731572
push!(trigger1, gid)
15741573
else
@@ -1581,7 +1580,6 @@ end
15811580
loading_extension::Bool = false
15821581
loadable_extensions::Union{Nothing,Vector{PkgId}} = nothing
15831582
precompiling_extension::Bool = false
1584-
precompilation_target::Union{Nothing,PkgId} = nothing
15851583
function run_extension_callbacks(extid::ExtensionId)
15861584
assert_havelock(require_lock)
15871585
succeeded = try
@@ -2178,7 +2176,6 @@ function canstart_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
21782176
# load already in progress for this module on the task
21792177
task, cond = loading
21802178
deps = String[modkey.name]
2181-
pkgid = modkey
21822179
assert_havelock(cond.lock)
21832180
if debug_loading_deadlocks && current_task() !== task
21842181
waiters = Dict{Task,Pair{Task,PkgId}}() # invert to track waiting tasks => loading tasks
@@ -2198,18 +2195,26 @@ function canstart_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
21982195
end
21992196
end
22002197
if current_task() === task
2201-
others = String[modkey.name] # repeat this to emphasize the cycle here
2198+
push!(deps, modkey.name) # repeat this to emphasize the cycle here
2199+
others = Set{String}()
22022200
for each in package_locks # list the rest of the packages being loaded too
22032201
if each[2][1] === task
22042202
other = each[1].name
2205-
other == modkey.name || other == pkgid.name || push!(others, other)
2203+
other == modkey.name || push!(others, other)
22062204
end
22072205
end
2206+
# remove duplicates from others already in deps
2207+
for dep in deps
2208+
delete!(others, dep)
2209+
end
22082210
msg = sprint(deps, others) do io, deps, others
22092211
print(io, "deadlock detected in loading ")
2210-
join(io, deps, " -> ")
2211-
print(io, " -> ")
2212-
join(io, others, " && ")
2212+
join(io, deps, " using ")
2213+
if !isempty(others)
2214+
print(io, " (while loading ")
2215+
join(io, others, " and ")
2216+
print(io, ")")
2217+
end
22132218
end
22142219
throw(ConcurrencyViolationError(msg))
22152220
end
@@ -2383,6 +2388,10 @@ function __require(into::Module, mod::Symbol)
23832388
error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \
23842389
is not allowed during package precompilation.")
23852390
end
2391+
topmod = moduleroot(into)
2392+
if nameof(topmod) === mod
2393+
return topmod
2394+
end
23862395
@lock require_lock begin
23872396
LOADING_CACHE[] = LoadingCache()
23882397
try
@@ -2491,10 +2500,7 @@ function _require_prelocked(uuidkey::PkgId, env=nothing)
24912500
try
24922501
toplevel_load[] = false
24932502
m = __require_prelocked(uuidkey, env)
2494-
if m === nothing
2495-
error("package `$(uuidkey.name)` did not define the expected \
2496-
module `$(uuidkey.name)`, check for typos in package module name")
2497-
end
2503+
m isa Module || check_package_module_loaded_error(uuidkey)
24982504
finally
24992505
toplevel_load[] = last
25002506
end_loading(uuidkey, m)
@@ -2984,6 +2990,9 @@ const newly_inferred = CodeInstance[]
29842990
function include_package_for_output(pkg::PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String},
29852991
concrete_deps::typeof(_concrete_dependencies), source::Union{Nothing,String})
29862992

2993+
@lock require_lock begin
2994+
m = start_loading(pkg, UInt128(0), false)
2995+
@assert m === nothing
29872996
append!(empty!(Base.DEPOT_PATH), depot_path)
29882997
append!(empty!(Base.DL_LOAD_PATH), dl_load_path)
29892998
append!(empty!(Base.LOAD_PATH), load_path)
@@ -2992,6 +3001,8 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto
29923001
Base._track_dependencies[] = true
29933002
get!(Base.PkgOrigin, Base.pkgorigins, pkg).path = input
29943003
append!(empty!(Base._concrete_dependencies), concrete_deps)
3004+
end
3005+
29953006
uuid_tuple = pkg.uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, pkg.uuid)
29963007

29973008
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), Base.__toplevel__, uuid_tuple)
@@ -3010,21 +3021,22 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto
30103021
ccall(:jl_set_newly_inferred, Cvoid, (Any,), nothing)
30113022
end
30123023
# check that the package defined the expected module so we can give a nice error message if not
3013-
Base.check_package_module_loaded(pkg)
3024+
m = maybe_root_module(pkg)
3025+
m isa Module || check_package_module_loaded_error(pkg)
30143026

30153027
# Re-populate the runtime's newly-inferred array, which will be included
30163028
# in the output. We removed it above to avoid including any code we may
30173029
# have compiled for error handling and validation.
30183030
ccall(:jl_set_newly_inferred, Cvoid, (Any,), newly_inferred)
3031+
@lock require_lock end_loading(pkg, m)
3032+
# insert_extension_triggers(pkg)
3033+
# run_package_callbacks(pkg)
30193034
end
30203035

3021-
function check_package_module_loaded(pkg::PkgId)
3022-
if !haskey(Base.loaded_modules, pkg)
3023-
# match compilecache error type for non-125 errors
3024-
error("$(repr("text/plain", pkg)) did not define the expected module `$(pkg.name)`, \
3025-
check for typos in package module name")
3026-
end
3027-
return nothing
3036+
function check_package_module_loaded_error(pkg)
3037+
# match compilecache error type for non-125 errors
3038+
error("package `$(pkg.name)` did not define the expected \
3039+
module `$(pkg.name)`, check for typos in package module name")
30283040
end
30293041

30303042
# protects against PkgId and UUID being imported and losing Base prefix
@@ -3100,7 +3112,6 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::
31003112
Base.track_nested_precomp($(_pkg_str(vcat(Base.precompilation_stack, pkg))))
31013113
Base.loadable_extensions = $(_pkg_str(loadable_exts))
31023114
Base.precompiling_extension = $(loading_extension)
3103-
Base.precompilation_target = $(_pkg_str(pkg))
31043115
Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)),
31053116
$(repr(load_path)), $(_pkg_str(concrete_deps)), $(repr(source_path(nothing))))
31063117
""")

doc/src/manual/modules.md

+14-16
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ There are three important standard modules:
371371

372372
Modules can contain *submodules*, nesting the same syntax `module ... end`. They can be used to introduce separate namespaces, which can be helpful for organizing complex codebases. Note that each `module` introduces its own [scope](@ref scope-of-variables), so submodules do not automatically “inherit” names from their parent.
373373

374-
It is recommended that submodules refer to other modules within the enclosing parent module (including the latter) using *relative module qualifiers* in `using` and `import` statements. A relative module qualifier starts with a period (`.`), which corresponds to the current module, and each successive `.` leads to the parent of the current module. This should be followed by modules if necessary, and eventually the actual name to access, all separated by `.`s.
374+
It is recommended that submodules refer to other modules within the enclosing parent module (including the latter) using *relative module qualifiers* in `using` and `import` statements. A relative module qualifier starts with a period (`.`), which corresponds to the current module, and each successive `.` leads to the parent of the current module. This should be followed by modules if necessary, and eventually the actual name to access, all separated by `.`s. As a special case, however, referring to the module root can be written without `.`, avoiding the need to count the depth to reach that module.
375375

376376
Consider the following example, where the submodule `SubA` defines a function, which is then extended in its “sibling” module:
377377

@@ -386,19 +386,24 @@ julia> module ParentModule
386386
export add_D # export it from ParentModule too
387387
module SubB
388388
import ..SubA: add_D # relative path for a “sibling” module
389+
# import ParentModule.SubA: add_D # when in a package, such as when this is loaded by using or import, this would be equivalent to the previous import, but not at the REPL
389390
struct Infinity end
390391
add_D(x::Infinity) = x
391392
end
392393
end;
393394
394395
```
395396

396-
You may see code in packages, which, in a similar situation, uses
397+
You may see code in packages, which, in a similar situation, uses import without the `.`:
398+
```jldoctest
399+
julia> import ParentModule.SubA: add_D
400+
ERROR: ArgumentError: Package ParentModule not found in current path.
401+
```
402+
However, since this operates through [code loading](@ref code-loading), it only works if `ParentModule` is in a package in a file. If `ParentModule` was defined at the REPL, it is necessary to use use relative paths:
397403
```jldoctest module_manual
398404
julia> import .ParentModule.SubA: add_D
399405
400406
```
401-
However, this operates through [code loading](@ref code-loading), and thus only works if `ParentModule` is in a package. It is better to use relative paths.
402407

403408
Note that the order of definitions also matters if you are evaluating values. Consider
404409

@@ -491,8 +496,12 @@ In particular, if you define a `function __init__()` in a module, then Julia wil
491496
immediately *after* the module is loaded (e.g., by `import`, `using`, or `require`) at runtime
492497
for the *first* time (i.e., `__init__` is only called once, and only after all statements in the
493498
module have been executed). Because it is called after the module is fully imported, any submodules
494-
or other imported modules have their `__init__` functions called *before* the `__init__` of the
495-
enclosing module.
499+
or other imported modules have their `__init__` functions called *before* the `__init__` of
500+
the enclosing module. This is also synchronized across threads, so that code can safely rely upon
501+
this ordering of effects, such that all `__init__` will have run, in dependency ordering,
502+
before the `using` result is completed. They may run concurrently with other `__init__`
503+
methods which are not dependencies however, so be careful when accessing any shared state
504+
outside the current module to use locks when needed.
496505

497506
Two typical uses of `__init__` are calling runtime initialization functions of external C libraries
498507
and initializing global constants that involve pointers returned by external libraries. For example,
@@ -524,17 +533,6 @@ pointer value must be called at runtime for precompilation to work ([`Ptr`](@ref
524533
null pointers unless they are hidden inside an [`isbits`](@ref) object). This includes the return values
525534
of the Julia functions [`@cfunction`](@ref) and [`pointer`](@ref).
526535

527-
Dictionary and set types, or in general anything that depends on the output of a `hash(key)` method,
528-
are a trickier case. In the common case where the keys are numbers, strings, symbols, ranges,
529-
`Expr`, or compositions of these types (via arrays, tuples, sets, pairs, etc.) they are safe to
530-
precompile. However, for a few other key types, such as `Function` or `DataType` and generic
531-
user-defined types where you haven't defined a `hash` method, the fallback `hash` method depends
532-
on the memory address of the object (via its `objectid`) and hence may change from run to run.
533-
If you have one of these key types, or if you aren't sure, to be safe you can initialize this
534-
dictionary from within your `__init__` function. Alternatively, you can use the [`IdDict`](@ref)
535-
dictionary type, which is specially handled by precompilation so that it is safe to initialize
536-
at compile-time.
537-
538536
When using precompilation, it is important to keep a clear sense of the distinction between the
539537
compilation phase and the execution phase. In this mode, it will often be much more clearly apparent
540538
that Julia is a compiler which allows execution of arbitrary Julia code, not a standalone interpreter

test/loading.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -1345,9 +1345,9 @@ module loaded_pkgid4 end
13451345
end
13461346
wait(e)
13471347
reset(e)
1348-
@test_throws(ConcurrencyViolationError("deadlock detected in loading pkgid3 -> pkgid2 -> pkgid1 -> pkgid3 && pkgid4"),
1348+
@test_throws(ConcurrencyViolationError("deadlock detected in loading pkgid3 using pkgid2 using pkgid1 using pkgid3 (while loading pkgid4)"),
13491349
@lock Base.require_lock Base.start_loading(pkid3, build_id, false)).value # try using pkgid3
1350-
@test_throws(ConcurrencyViolationError("deadlock detected in loading pkgid4 -> pkgid4 && pkgid1"),
1350+
@test_throws(ConcurrencyViolationError("deadlock detected in loading pkgid4 using pkgid4 (while loading pkgid1)"),
13511351
@lock Base.require_lock Base.start_loading(pkid4, build_id, false)).value # try using pkgid4
13521352
@lock Base.require_lock Base.end_loading(pkid1, loaded_pkgid1) # end
13531353
@lock Base.require_lock Base.end_loading(pkid4, loaded_pkgid4) # end

test/precompile.jl

+30-12
Original file line numberDiff line numberDiff line change
@@ -1633,25 +1633,28 @@ precompile_test_harness("Issue #26028") do load_path
16331633
"""
16341634
module Foo26028
16351635
module Bar26028
1636+
using Foo26028: Foo26028 as InnerFoo1
1637+
using ..Foo26028: Foo26028 as InnerFoo2
16361638
x = 0
16371639
y = 0
16381640
end
16391641
function __init__()
1640-
include(joinpath(@__DIR__, "Baz26028.jl"))
1641-
end
1642+
Baz = @eval module Baz26028
1643+
using Test
1644+
public @test_throws
1645+
import Foo26028.Bar26028.y as y1
1646+
import ..Foo26028.Bar26028.y as y2
1647+
end
1648+
@eval Base \$Baz.@test_throws(ConcurrencyViolationError("deadlock detected in loading Foo26028 using Foo26028"),
1649+
import Foo26028.Bar26028.x)
16421650
end
1643-
""")
1644-
write(joinpath(load_path, "Baz26028.jl"),
1645-
"""
1646-
module Baz26028
1647-
using Test
1648-
@test_throws(ConcurrencyViolationError("deadlock detected in loading Foo26028 -> Foo26028"),
1649-
@eval import Foo26028.Bar26028.x)
1650-
import ..Foo26028.Bar26028.y
16511651
end
16521652
""")
16531653
Base.compilecache(Base.PkgId("Foo26028"))
16541654
@test_nowarn @eval using Foo26028
1655+
invokelatest() do
1656+
@test Foo26028 === Foo26028.Bar26028.InnerFoo1 === Foo26028.Bar26028.InnerFoo2
1657+
end
16551658
end
16561659

16571660
precompile_test_harness("Issue #29936") do load_path
@@ -2265,7 +2268,7 @@ precompile_test_harness("No package module") do load_path
22652268
""")
22662269
@test_throws r"Failed to precompile NoModule" Base.compilecache(Base.identify_package("NoModule"), io, io)
22672270
@test occursin(
2268-
"NoModule [top-level] did not define the expected module `NoModule`, check for typos in package module name",
2271+
"package `NoModule` did not define the expected module `NoModule`, check for typos in package module name",
22692272
String(take!(io)))
22702273

22712274

@@ -2277,7 +2280,7 @@ precompile_test_harness("No package module") do load_path
22772280
""")
22782281
@test_throws r"Failed to precompile WrongModuleName" Base.compilecache(Base.identify_package("WrongModuleName"), io, io)
22792282
@test occursin(
2280-
"WrongModuleName [top-level] did not define the expected module `WrongModuleName`, check for typos in package module name",
2283+
"package `WrongModuleName` did not define the expected module `WrongModuleName`, check for typos in package module name",
22812284
String(take!(io)))
22822285

22832286

@@ -2398,4 +2401,19 @@ precompile_test_harness("MainImportDisallow") do load_path
23982401
end
23992402
end
24002403

2404+
precompile_test_harness("Package top-level load itself") do load_path
2405+
write(joinpath(load_path, "UsingSelf.jl"),
2406+
"""
2407+
__precompile__(false)
2408+
module UsingSelf
2409+
using UsingSelf
2410+
x = 3
2411+
end
2412+
""")
2413+
@eval using UsingSelf
2414+
invokelatest() do
2415+
@test UsingSelf.x == 3
2416+
end
2417+
end
2418+
24012419
finish_precompile_test!()

0 commit comments

Comments
 (0)