diff options
Diffstat (limited to 'thread_pthread.c')
| -rw-r--r-- | thread_pthread.c | 298 |
1 files changed, 194 insertions, 104 deletions
diff --git a/thread_pthread.c b/thread_pthread.c index 730ecb5416..5b5329fd21 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -90,9 +90,16 @@ static const void *const condattr_monotonic = NULL; #endif #endif +#ifdef HAVE_SCHED_YIELD +#define native_thread_yield() (void)sched_yield() +#else +#define native_thread_yield() ((void)0) +#endif + // native thread wrappers #define NATIVE_MUTEX_LOCK_DEBUG 0 +#define NATIVE_MUTEX_LOCK_DEBUG_YIELD 0 static void mutex_debug(const char *msg, void *lock) @@ -111,6 +118,9 @@ void rb_native_mutex_lock(pthread_mutex_t *lock) { int r; +#if NATIVE_MUTEX_LOCK_DEBUG_YIELD + native_thread_yield(); +#endif mutex_debug("lock", lock); if ((r = pthread_mutex_lock(lock)) != 0) { rb_bug_errno("pthread_mutex_lock", r); @@ -310,12 +320,6 @@ static rb_serial_t current_fork_gen = 1; /* We can't use GET_VM()->fork_gen */ static void threadptr_trap_interrupt(rb_thread_t *); -#ifdef HAVE_SCHED_YIELD -#define native_thread_yield() (void)sched_yield() -#else -#define native_thread_yield() ((void)0) -#endif - static void native_thread_dedicated_inc(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt); static void native_thread_dedicated_dec(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt); static void native_thread_assign(struct rb_native_thread *nt, rb_thread_t *th); @@ -694,8 +698,12 @@ thread_sched_readyq_contain_p(struct rb_thread_sched *sched, rb_thread_t *th) { rb_thread_t *rth; ccan_list_for_each(&sched->readyq, rth, sched.node.readyq) { - if (rth == th) return true; + if (rth == th) { + VM_ASSERT(th->sched.node.is_ready); + return true; + } } + VM_ASSERT(!th->sched.node.is_ready); return false; } @@ -716,6 +724,8 @@ thread_sched_deq(struct rb_thread_sched *sched) } else { next_th = ccan_list_pop(&sched->readyq, rb_thread_t, sched.node.readyq); + VM_ASSERT(next_th->sched.node.is_ready); + next_th->sched.node.is_ready = false; VM_ASSERT(sched->readyq_cnt > 0); sched->readyq_cnt--; @@ -749,6 +759,7 @@ thread_sched_enq(struct rb_thread_sched *sched, rb_thread_t *ready_th) } ccan_list_add_tail(&sched->readyq, &ready_th->sched.node.readyq); + ready_th->sched.node.is_ready = true; sched->readyq_cnt++; } @@ -832,6 +843,30 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b VM_ASSERT(th == rb_ec_thread_ptr(rb_current_ec_noinline())); if (th != sched->running) { + // TODO: This optimization should also be made to work for MN_THREADS + if (th->has_dedicated_nt && th == sched->runnable_hot_th && (sched->running == NULL || sched->running->has_dedicated_nt)) { + RUBY_DEBUG_LOG("(nt) stealing: hot-th:%u. running:%u", rb_th_serial(th), rb_th_serial(sched->running)); + + // If there is a thread set to run, move it back to the front of the readyq + if (sched->running != NULL) { + rb_thread_t *running = sched->running; + VM_ASSERT(!thread_sched_readyq_contain_p(sched, running)); + running->sched.node.is_ready = true; + ccan_list_add(&sched->readyq, &running->sched.node.readyq); + sched->readyq_cnt++; + } + + // Pull off the ready queue and start running. + if (th->sched.node.is_ready) { + VM_ASSERT(thread_sched_readyq_contain_p(sched, th)); + ccan_list_del_init(&th->sched.node.readyq); + th->sched.node.is_ready = false; + sched->readyq_cnt--; + } + thread_sched_set_running(sched, th); + rb_ractor_thread_switch(th->ractor, th, false); + } + // already deleted from running threads // VM_ASSERT(!ractor_sched_running_threads_contain_p(th->vm, th)); // need locking @@ -848,6 +883,15 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b } thread_sched_set_locked(sched, th); + if (sched->runnable_hot_th != NULL && sched->runnable_hot_th_waiting) { + VM_ASSERT(sched->runnable_hot_th != th); + // Give the hot thread a chance to preempt, if it's actively spinning. + // On multicore, this reduces the rate of core-switching. On single-core it + // should mostly be a nop, since the other thread can't be concurrently spinning. + thread_sched_unlock(sched, th); + thread_sched_lock(sched, th); + } + RUBY_DEBUG_LOG("(nt) wakeup %s", sched->running == th ? "success" : "failed"); if (th == sched->running) { rb_ractor_thread_switch(th->ractor, th, false); @@ -896,6 +940,11 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b thread_sched_add_running_thread(sched, th); } + // Control transfer to the current thread is now complete. The original thread + // cannot steal control at this point. + sched->runnable_hot_th = NULL; + sched->runnable_hot_th_waiting = 0; + // VM_ASSERT(ractor_sched_running_threads_contain_p(th->vm, th)); need locking RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_RESUMED, th); } @@ -932,6 +981,13 @@ thread_sched_to_running_common(struct rb_thread_sched *sched, rb_thread_t *th) static void thread_sched_to_running(struct rb_thread_sched *sched, rb_thread_t *th) { + // We are reading and writing these sched fields without lock cover, but + // there are no correctness issues resulting from stale cache or delayed writeback. + // When it works, this causes the next-scheduled thread to yield the sched lock + // briefly so that we can grab it if we're still spinning (not descheduled yet). + if (sched->runnable_hot_th == th) { + sched->runnable_hot_th_waiting = 1; + } thread_sched_lock(sched, th); { thread_sched_to_running_common(sched, th); @@ -968,33 +1024,16 @@ thread_sched_wakeup_next_thread(struct rb_thread_sched *sched, rb_thread_t *th, } } -// running -> waiting -// -// to_dead: false -// th will run dedicated task. -// run another ready thread. -// to_dead: true -// th will be dead. -// run another ready thread. +// running -> dead (locked) static void -thread_sched_to_waiting_common0(struct rb_thread_sched *sched, rb_thread_t *th, bool to_dead) +thread_sched_to_dead_common(struct rb_thread_sched *sched, rb_thread_t *th) { - RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); - - if (!to_dead) native_thread_dedicated_inc(th->vm, th->ractor, th->nt); + RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated); - RUBY_DEBUG_LOG("%sth:%u", to_dead ? "to_dead " : "", rb_th_serial(th)); + RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); - bool can_switch = to_dead ? !th_has_dedicated_nt(th) : false; - thread_sched_wakeup_next_thread(sched, th, can_switch); -} + thread_sched_wakeup_next_thread(sched, th, !th_has_dedicated_nt(th)); -// running -> dead (locked) -static void -thread_sched_to_dead_common(struct rb_thread_sched *sched, rb_thread_t *th) -{ - RUBY_DEBUG_LOG("dedicated:%d", th->nt->dedicated); - thread_sched_to_waiting_common0(sched, th, true); RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_EXITED, th); } @@ -1013,21 +1052,29 @@ thread_sched_to_dead(struct rb_thread_sched *sched, rb_thread_t *th) // // This thread will run dedicated task (th->nt->dedicated++). static void -thread_sched_to_waiting_common(struct rb_thread_sched *sched, rb_thread_t *th) +thread_sched_to_waiting_common(struct rb_thread_sched *sched, rb_thread_t *th, bool yield_immediately) { - RUBY_DEBUG_LOG("dedicated:%d", th->nt->dedicated); - thread_sched_to_waiting_common0(sched, th, false); + RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated); + + RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); + + native_thread_dedicated_inc(th->vm, th->ractor, th->nt); + if (!yield_immediately) { + sched->runnable_hot_th = th; + sched->runnable_hot_th_waiting = 0; + } + thread_sched_wakeup_next_thread(sched, th, false); } // running -> waiting // // This thread will run a dedicated task. static void -thread_sched_to_waiting(struct rb_thread_sched *sched, rb_thread_t *th) +thread_sched_to_waiting(struct rb_thread_sched *sched, rb_thread_t *th, bool yield_immediately) { thread_sched_lock(sched, th); { - thread_sched_to_waiting_common(sched, th); + thread_sched_to_waiting_common(sched, th, yield_immediately); } thread_sched_unlock(sched, th); } @@ -1035,7 +1082,7 @@ thread_sched_to_waiting(struct rb_thread_sched *sched, rb_thread_t *th) // mini utility func // return true if any there are any interrupts static bool -ubf_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg) +ubf_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, rb_atomic_t *event_serial) { VM_ASSERT(func != NULL); @@ -1055,6 +1102,10 @@ ubf_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg) VM_ASSERT(th->unblock.func == NULL); th->unblock.func = func; th->unblock.arg = arg; + if (event_serial) { + rb_atomic_t prev_serial = RUBY_ATOMIC_FETCH_ADD(th->unblock.event_serial, 1); + *event_serial = prev_serial+1; + } } rb_native_mutex_unlock(&th->interrupt_lock); @@ -1062,16 +1113,17 @@ ubf_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg) } static void -ubf_clear(rb_thread_t *th) +ubf_clear(rb_thread_t *th, bool clear_serial) { - if (th->unblock.func) { - rb_native_mutex_lock(&th->interrupt_lock); - { - th->unblock.func = NULL; - th->unblock.arg = NULL; + rb_native_mutex_lock(&th->interrupt_lock); + { + th->unblock.func = NULL; + th->unblock.arg = NULL; + if (clear_serial) { + RUBY_ATOMIC_ADD(th->unblock.event_serial, 1); } - rb_native_mutex_unlock(&th->interrupt_lock); } + rb_native_mutex_unlock(&th->interrupt_lock); } static void @@ -1108,26 +1160,25 @@ thread_sched_to_waiting_until_wakeup(struct rb_thread_sched *sched, rb_thread_t RB_VM_SAVE_MACHINE_CONTEXT(th); - if (ubf_set(th, ubf_waiting, (void *)th)) { - return; - } RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th); thread_sched_lock(sched, th); { - if (!RUBY_VM_INTERRUPTED(th->ec)) { + // NOTE: there's a lock ordering inversion here with the ubf call, but it's benign. + if (ubf_set(th, ubf_waiting, (void *)th, NULL)) { + RUBY_DEBUG_LOG("th:%u interrupted", rb_th_serial(th)); + } + else { bool can_direct_transfer = !th_has_dedicated_nt(th); + // NOTE: th->status is set before and after this sleep outside of this function in `sleep_forever` thread_sched_wakeup_next_thread(sched, th, can_direct_transfer); thread_sched_wait_running_turn(sched, th, can_direct_transfer); } - else { - RUBY_DEBUG_LOG("th:%u interrupted", rb_th_serial(th)); - } } thread_sched_unlock(sched, th); - ubf_clear(th); + ubf_clear(th, false); } // run another thread in the ready queue. @@ -1145,6 +1196,7 @@ thread_sched_yield(struct rb_thread_sched *sched, rb_thread_t *th) bool can_direct_transfer = !th_has_dedicated_nt(th); thread_sched_to_ready_common(sched, th, false, can_direct_transfer); thread_sched_wait_running_turn(sched, th, can_direct_transfer); + th->status = THREAD_RUNNABLE; } else { VM_ASSERT(sched->readyq_cnt == 0); @@ -1334,7 +1386,7 @@ void rb_ractor_lock_self(rb_ractor_t *r); void rb_ractor_unlock_self(rb_ractor_t *r); // The current thread for a ractor is put to "sleep" (descheduled in the STOPPED_FOREVER state) waiting for -// a ractor action to wake it up. See docs for `ractor_sched_sleep_with_cleanup` for more info. +// a ractor action to wake it up. void rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf, void *ubf_arg) { @@ -1344,8 +1396,9 @@ rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_fun rb_thread_t * volatile th = rb_ec_thread_ptr(ec); struct rb_thread_sched *sched = TH_SCHED(th); + struct ractor_waiter *waiter = (struct ractor_waiter*)ubf_arg; - if (ubf_set(th, ubf, ubf_arg)) { + if (ubf_set(th, ubf, ubf_arg, &waiter->event_serial)) { // interrupted return; } @@ -1366,7 +1419,7 @@ rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_fun thread_sched_unlock(sched, th); rb_ractor_lock_self(cr); - ubf_clear(th); + ubf_clear(th, true); RUBY_DEBUG_LOG("end%s", ""); } @@ -1374,7 +1427,7 @@ rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_fun void rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *r_th) { - // ractor lock of r is NOT acquired + // ractor lock of r acquired struct rb_thread_sched *sched = TH_SCHED(r_th); RUBY_DEBUG_LOG("r:%u th:%d", (unsigned int)rb_ractor_id(r), r_th->serial); @@ -1382,6 +1435,7 @@ rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *r_th) thread_sched_lock(sched, r_th); { if (r_th->status == THREAD_STOPPED_FOREVER) { + RUBY_ATOMIC_ADD(r_th->unblock.event_serial, 1); thread_sched_to_ready_common(sched, r_th, true, false); } } @@ -1452,7 +1506,7 @@ rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr) vm->ractor.sync.lock_owner = cr; } - // do not release ractor_sched_lock and threre is no newly added (resumed) thread + // do not release ractor_sched_lock and there is no newly added (resumed) thread // thread_sched_setup_running_threads } @@ -1567,6 +1621,8 @@ get_native_thread_id(void) #endif #if defined(HAVE_WORKING_FORK) +void rb_internal_thread_event_hooks_rw_lock_atfork(void); + static void thread_sched_atfork(struct rb_thread_sched *sched) { @@ -1598,6 +1654,8 @@ thread_sched_atfork(struct rb_thread_sched *sched) ccan_list_head_init(&vm->ractor.sched.timeslice_threads); ccan_list_head_init(&vm->ractor.sched.running_threads); + rb_internal_thread_event_hooks_rw_lock_atfork(); + VM_ASSERT(sched->is_running); sched->is_running_timeslice = false; @@ -1741,7 +1799,12 @@ ruby_mn_threads_params(void) main_ractor->threads.sched.enable_mn_threads = enable_mn_threads; const char *max_cpu_cstr = getenv("RUBY_MAX_CPU"); - const int default_max_cpu = 8; // TODO: CPU num? +#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_ONLN) + long nprocessors = sysconf(_SC_NPROCESSORS_ONLN); + const int default_max_cpu = (nprocessors > 0) ? (int)nprocessors : 8; +#else + const int default_max_cpu = 8; +#endif int max_cpu = default_max_cpu; if (USE_MN_THREADS && max_cpu_cstr) { @@ -1829,8 +1892,8 @@ native_thread_destroy_atfork(struct rb_native_thread *nt) */ RB_ALTSTACK_FREE(nt->altstack); - ruby_xfree(nt->nt_context); - ruby_xfree(nt); + SIZED_FREE(nt->nt_context); + SIZED_FREE(nt); } } @@ -2208,7 +2271,7 @@ native_thread_create_dedicated(rb_thread_t *th) th->sched.malloc_stack = true; rb_ec_initialize_vm_stack(th->ec, vm_stack, vm_stack_word_size); th->sched.context_stack = vm_stack; - + th->sched.context_stack_size = vm_stack_word_size; int err = native_thread_create0(th->nt); if (!err) { @@ -2346,7 +2409,7 @@ rb_threadptr_sched_free(rb_thread_t *th) #if USE_MN_THREADS if (th->sched.malloc_stack) { // has dedicated - ruby_xfree(th->sched.context_stack); + SIZED_FREE_N((VALUE *)th->sched.context_stack, th->sched.context_stack_size); native_thread_destroy(th->nt); } else { @@ -2354,11 +2417,11 @@ rb_threadptr_sched_free(rb_thread_t *th) // TODO: how to free nt and nt->altstack? } - ruby_xfree(th->sched.context); + SIZED_FREE(th->sched.context); th->sched.context = NULL; // VM_ASSERT(th->sched.context == NULL); #else - ruby_xfree(th->sched.context_stack); + SIZED_FREE_N((VALUE *)th->sched.context_stack, th->sched.context_stack_size); native_thread_destroy(th->nt); #endif @@ -2588,16 +2651,14 @@ ubf_threads_empty(void) static void ubf_wakeup_all_threads(void) { - if (!ubf_threads_empty()) { - rb_thread_t *th; - rb_native_mutex_lock(&ubf_list_lock); - { - ccan_list_for_each(&ubf_list_head, th, sched.node.ubf) { - ubf_wakeup_thread(th); - } + rb_thread_t *th; + rb_native_mutex_lock(&ubf_list_lock); + { + ccan_list_for_each(&ubf_list_head, th, sched.node.ubf) { + ubf_wakeup_thread(th); } - rb_native_mutex_unlock(&ubf_list_lock); } + rb_native_mutex_unlock(&ubf_list_lock); } #else /* USE_UBF_LIST */ @@ -2866,7 +2927,7 @@ static struct { static void timer_thread_check_timeslice(rb_vm_t *vm); static int timer_thread_set_timeout(rb_vm_t *vm); -static void timer_thread_wakeup_thread(rb_thread_t *th); +static void timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial); #include "thread_pthread_mn.c" @@ -2910,24 +2971,29 @@ timer_thread_set_timeout(rb_vm_t *vm) } ractor_sched_unlock(vm, NULL); - if (vm->ractor.sched.timeslice_wait_inf) { - rb_native_mutex_lock(&timer_th.waiting_lock); - { - struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node); - rb_thread_t *th = thread_sched_waiting_thread(w); + // Always check waiting threads to find minimum timeout + // even when scheduler has work (grq_cnt > 0) + rb_native_mutex_lock(&timer_th.waiting_lock); + { + struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node); + rb_thread_t *th = thread_sched_waiting_thread(w); + + if (th && (th->sched.waiting_reason.flags & thread_sched_waiting_timeout)) { + rb_hrtime_t now = rb_hrtime_now(); + rb_hrtime_t hrrel = rb_hrtime_sub(th->sched.waiting_reason.data.timeout, now); - if (th && (th->sched.waiting_reason.flags & thread_sched_waiting_timeout)) { - rb_hrtime_t now = rb_hrtime_now(); - rb_hrtime_t hrrel = rb_hrtime_sub(th->sched.waiting_reason.data.timeout, now); + RUBY_DEBUG_LOG("th:%u now:%lu rel:%lu", rb_th_serial(th), (unsigned long)now, (unsigned long)hrrel); - RUBY_DEBUG_LOG("th:%u now:%lu rel:%lu", rb_th_serial(th), (unsigned long)now, (unsigned long)hrrel); + // TODO: overflow? + int thread_timeout = (int)((hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC); // ms - // TODO: overflow? - timeout = (int)((hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC); // ms + // Use minimum of scheduler timeout and thread sleep timeout + if (timeout < 0 || thread_timeout < timeout) { + timeout = thread_timeout; } } - rb_native_mutex_unlock(&timer_th.waiting_lock); } + rb_native_mutex_unlock(&timer_th.waiting_lock); RUBY_DEBUG_LOG("timeout:%d inf:%d", timeout, (int)vm->ractor.sched.timeslice_wait_inf); @@ -2951,19 +3017,11 @@ timer_thread_check_signal(rb_vm_t *vm) static bool timer_thread_check_exceed(rb_hrtime_t abs, rb_hrtime_t now) { - if (abs < now) { - return true; - } - else if (abs - now < RB_HRTIME_PER_MSEC) { - return true; // too short time - } - else { - return false; - } + return abs <= now; } static rb_thread_t * -timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now) +timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now, uint32_t *event_serial) { struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node); @@ -2980,26 +3038,31 @@ timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now) w->flags = thread_sched_waiting_none; w->data.result = 0; - return thread_sched_waiting_thread(w); + rb_thread_t *th = thread_sched_waiting_thread(w); + *event_serial = w->data.event_serial; + return th; } return NULL; } static void -timer_thread_wakeup_thread(rb_thread_t *th) +timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th, uint32_t event_serial) +{ + if (sched->running != th && th->sched.event_serial == event_serial) { + thread_sched_to_ready_common(sched, th, true, false); + } +} + +static void +timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial) { RUBY_DEBUG_LOG("th:%u", rb_th_serial(th)); struct rb_thread_sched *sched = TH_SCHED(th); thread_sched_lock(sched, th); { - if (sched->running != th) { - thread_sched_to_ready_common(sched, th, true, false); - } - else { - // will be release the execution right - } + timer_thread_wakeup_thread_locked(sched, th, event_serial); } thread_sched_unlock(sched, th); } @@ -3009,11 +3072,14 @@ timer_thread_check_timeout(rb_vm_t *vm) { rb_hrtime_t now = rb_hrtime_now(); rb_thread_t *th; + uint32_t event_serial; rb_native_mutex_lock(&timer_th.waiting_lock); { - while ((th = timer_thread_deq_wakeup(vm, now)) != NULL) { - timer_thread_wakeup_thread(th); + while ((th = timer_thread_deq_wakeup(vm, now, &event_serial)) != NULL) { + rb_native_mutex_unlock(&timer_th.waiting_lock); + timer_thread_wakeup_thread(th, event_serial); + rb_native_mutex_lock(&timer_th.waiting_lock); } } rb_native_mutex_unlock(&timer_th.waiting_lock); @@ -3398,6 +3464,22 @@ struct rb_internal_thread_event_hook { static pthread_rwlock_t rb_internal_thread_event_hooks_rw_lock = PTHREAD_RWLOCK_INITIALIZER; +#if defined(HAVE_WORKING_FORK) +void +rb_internal_thread_event_hooks_rw_lock_atfork(void) +{ + // After fork(), this rwlock may have been held by a now-dead thread. + // + // pthread_rwlock_destroy() on a held lock is undefined behavior, and + // pthread_rwlock_init() on an already-initialized lock is also undefined + // behavior + // + // Direct assignment of PTHREAD_RWLOCK_INITIALIZER is safe and portable. + rb_internal_thread_event_hooks_rw_lock = + (pthread_rwlock_t)PTHREAD_RWLOCK_INITIALIZER; +} +#endif + rb_internal_thread_event_hook_t * rb_internal_thread_add_event_hook(rb_internal_thread_event_callback callback, rb_event_flag_t internal_event, void *user_data) { @@ -3451,7 +3533,7 @@ rb_internal_thread_remove_event_hook(rb_internal_thread_event_hook_t * hook) } if (success) { - ruby_xfree(hook); + SIZED_FREE(hook); } return success; } @@ -3492,4 +3574,12 @@ rb_thread_lock_native_thread(void) return is_snt; } +void +rb_thread_malloc_stack_set(rb_thread_t *th, void *stack, size_t stack_size) +{ + th->sched.malloc_stack = true; + th->sched.context_stack = stack; + th->sched.context_stack_size = stack_size; +} + #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */ |
