Skip to content

Commit 9f55128

Browse files
JeffBezansontimholyIanButterworth
authored andcommitted
Devdocs on fixing precompile hangs, take 2 (#51895)
This is #50914, with only the documentation changes, plus an improvement to the warning message. --------- Co-authored-by: Tim Holy <[email protected]> Co-authored-by: Ian Butterworth <[email protected]> (cherry picked from commit 6d5787a)
1 parent 2030e7d commit 9f55128

File tree

4 files changed

+105
-3
lines changed

4 files changed

+105
-3
lines changed

doc/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ DevDocs = [
154154
"devdocs/EscapeAnalysis.md",
155155
"devdocs/gc-sa.md",
156156
"devdocs/gc.md",
157+
"devdocs/precompile_hang.md",
157158
],
158159
"Developing/debugging Julia's C code" => [
159160
"devdocs/backtraces.md",
8.96 KB
Loading

doc/src/devdocs/precompile_hang.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Fixing precompilation hangs due to open tasks or IO
2+
3+
On Julia 1.10 or higher, you might see the following message:
4+
5+
![Screenshot of precompilation hang](./img/precompilation_hang.png)
6+
7+
This may repeat. If it continues to repeat with no hints that it will
8+
resolve itself, you may have a "precompilation hang" that requires
9+
fixing. Even if it's transient, you might prefer to resolve it so that
10+
users will not be bothered by this warning. This page walks you
11+
through how to analyze and fix such issues.
12+
13+
If you follow the advice and hit `Ctrl-C`, you might see
14+
15+
```
16+
^C Interrupted: Exiting precompilation...
17+
18+
1 dependency had warnings during precompilation:
19+
┌ Test1 [ac89d554-e2ba-40bc-bc5c-de68b658c982]
20+
│ [pid 2745] waiting for IO to finish:
21+
│ Handle type uv_handle_t->data
22+
│ timer 0x55580decd1e0->0x7f94c3a4c340
23+
```
24+
25+
This message conveys two key pieces of information:
26+
27+
- the hang is occurring during precompilation of `Test1`, a dependency of `Test2` (the package we were trying to load with `using Test2`)
28+
- during precompilation of `Test1`, Julia created a `Timer` object (use `?Timer` if you're unfamiliar with Timers) which is still open; until that closes, the process is hung
29+
30+
If this is enough of a hint for you to figure out how `timer = Timer(args...)` is being created, one good solution is to add `wait(timer)` if `timer` eventually finishes on its own, or `close(timer)` if you need to force-close it, before the final `end` of the module.
31+
32+
However, there are cases that may not be that straightforward. Usually the best option is to start by determining whether the hang is due to code in Test1 or whether it is due to one of Test1's dependencies:
33+
34+
- Option 1: `Pkg.add("Aqua")` and use [`Aqua.test_persistent_tasks`](https://juliatesting.github.io/Aqua.jl/dev/#Aqua.test_persistent_tasks-Tuple{Base.PkgId}). This should help you identify which package is causing the problem, after which the instructions [below](@ref pchang_fix) should be followed. If needed, you can create a `PkgId` as `Base.PkgId(UUID("..."), "Test1")`, where `...` comes from the `uuid` entry in `Test1/Project.toml`.
35+
- Option 2: manually diagnose the source of the hang.
36+
37+
To manually diagnose:
38+
39+
1. `Pkg.develop("Test1")`
40+
2. Comment out all the code `include`d or defined in `Test1`, *except* the `using/import` statements.
41+
3. Try `using Test2` (or even `using Test1` assuming that hangs too) again
42+
43+
Now we arrive at a fork in the road: either
44+
45+
- the hang persists, indicating it is [due to one of your dependencies](@ref pchang_deps)
46+
- the hang disappears, indicating that it is [due to something in your code](@ref pchang_fix).
47+
48+
## [Diagnosing and fixing hangs due to a package dependency](@id pchang_deps)
49+
50+
Use a binary search to identify the problematic dependency: start by commenting out half your dependencies, then when you isolate which half is responsible comment out half of that half, etc. (You don't have to remove them from the project, just comment out the `using`/`import` statements.)
51+
52+
Once you've identified a suspect (here we'll call it `ThePackageYouThinkIsCausingTheProblem`), first try precompiling that package. If it also hangs during precompilation, continue chasing the problem backwards.
53+
54+
However, most likely `ThePackageYouThinkIsCausingTheProblem` will precompile fine. This suggests it's in the function `ThePackageYouThinkIsCausingTheProblem.__init__`, which does not run during precompilation of `ThePackageYouThinkIsCausingTheProblem` but *does* in any package that loads `ThePackageYouThinkIsCausingTheProblem`. To test this theory, set up a minimal working example (MWE), something like
55+
56+
```julia
57+
(@v1.10) pkg> generate MWE
58+
Generating project MWE:
59+
MWE\Project.toml
60+
MWE\src\MWE.jl
61+
```
62+
63+
where the source code of `MWE.jl` is
64+
65+
```julia
66+
module MWE
67+
using ThePackageYouThinkIsCausingTheProblem
68+
end
69+
```
70+
71+
and you've added `ThePackageYouThinkIsCausingTheProblem` to MWE's dependencies.
72+
73+
If that MWE reproduces the hang, you've found your culprit:
74+
`ThePackageYouThinkIsCausingTheProblem.__init__` must be creating the `Timer` object. If the timer object can be safely `close`d, that's a good option. Otherwise, the most common solution is to avoid creating the timer while *any* package is being precompiled: add
75+
76+
```julia
77+
ccall(:jl_generating_output, Cint, ()) == 1 && return nothing
78+
```
79+
80+
as the first line of `ThePackageYouThinkIsCausingTheProblem.__init__`, and it will avoid doing any initialization in any Julia process whose purpose is to precompile packages.
81+
82+
## [Fixing package code to avoid hangs](@id pchang_fix)
83+
84+
Search your package for suggestive words (here like "Timer") and see if you can identify where the problem is being created. Note that a method *definition* like
85+
86+
```julia
87+
maketimer() = Timer(timer -> println("hi"), 0; interval=1)
88+
```
89+
90+
is not problematic in and of itself: it can cause this problem only if `maketimer` gets called while the module is being defined. This might be happening from a top-level statement such as
91+
92+
```julia
93+
const GLOBAL_TIMER = maketimer()
94+
```
95+
96+
or it might conceivably occur in a [precompile workload](https://github.com/JuliaLang/PrecompileTools.jl).
97+
98+
If you struggle to identify the causative lines, then consider doing a binary search: comment out sections of your package (or `include` lines to omit entire files) until you've reduced the problem in scope.

src/jl_uv.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ static void walk_print_cb(uv_handle_t *h, void *arg)
5151
npad += strlen(type);
5252
pad += npad < strlen(pad) ? npad : strlen(pad);
5353
if (fd == -1)
54-
jl_safe_printf(" %s %s@%p->%p\n", type, pad, (void*)h, (void*)h->data);
54+
jl_safe_printf(" %s %s%p->%p\n", type, pad, (void*)h, (void*)h->data);
5555
else
56-
jl_safe_printf(" %s[%zd] %s@%p->%p\n", type, (size_t)fd, pad, (void*)h, (void*)h->data);
56+
jl_safe_printf(" %s[%zd] %s%p->%p\n", type, (size_t)fd, pad, (void*)h, (void*)h->data);
5757
}
5858

5959
static void wait_empty_func(uv_timer_t *t)
@@ -63,9 +63,12 @@ static void wait_empty_func(uv_timer_t *t)
6363
if (!uv_loop_alive(t->loop))
6464
return;
6565
jl_safe_printf("\n[pid %zd] waiting for IO to finish:\n"
66-
" TYPE[FD/PID] @UV_HANDLE_T->DATA\n",
66+
" Handle type uv_handle_t->data\n",
6767
(size_t)uv_os_getpid());
6868
uv_walk(jl_io_loop, walk_print_cb, NULL);
69+
if (jl_generating_output() && jl_options.incremental) {
70+
jl_safe_printf("This means that a package has started a background task or event source that has not finished running. For precompilation to complete successfully, the event source needs to be closed explicitly. See the developer documentation on fixing precompilation hangs for more help.\n");
71+
}
6972
jl_gc_collect(JL_GC_FULL);
7073
}
7174

0 commit comments

Comments
 (0)