-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathpersistent_tasks.jl
128 lines (116 loc) · 4.68 KB
/
persistent_tasks.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""
Aqua.test_persistent_tasks(package)
Test whether loading `package` creates persistent `Task`s
which may block precompilation of dependent packages.
See also [`Aqua.find_persistent_tasks_deps`](@ref).
If you provide an optional `expr`, this tests whether loading `package` and running `expr`
creates persistent `Task`s. For example, you might start and shutdown a web server, and
this will test that there aren't any persistent `Task`s.
On Julia version 1.9 and before, this test always succeeds.
# 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).
- `expr::Expr = quote end`: An expression to run in the precompile package.
"""
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; expr::Expr = quote end, tmax = 10)
root_project_path, found = root_project_toml(package)
found || error("Unable to locate Project.toml")
return !precompile_wrapper(root_project_path, tmax, expr)
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...)
root_project_path, found = root_project_toml(package)
found || error("Unable to locate Project.toml")
prj = TOML.parsefile(root_project_path)
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, expr)
@static if VERSION < v"1.10.0-"
return true
end
prev_project = Base.active_project()::String
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; io = devnull))
Pkg.activate(wrapperdir; io = devnull)
Pkg.develop(PackageSpec(path = pkgdir); io = devnull)
statusfile = joinpath(wrapperdir, "done.log")
open(joinpath(wrapperdir, "src", wrappername * ".jl"), "w") do io
println(
io,
"""
module $wrappername
using $pkgname
$expr
# 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 'push!(LOAD_PATH, "@stdlib"); using Pkg; Pkg.precompile(; io = devnull)'`
proc = run(cmd, stdin, stdout, stderr; 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
# SIGKILL to prevent julia from printing the SIG 15 handler, which can
# misleadingly look like it's caused by an issue in the user's program.
kill(proc, Base.SIGKILL)
end
return success
finally
isdefined(Pkg, :respect_sysimage_versions) && Pkg.respect_sysimage_versions(true)
Pkg.activate(prev_project; io = devnull)
end
end