summaryrefslogtreecommitdiff
path: root/thread.c
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2021-07-28 19:55:55 +1200
committernagachika <nagachika@ruby-lang.org>2021-09-26 20:49:54 +0900
commit8ac058b64577d9b6fb1ee998d0fa7800529d7d34 (patch)
tree840566542980745ab96d859b503671ea368062e1 /thread.c
parent7d4a0c4b93cbdf40a9a43c4974199405099a8545 (diff)
Fix potential hang when joining threads.
If the thread termination invokes user code after `th->status` becomes `THREAD_KILLED`, and the user unblock function causes that `th->status` to become something else (e.g. `THREAD_RUNNING`), threads waiting in `thread_join_sleep` will hang forever. We move the unblock function call to before the thread status is updated, and allow threads to join as soon as `th->value` becomes defined. This reverts commit 6505c77501f1924571b2fe620c5c7b31ede0cd22.
Diffstat (limited to 'thread.c')
-rw-r--r--thread.c75
1 files changed, 48 insertions, 27 deletions
diff --git a/thread.c b/thread.c
index 508772c8fe..6f4be7d62f 100644
--- a/thread.c
+++ b/thread.c
@@ -631,6 +631,7 @@ thread_cleanup_func_before_exec(void *th_ptr)
{
rb_thread_t *th = th_ptr;
th->status = THREAD_KILLED;
+
// The thread stack doesn't exist in the forked process:
th->ec->machine.stack_start = th->ec->machine.stack_end = NULL;
@@ -687,7 +688,7 @@ rb_vm_proc_local_ep(VALUE proc)
VALUE rb_vm_invoke_proc_with_self(rb_execution_context_t *ec, rb_proc_t *proc, VALUE self,
int argc, const VALUE *argv, int kw_splat, VALUE passed_block_handler);
-static void
+static VALUE
thread_do_start_proc(rb_thread_t *th)
{
VALUE args = th->invoke_arg.proc.args;
@@ -701,7 +702,6 @@ thread_do_start_proc(rb_thread_t *th)
th->ec->root_lep = rb_vm_proc_local_ep(procval);
th->ec->root_svar = Qfalse;
- EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_BEGIN, th->self, 0, 0, 0, Qundef);
vm_check_ints_blocking(th->ec);
if (th->invoke_type == thread_invoke_type_ractor_proc) {
@@ -712,11 +712,12 @@ thread_do_start_proc(rb_thread_t *th)
rb_ractor_receive_parameters(th->ec, th->ractor, args_len, (VALUE *)args_ptr);
vm_check_ints_blocking(th->ec);
- // kick thread
- th->value = rb_vm_invoke_proc_with_self(th->ec, proc, self,
- args_len, args_ptr,
- th->invoke_arg.proc.kw_splat,
- VM_BLOCK_HANDLER_NONE);
+ return rb_vm_invoke_proc_with_self(
+ th->ec, proc, self,
+ args_len, args_ptr,
+ th->invoke_arg.proc.kw_splat,
+ VM_BLOCK_HANDLER_NONE
+ );
}
else {
args_len = RARRAY_LENINT(args);
@@ -732,17 +733,12 @@ thread_do_start_proc(rb_thread_t *th)
vm_check_ints_blocking(th->ec);
- // kick thread
- th->value = rb_vm_invoke_proc(th->ec, proc,
- args_len, args_ptr,
- th->invoke_arg.proc.kw_splat,
- VM_BLOCK_HANDLER_NONE);
- }
-
- EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef);
-
- if (th->invoke_type == thread_invoke_type_ractor_proc) {
- rb_ractor_atexit(th->ec, th->value);
+ return rb_vm_invoke_proc(
+ th->ec, proc,
+ args_len, args_ptr,
+ th->invoke_arg.proc.kw_splat,
+ VM_BLOCK_HANDLER_NONE
+ );
}
}
@@ -750,20 +746,33 @@ static void
thread_do_start(rb_thread_t *th)
{
native_set_thread_name(th);
+ VALUE result = Qundef;
+
+ EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_BEGIN, th->self, 0, 0, 0, Qundef);
switch (th->invoke_type) {
case thread_invoke_type_proc:
+ result = thread_do_start_proc(th);
+ break;
+
case thread_invoke_type_ractor_proc:
- thread_do_start_proc(th);
+ result = thread_do_start_proc(th);
+ rb_ractor_atexit(th->ec, result);
break;
+
case thread_invoke_type_func:
- th->value = (*th->invoke_arg.func.func)(th->invoke_arg.func.arg);
+ result = (*th->invoke_arg.func.func)(th->invoke_arg.func.arg);
break;
+
case thread_invoke_type_none:
rb_bug("unreachable");
}
rb_scheduler_set(Qnil);
+
+ th->value = result;
+
+ EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef);
}
void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec);
@@ -816,6 +825,9 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start)
thread_debug("thread start (get lock): %p\n", (void *)th);
+ // Ensure that we are not joinable.
+ VM_ASSERT(th->value == Qundef);
+
EC_PUSH_TAG(th->ec);
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
@@ -855,6 +867,12 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start)
th->value = Qnil;
}
+ // The thread is effectively finished and can be joined.
+ VM_ASSERT(th->value != Qundef);
+
+ rb_threadptr_join_list_wakeup(th);
+ rb_threadptr_unlock_all_locking_mutexes(th);
+
if (th->invoke_type == thread_invoke_type_ractor_proc) {
rb_thread_terminate_all(th);
rb_ractor_teardown(th->ec);
@@ -872,9 +890,6 @@ thread_start_func_2(rb_thread_t *th, VALUE *stack_start)
rb_threadptr_raise(ractor_main_th, 1, &errinfo);
}
- rb_threadptr_join_list_wakeup(th);
- rb_threadptr_unlock_all_locking_mutexes(th);
-
EC_POP_TAG();
rb_ec_clear_current_thread_trace_func(th->ec);
@@ -1151,6 +1166,12 @@ remove_from_join_list(VALUE arg)
static rb_hrtime_t *double2hrtime(rb_hrtime_t *, double);
+static int
+thread_finished(rb_thread_t *th)
+{
+ return th->status == THREAD_KILLED || th->value != Qundef;
+}
+
static VALUE
thread_join_sleep(VALUE arg)
{
@@ -1177,7 +1198,7 @@ thread_join_sleep(VALUE arg)
end = rb_hrtime_add(*limit, rb_hrtime_now());
}
- while (target_th->status != THREAD_KILLED) {
+ while (!thread_finished(target_th)) {
VALUE scheduler = rb_scheduler_current();
if (scheduler != Qnil) {
@@ -3318,11 +3339,11 @@ rb_thread_status(VALUE thread)
static VALUE
rb_thread_alive_p(VALUE thread)
{
- if (rb_threadptr_dead(rb_thread_ptr(thread))) {
- return Qfalse;
+ if (thread_finished(rb_thread_ptr(thread))) {
+ return Qfalse;
}
else {
- return Qtrue;
+ return Qtrue;
}
}