summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Gruber <luke.gruber@shopify.com>2026-03-10 16:13:38 -0400
committerLuke Gruber <luke.gru@gmail.com>2026-03-11 09:24:18 -0400
commitd72a0fed6019e7e6b2453594babb89f406aed1d7 (patch)
treeed3f50b18ec6f31d6026ed1d94cfd5a9ba4ea742
parent44b99d688be701088e613c5495ea871d14738e29 (diff)
Always take th->interrupt_lock in ubf_clear
Patch 08372635f7 fixed a race condition on ubfs, but it's only valid if right after a call to `ubf_clear`, we assume the ubf function cannot be in the middle of running. This patch removes an optimization in `ubf_clear` that violates that assumption. In short, `ubf_clear` needs to take `th->interrupt_lock` unconditionally both to avoid deadlocks and to be able to reason about when ubfs can be run. This should fix CI errors like https://ci.rvm.jp/results/trunk-jemalloc@ruby-sp2-noble-docker/6242153. The error was in test_timeout.rb, which had a deadlock during VM shutdown. ```ruby r = Ractor.new do begin Timeout.timeout(0.1) { sleep } rescue Timeout::Error :ok end end.value assert_equal :ok, r ``` The deadlock happened during `rb_ractor_terminate_interrupt_main_thread` with 2 ractors: 1) r1 t1: UBF called with t2->interrupt_lock (ubf = ubf_waiting) 2) r2 t2: ubf cleared from previous thread_sched_wait_events_call (but no lock taken, because of optimization) 3) r2 t2: thread_sched_wait_events: acquire thread_sched_lock(t2) (caller calling native_sleep() in loop) 4) r2 t2: ubf_set: try to acquire t2->interrupt_lock [block] 5) r1 t1: try to acquire thread_sched_lock(t2) [block, deadlock] t2 needs to block on t2->interrupt_lock in step 2 until the ubf has completed. Only then can it register a new ubf in the next `native_sleep` iteration.
-rw-r--r--thread_pthread.c12
1 files changed, 5 insertions, 7 deletions
diff --git a/thread_pthread.c b/thread_pthread.c
index 38bc134fa8..9f6707ae5d 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -1055,14 +1055,12 @@ ubf_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg)
static void
ubf_clear(rb_thread_t *th)
{
- if (th->unblock.func) {
- rb_native_mutex_lock(&th->interrupt_lock);
- {
- th->unblock.func = NULL;
- th->unblock.arg = NULL;
- }
- rb_native_mutex_unlock(&th->interrupt_lock);
+ rb_native_mutex_lock(&th->interrupt_lock);
+ {
+ th->unblock.func = NULL;
+ th->unblock.arg = NULL;
}
+ rb_native_mutex_unlock(&th->interrupt_lock);
}
static void