Skip to content

Commit fa880a7

Browse files
authored
threading deadlock: change jl_fptr_wait_for_compiled to actually compile code (JuliaLang#56571)
The jl_fptr_wait_for_compiled state merely means it could compile that code, but in many circumstances, it will not actually compile that code until a long delay: when either all edges are satisfied or it is demanded to run immediately. The previous logic did not handle that possibility leading to deadlocks (possible even on one thread). A high rate of failure was shown on running the following CI test: $ ./julia -t 20 -q <<EOF for i = 1:1000 i % 10 == 0 && @show i (@eval function _atthreads_greedy_dynamic_schedule() inc = Threads.Atomic{Int}(0) Threads.@threads :greedy for _ = 1:Threads.threadpoolsize() Threads.@threads :dynamic for _ = 1:Threads.threadpoolsize() Threads.atomic_add!(inc, 1) end end return inc[] end)() == Threads.threadpoolsize() * Threads.threadpoolsize() end EOF
1 parent 38908e9 commit fa880a7

File tree

3 files changed

+19
-25
lines changed

3 files changed

+19
-25
lines changed

src/gf.c

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2654,15 +2654,15 @@ void jl_read_codeinst_invoke(jl_code_instance_t *ci, uint8_t *specsigflags, jl_c
26542654
uint8_t flags = jl_atomic_load_acquire(&ci->specsigflags); // happens-before for subsequent read of fptr
26552655
while (1) {
26562656
jl_callptr_t initial_invoke = jl_atomic_load_acquire(&ci->invoke); // happens-before for subsequent read of fptr
2657-
while (initial_invoke == jl_fptr_wait_for_compiled_addr) {
2657+
if (initial_invoke == jl_fptr_wait_for_compiled_addr) {
26582658
if (!waitcompile) {
26592659
*invoke = NULL;
26602660
*specptr = NULL;
26612661
*specsigflags = 0b00;
26622662
return;
26632663
}
2664-
jl_cpu_pause();
2665-
initial_invoke = jl_atomic_load_acquire(&ci->invoke);
2664+
jl_compile_codeinst(ci);
2665+
initial_invoke = jl_atomic_load_acquire(&ci->invoke); // happens-before for subsequent read of fptr
26662666
}
26672667
void *fptr = jl_atomic_load_relaxed(&ci->specptr.fptr);
26682668
if (initial_invoke == NULL || fptr == NULL) {
@@ -2759,14 +2759,14 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t
27592759
jl_code_instance_t *unspec = jl_atomic_load_relaxed(&unspecmi->cache);
27602760
jl_callptr_t unspec_invoke = NULL;
27612761
if (unspec && (unspec_invoke = jl_atomic_load_acquire(&unspec->invoke))) {
2762-
jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing,
2763-
(jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL,
2764-
0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL);
2765-
codeinst->rettype_const = unspec->rettype_const;
27662762
uint8_t specsigflags;
27672763
jl_callptr_t invoke;
27682764
void *fptr;
27692765
jl_read_codeinst_invoke(unspec, &specsigflags, &invoke, &fptr, 1);
2766+
jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing,
2767+
(jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL,
2768+
0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL);
2769+
codeinst->rettype_const = unspec->rettype_const;
27702770
jl_atomic_store_relaxed(&codeinst->specptr.fptr, fptr);
27712771
jl_atomic_store_relaxed(&codeinst->invoke, invoke);
27722772
// unspec is probably not specsig, but might be using specptr
@@ -2864,14 +2864,14 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t
28642864
jl_typeinf_timing_end(start, is_recompile);
28652865
return ucache;
28662866
}
2867-
codeinst = jl_new_codeinst(mi, jl_nothing,
2868-
(jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL,
2869-
0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL);
2870-
codeinst->rettype_const = ucache->rettype_const;
28712867
uint8_t specsigflags;
28722868
jl_callptr_t invoke;
28732869
void *fptr;
28742870
jl_read_codeinst_invoke(ucache, &specsigflags, &invoke, &fptr, 1);
2871+
codeinst = jl_new_codeinst(mi, jl_nothing,
2872+
(jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL,
2873+
0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL);
2874+
codeinst->rettype_const = ucache->rettype_const;
28752875
// unspec is always not specsig, but might use specptr
28762876
jl_atomic_store_relaxed(&codeinst->specptr.fptr, fptr);
28772877
jl_atomic_store_relaxed(&codeinst->invoke, invoke);
@@ -2906,16 +2906,9 @@ jl_value_t *jl_fptr_sparam(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_
29062906

29072907
jl_value_t *jl_fptr_wait_for_compiled(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m)
29082908
{
2909-
// This relies on the invariant that the JIT will set the invoke ptr immediately upon adding `m` to itself.
2910-
size_t nthreads = jl_atomic_load_relaxed(&jl_n_threads);
2911-
// This should only be possible if there's more than one thread. If not, either there's a bug somewhere
2912-
// that resulted in this not getting cleared, or we're about to deadlock. Either way, that's bad.
2913-
if (nthreads == 1) {
2914-
jl_error("Internal error: Reached jl_fptr_wait_for_compiled in single-threaded execution.");
2915-
}
29162909
jl_callptr_t invoke = jl_atomic_load_acquire(&m->invoke);
2917-
while (invoke == &jl_fptr_wait_for_compiled) {
2918-
jl_cpu_pause();
2910+
if (invoke == &jl_fptr_wait_for_compiled) {
2911+
jl_compile_codeinst(m);
29192912
invoke = jl_atomic_load_acquire(&m->invoke);
29202913
}
29212914
return invoke(f, args, nargs, m);

src/jitlayers.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ static int jl_analyze_workqueue(jl_code_instance_t *callee, jl_codegen_params_t
278278
// But it must be consistent with the following invokenames lookup, which is protected by the engine_lock
279279
uint8_t specsigflags;
280280
void *fptr;
281+
void jl_read_codeinst_invoke(jl_code_instance_t *ci, uint8_t *specsigflags, jl_callptr_t *invoke, void **specptr, int waitcompile) JL_NOTSAFEPOINT; // not a safepoint (or deadlock) in this file due to 0 parameter
281282
jl_read_codeinst_invoke(codeinst, &specsigflags, &invoke, &fptr, 0);
282283
//if (specsig ? specsigflags & 0b1 : invoke == jl_fptr_args_addr)
283284
if (invoke == jl_fptr_args_addr) {
@@ -697,13 +698,13 @@ static void recursive_compile_graph(
697698
// and adds the result to the jitlayers
698699
// (and the shadow module),
699700
// and generates code for it
700-
static jl_callptr_t _jl_compile_codeinst(
701+
static void _jl_compile_codeinst(
701702
jl_code_instance_t *codeinst,
702703
jl_code_info_t *src)
703704
{
704705
recursive_compile_graph(codeinst, src);
705706
jl_compile_codeinst_now(codeinst);
706-
return jl_atomic_load_acquire(&codeinst->invoke);
707+
assert(jl_is_compiled_codeinst(codeinst));
707708
}
708709

709710

@@ -819,7 +820,7 @@ extern "C" JL_DLLEXPORT_CODEGEN
819820
int jl_compile_codeinst_impl(jl_code_instance_t *ci)
820821
{
821822
int newly_compiled = 0;
822-
if (jl_atomic_load_relaxed(&ci->invoke) == NULL) {
823+
if (!jl_is_compiled_codeinst(ci)) {
823824
++SpecFPtrCount;
824825
uint64_t start = jl_typeinf_timing_begin();
825826
_jl_compile_codeinst(ci, NULL);
@@ -862,7 +863,7 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec)
862863
if (src) {
863864
// TODO: first prepare recursive_compile_graph(unspec, src) before taking this lock to avoid recursion?
864865
JL_LOCK(&jitlock); // TODO: use a better lock
865-
if (jl_atomic_load_relaxed(&unspec->invoke) == NULL) {
866+
if (!jl_is_compiled_codeinst(unspec)) {
866867
assert(jl_is_code_info(src));
867868
++UnspecFPtrCount;
868869
jl_debuginfo_t *debuginfo = src->debuginfo;

src/julia_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred(
677677
jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype,
678678
size_t min_world, size_t max_world, jl_debuginfo_t *di, jl_svec_t *edges);
679679
JL_DLLEXPORT jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT);
680-
JL_DLLEXPORT void jl_read_codeinst_invoke(jl_code_instance_t *ci, uint8_t *specsigflags, jl_callptr_t *invoke, void **specptr, int waitcompile) JL_NOTSAFEPOINT;
680+
JL_DLLEXPORT void jl_read_codeinst_invoke(jl_code_instance_t *ci, uint8_t *specsigflags, jl_callptr_t *invoke, void **specptr, int waitcompile);
681681
JL_DLLEXPORT jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t world, size_t min_valid, size_t max_valid, int mt_cache);
682682

683683
JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst_uninit(jl_method_instance_t *mi, jl_value_t *owner);

0 commit comments

Comments
 (0)