summaryrefslogtreecommitdiff
path: root/thread_pthread.c
diff options
context:
space:
mode:
Diffstat (limited to 'thread_pthread.c')
-rw-r--r--thread_pthread.c298
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 */