diff options
author | Samuel Williams <[email protected]> | 2021-07-28 19:55:55 +1200 |
---|---|---|
committer | Samuel Williams <[email protected]> | 2021-08-03 22:23:48 +1200 |
commit | 2d4f29e77e883c29e35417799f8001b8046cde03 (patch) | |
tree | c2201611275519a9f36b7d3d3ce79583bc7ba14f /thread.c | |
parent | 785c70e764c3222f9accac2555246d3921a7263d (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.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/4689
Diffstat (limited to 'thread.c')
-rw-r--r-- | thread.c | 75 |
1 files changed, 48 insertions, 27 deletions
@@ -632,6 +632,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; @@ -688,7 +689,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; @@ -702,7 +703,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) { @@ -713,11 +713,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); @@ -733,17 +734,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 + ); } } @@ -751,20 +747,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_fiber_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); @@ -817,6 +826,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) { @@ -857,6 +869,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); @@ -874,9 +892,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); @@ -1153,6 +1168,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) { @@ -1179,7 +1200,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_fiber_scheduler_current(); if (scheduler != Qnil) { @@ -3319,11 +3340,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; } } |