-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add persistent_tasks test * Run JuliaFormatter, response to review comments Co-authored-by: "Lars Göttgens <lars.goettgens@rwth-aachen.de>" * Improve robustness Flushing the io may prevent some of the CI hangs; also print diagnostics in case of unexpected outcomes. * More JuliaFormatter * Escape paths (windows) * Update src/persistent_tasks.jl Co-authored-by: Max Horn <max@quendi.de> * fails -> broken * Add to CHANGELOG * Adapt interface * Update changelog * Activate new test in `test_smoke` * Update src/persistent_tasks.jl Co-authored-by: Max Horn <max@quendi.de> * Restore the previous environment * Apply suggestions from code review Co-authored-by: Lars Göttgens <lars.goettgens@gmail.com> * Turn `test_persistent_tasks` on by default --------- Co-authored-by: Max Horn <max@quendi.de> Co-authored-by: Lars Göttgens <lars.goettgens@rwth-aachen.de> Co-authored-by: Lars Göttgens <lars.goettgens@gmail.com>
- Loading branch information
1 parent
44c89fd
commit b2d612c
Showing
12 changed files
with
251 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
""" | ||
Aqua.test_persistent_tasks(package) | ||
Test whether loading `package` creates persistent `Task`s | ||
which may block precompilation of dependent packages. | ||
# Motivation | ||
Julia 1.10 and higher wait for all running `Task`s to finish | ||
before writing out the precompiled (cached) version of the package. | ||
One consequence is that a package that launches | ||
`Task`s in its `__init__` function may precompile successfully, | ||
but block precompilation of any packages that depend on it. | ||
# Example | ||
Let's create a dummy package, `PkgA`, that launches a persistent `Task`: | ||
```julia | ||
module PkgA | ||
const t = Ref{Any}() # to prevent the Timer from being garbage-collected | ||
__init__() = t[] = Timer(0.1; interval=1) # create a persistent `Timer` `Task` | ||
end | ||
``` | ||
`PkgA` will precompile successfully, because `PkgA.__init__()` does not | ||
run when `PkgA` is precompiled. However, | ||
```julia | ||
module PkgB | ||
using PkgA | ||
end | ||
``` | ||
fails to precompile: `using PkgA` runs `PkgA.__init__()`, which | ||
leaves the `Timer` `Task` running, and that causes precompilation | ||
of `PkgB` to hang. | ||
# How the test works | ||
This test works by launching a Julia process that tries to precompile a | ||
dummy package similar to `PkgB` above, modified to signal back to Aqua when | ||
`PkgA` has finished loading. The test fails if the gap between loading `PkgA` | ||
and finishing precompilation exceeds time `tmax`. | ||
# How to fix failing packages | ||
Often, the easiest fix is to modify the `__init__` function to check whether the | ||
Julia process is precompiling some other package; if so, don't launch the | ||
persistent `Task`s. | ||
``` | ||
function __init__() | ||
# Other setup code here | ||
if ccall(:jl_generating_output, Cint, ()) == 0 # if we're not precompiling... | ||
# launch persistent tasks here | ||
end | ||
end | ||
``` | ||
In more complex cases, you may need to set up independently-callable functions | ||
to launch the tasks and set conditions that allow them to cleanly exit. | ||
# Arguments | ||
- `package`: a top-level `Module` or `Base.PkgId`. | ||
# Keyword Arguments | ||
- `broken::Bool = false`: If true, it uses `@test_broken` instead of | ||
`@test`. | ||
- `tmax::Real = 5`: the maximum time (in seconds) to wait after loading the | ||
package before forcibly shutting down the precompilation process (triggering | ||
a test failure). | ||
""" | ||
function test_persistent_tasks(package::PkgId; broken::Bool = false, kwargs...) | ||
if broken | ||
@test_broken !has_persistent_tasks(package; kwargs...) | ||
else | ||
@test !has_persistent_tasks(package; kwargs...) | ||
end | ||
end | ||
|
||
function test_persistent_tasks(package::Module; kwargs...) | ||
test_persistent_tasks(PkgId(package); kwargs...) | ||
end | ||
|
||
function has_persistent_tasks(package::PkgId; tmax = 10) | ||
result = root_project_or_failed_lazytest(package) | ||
result isa LazyTestResult && error("Unable to locate Project.toml") | ||
return !precompile_wrapper(result, tmax) | ||
end | ||
|
||
""" | ||
Aqua.find_persistent_tasks_deps(package; broken = Dict{String,Bool}(), kwargs...) | ||
Test all the dependencies of `package` with [`Aqua.test_persistent_tasks`](@ref). | ||
On Julia 1.10 and higher, it returns a list of all dependencies failing the test. | ||
These are likely the ones blocking precompilation of your package. | ||
Any additional kwargs (e.g., `tmax`) are passed to [`Aqua.test_persistent_tasks`](@ref). | ||
""" | ||
function find_persistent_tasks_deps(package::PkgId; kwargs...) | ||
result = root_project_or_failed_lazytest(package) | ||
result isa LazyTestResult && error("Unable to locate Project.toml") | ||
prj = TOML.parsefile(result) | ||
deps = get(prj, "deps", Dict{String,Any}()) | ||
filter!(deps) do (name, uuid) | ||
id = PkgId(UUID(uuid), name) | ||
return has_persistent_tasks(id; kwargs...) | ||
end | ||
return [name for (name, _) in deps] | ||
end | ||
|
||
function find_persistent_tasks_deps(package::Module; kwargs...) | ||
find_persistent_tasks_deps(PkgId(package); kwargs...) | ||
end | ||
|
||
function precompile_wrapper(project, tmax) | ||
prev_project = Base.active_project() | ||
isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(false) | ||
try | ||
pkgdir = dirname(project) | ||
pkgname = get(TOML.parsefile(project), "name", nothing) | ||
if isnothing(pkgname) | ||
@error "Unable to locate package name in $project" | ||
return false | ||
end | ||
wrapperdir = tempname() | ||
wrappername, _ = only(Pkg.generate(wrapperdir)) | ||
Pkg.activate(wrapperdir) | ||
Pkg.develop(PackageSpec(path = pkgdir)) | ||
statusfile = joinpath(wrapperdir, "done.log") | ||
open(joinpath(wrapperdir, "src", wrappername * ".jl"), "w") do io | ||
println( | ||
io, | ||
""" | ||
module $wrappername | ||
using $pkgname | ||
# Signal Aqua from the precompilation process that we've finished loading the package | ||
open("$(escape_string(statusfile))", "w") do io | ||
println(io, "done") | ||
flush(io) | ||
end | ||
end | ||
""", | ||
) | ||
end | ||
# Precompile the wrapper package | ||
cmd = `$(Base.julia_cmd()) --project=$wrapperdir -e 'using Pkg; Pkg.precompile()'` | ||
proc = run(cmd; wait = false) | ||
while !isfile(statusfile) && process_running(proc) | ||
sleep(0.5) | ||
end | ||
if !isfile(statusfile) | ||
@error "Unexpected error: $statusfile was not created, but precompilation exited" | ||
return false | ||
end | ||
# Check whether precompilation finishes in the required time | ||
t = time() | ||
while process_running(proc) && time() - t < tmax | ||
sleep(0.1) | ||
end | ||
success = !process_running(proc) | ||
if !success | ||
kill(proc) | ||
end | ||
return success | ||
finally | ||
isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(true) | ||
Pkg.activate(prev_project) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
name = "PersistentTask" | ||
uuid = "e5c298b6-d81d-47aa-a9ed-5ea983e22986" |
6 changes: 6 additions & 0 deletions
6
test/pkgs/PersistentTasks/PersistentTask/src/PersistentTask.jl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module PersistentTask | ||
|
||
const t = Ref{Any}() | ||
__init__() = t[] = Timer(0.1; interval = 1) # create a persistent `Timer` `Task` | ||
|
||
end # module PersistentTask |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
name = "TransientTask" | ||
uuid = "94ae9332-58b0-4342-989c-0a7e44abcca9" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module TransientTask | ||
|
||
__init__() = Timer(0.1) # create a transient `Timer` `Task` | ||
|
||
end # module TransientTask |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
name = "UsesBoth" | ||
uuid = "96f12b6e-60f8-43dc-b131-049a88a2f499" | ||
|
||
[deps] | ||
PersistentTask = "e5c298b6-d81d-47aa-a9ed-5ea983e22986" | ||
TransientTask = "94ae9332-58b0-4342-989c-0a7e44abcca9" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module UsesBoth | ||
|
||
using TransientTask | ||
using PersistentTask | ||
|
||
end # module UsesBoth |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
module TestPersistentTasks | ||
|
||
include("preamble.jl") | ||
using Base: PkgId, UUID | ||
using Pkg: TOML | ||
|
||
function getid(name) | ||
path = joinpath(@__DIR__, "pkgs", "PersistentTasks", name) | ||
if path ∉ LOAD_PATH | ||
pushfirst!(LOAD_PATH, path) | ||
end | ||
prj = TOML.parsefile(joinpath(path, "Project.toml")) | ||
return PkgId(UUID(prj["uuid"]), prj["name"]) | ||
end | ||
|
||
|
||
@testset "PersistentTasks" begin | ||
@test !Aqua.has_persistent_tasks(getid("TransientTask")) | ||
|
||
result = Aqua.find_persistent_tasks_deps(getid("TransientTask")) | ||
@test result == [] | ||
|
||
if Base.VERSION >= v"1.10-" | ||
@test Aqua.has_persistent_tasks(getid("PersistentTask")) | ||
|
||
result = Aqua.find_persistent_tasks_deps(getid("UsesBoth")) | ||
@test result == ["PersistentTask"] | ||
end | ||
filter!(str -> !occursin("PersistentTasks", str), LOAD_PATH) | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters