Skip to content

Commit

Permalink
fixup! threads: avoid deadlock from recursive lock acquire
Browse files Browse the repository at this point in the history
fix finalizers reset in jl_eh_restore_state
  • Loading branch information
vtjnash committed Nov 30, 2020
1 parent b048f7b commit 85ca118
Show file tree
Hide file tree
Showing 7 changed files with 34 additions and 28 deletions.
3 changes: 0 additions & 3 deletions base/gcutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,6 @@ Increment or decrement the counter that controls the running of finalizers on
the current thread. Finalizers will only run when the counter is at zero. (Set
`true` for enabling, `false` for disabling). They may still run concurrently on
another thread.
When exiting any try block, either normally or due to an exception, this may be
reset to the previous value from entering the try block.
"""
enable_finalizers(on::Bool) = ccall(:jl_gc_enable_finalizers, Cvoid, (Ptr{Cvoid}, Int32,), C_NULL, on)

Expand Down
6 changes: 3 additions & 3 deletions src/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ JL_DLLEXPORT void jl_gc_enable_finalizers(jl_ptls_t ptls, int on)
jl_error(""); // get a backtrace
}
JL_CATCH {
jl_printf((JL_STREAM*)STDERR_FILENO, "WARNING: GC finalizers already enabled on this thread. Possibly unpaired within a try block?\n");
jl_printf((JL_STREAM*)STDERR_FILENO, "WARNING: GC finalizers already enabled on this thread.\n");
// Only print the backtrace once, to avoid spamming the logs
static int backtrace_printed = 0;
if (backtrace_printed == 0) {
Expand All @@ -421,7 +421,7 @@ JL_DLLEXPORT void jl_gc_enable_finalizers(jl_ptls_t ptls, int on)
return;
}
ptls->finalizers_inhibited = new_val;
if (!new_val && old_val && !ptls->in_finalizer) {
if (!new_val && old_val && !ptls->in_finalizer && ptls->locks.len == 0) {
ptls->in_finalizer = 1;
run_finalizers(ptls);
ptls->in_finalizer = 0;
Expand Down Expand Up @@ -3216,7 +3216,7 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection)
// Only disable finalizers on current thread
// Doing this on all threads is racy (it's impossible to check
// or wait for finalizers on other threads without dead lock).
if (!ptls->finalizers_inhibited) {
if (!ptls->finalizers_inhibited && ptls->locks.len == 0) {
int8_t was_in_finalizer = ptls->in_finalizer;
ptls->in_finalizer = 1;
run_finalizers(ptls);
Expand Down
1 change: 0 additions & 1 deletion src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1733,7 +1733,6 @@ typedef struct _jl_handler_t {
int8_t gc_state;
size_t locks_len;
sig_atomic_t defer_signal;
int finalizers_inhibited;
jl_timing_block_t *timing_stack;
size_t world_age;
} jl_handler_t;
Expand Down
12 changes: 8 additions & 4 deletions src/rtutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh)
eh->gc_state = ptls->gc_state;
eh->locks_len = ptls->locks.len;
eh->defer_signal = ptls->defer_signal;
eh->finalizers_inhibited = ptls->finalizers_inhibited;
eh->world_age = ptls->world_age;
current_task->eh = eh;
#ifdef ENABLE_TIMINGS
Expand Down Expand Up @@ -249,14 +248,14 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh)
current_task->eh = eh->prev;
ptls->pgcstack = eh->gcstack;
small_arraylist_t *locks = &ptls->locks;
if (locks->len > eh->locks_len) {
for (size_t i = locks->len;i > eh->locks_len;i--)
int unlocks = locks->len > eh->locks_len;
if (unlocks) {
for (size_t i = locks->len; i > eh->locks_len; i--)
jl_mutex_unlock_nogc((jl_mutex_t*)locks->items[i - 1]);
locks->len = eh->locks_len;
}
ptls->world_age = eh->world_age;
ptls->defer_signal = eh->defer_signal;
ptls->finalizers_inhibited = eh->finalizers_inhibited;
if (old_gc_state != eh->gc_state) {
jl_atomic_store_release(&ptls->gc_state, eh->gc_state);
if (old_gc_state) {
Expand All @@ -266,6 +265,11 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh)
if (old_defer_signal && !eh->defer_signal) {
jl_sigint_safepoint(ptls);
}
if (unlocks && eh->locks_len == 0 && ptls->finalizers_inhibited == 0) {
// call run_finalizers
ptls->finalizers_inhibited = 1;
jl_gc_enable_finalizers(ptls, 1);
}
}

JL_DLLEXPORT void jl_pop_handler(int n)
Expand Down
1 change: 1 addition & 0 deletions src/task.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ void JL_NORETURN jl_finish_task(jl_task_t *t)
t->stkbuf = NULL;
// ensure that state is cleared
ptls->in_finalizer = 0;
ptls->finalizers_inhibited = 0;
ptls->in_pure_callback = 0;
jl_get_ptls_states()->world_age = jl_world_counter;
// let the runtime know this task is dead and find a new task to run
Expand Down
13 changes: 9 additions & 4 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ end

# lock / unlock
let l = ReentrantLock()
lock(l)
@test lock(l) === nothing
@test islocked(l)
success = Ref(false)
@test trylock(l) do
Expand All @@ -119,7 +119,7 @@ let l = ReentrantLock()
end === false
end
Base.wait(t)
unlock(l)
@test unlock(l) === nothing
@test_throws ErrorException unlock(l)
end

Expand All @@ -137,14 +137,19 @@ for l in (Threads.SpinLock(), ReentrantLock())
GC.enable_finalizers(true)
@test enable_finalizers_count() == 1
finally
@test enable_finalizers_count() == 0
GC.enable_finalizers(false)
@test enable_finalizers_count() == 1
GC.enable_finalizers(false)
@test enable_finalizers_count() == 2
end
@test enable_finalizers_count() == 2
GC.enable_finalizers(true)
@test enable_finalizers_count() == 1
GC.enable_finalizers(true)
@test enable_finalizers_count() == 0
@test_warn "WARNING: GC finalizers already enabled on this thread." GC.enable_finalizers(true)

@test lock(l) === nothing
@test try unlock(l) finally end === nothing
end

# task switching
Expand Down
26 changes: 13 additions & 13 deletions test/threads_exec.jl
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ test_threaded_atomic_minmax(UInt16(27000),UInt16(37000))
function threaded_add_locked(::Type{LockT}, x, n) where LockT
critical = LockT()
@threads for i = 1:n
lock(critical)
@test lock(critical) === nothing
@test islocked(critical)
x = x + 1
unlock(critical)
@test unlock(critical) === nothing
end
@test !islocked(critical)
nentered = 0
Expand All @@ -124,7 +124,7 @@ function threaded_add_locked(::Type{LockT}, x, n) where LockT
if trylock(critical)
@test islocked(critical)
nentered += 1
unlock(critical)
@test unlock(critical) === nothing
else
atomic_add!(nfailed, 1)
end
Expand All @@ -142,21 +142,21 @@ end
let critical = ReentrantLock()
@test !islocked(critical)
@test_throws ErrorException("unlock count must match lock count") unlock(critical)
lock(critical)
@test lock(critical) === nothing
@test islocked(critical)
lock(critical)
t = trylock(critical); @test t
@test lock(critical) === nothing
@test trylock(critical) == true
@test islocked(critical)
unlock(critical)
@test unlock(critical) === nothing
@test islocked(critical)
unlock(critical)
@test unlock(critical) === nothing
@test islocked(critical)
unlock(critical)
@test unlock(critical) === nothing
@test !islocked(critical)
@test_throws ErrorException("unlock count must match lock count") unlock(critical)
t = trylock(critical); @test t
@test trylock(critical) == true
@test islocked(critical)
unlock(critical)
@test unlock(critical) === nothing
@test !islocked(critical)
@test_throws ErrorException("unlock count must match lock count") unlock(critical)
@test !islocked(critical)
Expand All @@ -167,10 +167,10 @@ end
function threaded_gc_locked(::Type{LockT}) where LockT
critical = LockT()
@threads for i = 1:20
lock(critical)
@test lock(critical) === nothing
@test islocked(critical)
GC.gc(false)
unlock(critical)
@test unlock(critical) === nothing
end
@test !islocked(critical)
end
Expand Down

0 comments on commit 85ca118

Please sign in to comment.