23#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
24static void timer_thread_unregister_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags);
28timer_thread_check_exceed(rb_hrtime_t abs, rb_hrtime_t now)
44#define FD_WAIT_IO_MASK (thread_sched_waiting_io_read | thread_sched_waiting_io_write)
56fd_shard_unlock(
int fd)
61#define TIMER_WHEEL_NO_EXPIRY RB_HRTIME_MAX
62#ifndef TIMER_WHEEL_TICK_MS
63#define TIMER_WHEEL_TICK_MS 1
79timer_wheel_tick(rb_hrtime_t hrt)
81 return hrt / (RB_HRTIME_PER_MSEC * TIMER_WHEEL_TICK_MS);
85timer_wheel_ctz64(uint64_t v)
87#if defined(__GNUC__) || defined(__clang__)
88 return __builtin_ctzll(v);
91 while (!(v & 1)) { v >>= 1; n++; }
97timer_wheel_level(uint64_t dist_ms)
99 if (dist_ms < ((uint64_t)1 << TIMER_WHEEL_SLOT_BITS))
return 0;
100 if (dist_ms < ((uint64_t)1 << 2 * TIMER_WHEEL_SLOT_BITS))
return 1;
101 if (dist_ms < ((uint64_t)1 << 3 * TIMER_WHEEL_SLOT_BITS))
return 2;
102 return TIMER_WHEEL_LEVELS - 1;
108 uint64_t dl_tick = timer_wheel_tick(w->data.timeout);
109 uint64_t cur = timer_th.wheel_cursor_tick;
113 uint64_t target = dl_tick > cur ? dl_tick : cur + 1;
114 int lvl = timer_wheel_level(target - cur);
115 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
116 uint64_t slot_tick = target >> shift;
118 if (lvl == TIMER_WHEEL_LEVELS - 1) {
121 uint64_t far = (cur >> shift) + TIMER_WHEEL_SLOTS - 1;
122 if (slot_tick > far) slot_tick = far;
125 int slot = (int)(slot_tick & (TIMER_WHEEL_SLOTS - 1));
127 ccan_list_add_tail(&timer_th.wheel[lvl].slots[slot], &w->node);
128 timer_th.wheel[lvl].occupied |= UINT64_C(1) << slot;
129 w->wheel_lvl = (uint8_t)lvl;
130 w->wheel_slot = (uint8_t)slot;
132 if (w->data.timeout < timer_th.next_expiry) {
133 timer_th.next_expiry = w->data.timeout;
141 ccan_list_del_init(&w->node);
143 if (w->flags & thread_sched_waiting_timeout) {
144 struct timer_wheel_level *lv = &timer_th.wheel[w->wheel_lvl];
145 if (ccan_list_empty(&lv->slots[w->wheel_slot])) {
146 lv->occupied &= ~(UINT64_C(1) << w->wheel_slot);
155timer_wheel_next_expiry(
void)
157 rb_hrtime_t best = TIMER_WHEEL_NO_EXPIRY;
159 for (
int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
160 uint64_t occ = timer_th.wheel[lvl].occupied;
163 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
164 uint64_t cur_tick = timer_th.wheel_cursor_tick >> shift;
165 unsigned base = (unsigned)((cur_tick + 1) & (TIMER_WHEEL_SLOTS - 1));
167 uint64_t rot = (occ >> base) | (base ? (occ << (TIMER_WHEEL_SLOTS - base)) : 0);
168 uint64_t tick = cur_tick + 1 + timer_wheel_ctz64(rot);
169 rb_hrtime_t start = (rb_hrtime_t)(tick << shift) * RB_HRTIME_PER_MSEC * TIMER_WHEEL_TICK_MS;
171 if (start < best) best = start;
181timer_wheel_drain(rb_hrtime_t now, uint64_t now_tick,
struct ccan_list_head *expired)
183 uint64_t prev_tick = timer_th.wheel_cursor_tick;
184 timer_th.wheel_cursor_tick = now_tick;
188 rb_hrtime_t reinserted = TIMER_WHEEL_NO_EXPIRY;
190 for (
int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
191 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
192 uint64_t from = prev_tick >> shift;
193 uint64_t to = now_tick >> shift;
195 if (to == from)
break;
197 uint64_t steps = to - from;
198 if (steps > TIMER_WHEEL_SLOTS) steps = TIMER_WHEEL_SLOTS;
199 struct timer_wheel_level *lv = &timer_th.wheel[lvl];
201 for (uint64_t tick = to - steps + 1; tick <= to; tick++) {
202 int slot = (int)(tick & (TIMER_WHEEL_SLOTS - 1));
204 if (!(lv->occupied & (UINT64_C(1) << slot)))
continue;
205 lv->occupied &= ~(UINT64_C(1) << slot);
209 struct ccan_list_head pending;
210 ccan_list_head_init(&pending);
211 ccan_list_append_list(&pending, &lv->slots[slot]);
215 if (timer_thread_check_exceed(w->data.timeout, now)) {
216 RUBY_DEBUG_LOG(
"expired th:%u", rb_th_serial(thread_sched_waiting_thread(w)));
223 ccan_list_add_tail(expired, &w->node);
228 if (w->data.timeout < reinserted) reinserted = w->data.timeout;
229 timer_wheel_insert(w);
235 timer_th.next_expiry = timer_wheel_next_expiry();
236 if (reinserted < timer_th.next_expiry) timer_th.next_expiry = reinserted;
242timer_wheel_timeout(
int timeout)
246 if (timer_th.next_expiry != TIMER_WHEEL_NO_EXPIRY) {
247 rb_hrtime_t now = rb_hrtime_now();
248 rb_hrtime_t hrrel = rb_hrtime_sub(timer_th.next_expiry, now);
250 RUBY_DEBUG_LOG(
"now:%lu rel:%lu", (
unsigned long)now, (
unsigned long)hrrel);
252 rb_hrtime_t msec = (hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC;
255 int thread_timeout = msec > INT_MAX ? INT_MAX : (int)msec;
258 if (timeout < 0 || thread_timeout < timeout) {
259 timeout = thread_timeout;
271 if (sched->running != th && th->sched.event_serial == event_serial) {
272 thread_sched_to_ready_common(sched, th,
true,
false);
277timer_thread_wakeup_thread(
rb_thread_t *th, uint32_t event_serial)
279 RUBY_DEBUG_LOG(
"th:%u", rb_th_serial(th));
282 thread_sched_lock(sched, th);
284 timer_thread_wakeup_thread_locked(sched, th, event_serial);
286 thread_sched_unlock(sched, th);
289#define TIMEOUT_WAKE_BATCH 16
292struct timer_wake {
rb_thread_t *th; uint32_t serial; };
302 th->sched.wake_pending_cnt++;
310 VM_ASSERT(th->sched.wake_pending_cnt > 0);
311 th->sched.wake_pending_cnt--;
317timer_wake_pending_clear(
struct timer_wake *batch,
int n)
319 for (
int i = 0; i < n; i++) {
320 timer_wake_pending_dec(batch[i].th);
329 if (!TIMER_THREAD_CREATED_P())
return;
332 while (th->sched.wake_pending_cnt > 0) {
339timer_thread_check_timeout(
rb_vm_t *vm)
341 rb_hrtime_t now = rb_hrtime_now();
342 uint64_t now_tick = timer_wheel_tick(now);
343 struct ccan_list_head expired;
345 ccan_list_head_init(&expired);
347 struct timer_wake batch[TIMEOUT_WAKE_BATCH];
352 struct {
rb_thread_t *th; uint32_t serial;
int fd; } io_claims[TIMEOUT_WAKE_BATCH];
358 if (now_tick > timer_th.wheel_cursor_tick) {
359 timer_wheel_drain(now, now_tick, &expired);
363 while (n + n_io < TIMEOUT_WAKE_BATCH &&
370 ccan_list_node_init(&w->node);
372 if (w->flags & FD_WAIT_IO_MASK) {
377 io_claims[n_io].th = thread_sched_waiting_thread(w);
378 io_claims[n_io].serial = w->data.event_serial;
379 io_claims[n_io].fd = w->data.fd;
380 timer_wake_pending_inc(io_claims[n_io].th);
385 batch[n].th = thread_sched_waiting_thread(w);
386 batch[n].serial = w->data.event_serial;
387 timer_wake_pending_inc(batch[n].th);
388 w->flags = thread_sched_waiting_none;
393 more = !ccan_list_empty(&expired);
397 for (
int i = 0; i < n; i++) {
398 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
400 timer_wake_pending_clear(batch, n);
403 for (
int i = 0; i < n_io; i++) {
405 int fd = io_claims[i].fd;
406 bool claimed =
false;
412 if (w->flags != thread_sched_waiting_none &&
413 w->data.event_serial == io_claims[i].serial) {
414 VM_ASSERT(w->data.fd == fd);
415 timer_thread_unregister_waiting(th, fd, w->flags);
416 w->flags = thread_sched_waiting_none;
424 timer_thread_wakeup_thread(th, io_claims[i].serial);
426 timer_wake_pending_dec(th);
438 enum thread_sched_waiting_flag flags = w->flags;
440 if (flags == thread_sched_waiting_none) {
443 else if (flags & FD_WAIT_IO_MASK) {
447 if ((w->flags & FD_WAIT_IO_MASK) && w->data.fd == fd) {
448 if (w->flags & thread_sched_waiting_timeout) {
453 timer_thread_unregister_waiting(th, fd, w->flags);
454 w->flags = thread_sched_waiting_none;
462 if (w->flags && !(w->flags & FD_WAIT_IO_MASK)) {
464 w->flags = thread_sched_waiting_none;
475ubf_event_waiting(
void *ptr)
480 RUBY_DEBUG_LOG(
"th:%u", rb_th_serial(th));
482 VM_ASSERT(th->nt == NULL || !th_has_dedicated_nt(th));
485 th->unblock.func = NULL;
486 th->unblock.arg = NULL;
488 thread_sched_lock(sched, th);
490 bool canceled = timer_thread_cancel_waiting(th);
492 if (sched->running == th) {
493 RUBY_DEBUG_LOG(
"not waiting yet");
496 thread_sched_to_ready_common(sched, th,
true,
false);
499 RUBY_DEBUG_LOG(
"already not waiting");
502 thread_sched_unlock(sched, th);
507enum timer_thread_register_result {
508 timer_thread_registered,
509 timer_thread_already_ready,
510 timer_thread_unavailable,
513static enum timer_thread_register_result
514timer_thread_register_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial);
519ractor_sched_timeout_arm(
rb_thread_t *th,
const rb_hrtime_t *rel)
521 rb_hrtime_t rel_copy = *rel;
523 return timer_thread_register_waiting(th, -1, thread_sched_waiting_timeout, &rel_copy,
524 ++th->sched.event_serial) == timer_thread_registered;
531 return timer_thread_cancel_waiting(th);
535static enum thread_sched_wait_result
536thread_sched_wait_events(
struct rb_thread_sched *sched,
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
538 VM_ASSERT(!th_has_dedicated_nt(th));
540 volatile bool timedout =
false, need_cancel =
false;
541 volatile enum timer_thread_register_result reg = timer_thread_unavailable;
543 uint32_t event_serial = ++th->sched.event_serial;
546 thread_sched_lock(sched, th);
549 if (ubf_set(th, ubf_event_waiting, (
void *)th, NULL)) {
552 thread_sched_unlock(sched, th);
553 return thread_sched_wait_event;
556 reg = timer_thread_register_waiting(th, fd, events, rel, event_serial);
558 if (reg == timer_thread_registered) {
559 RUBY_DEBUG_LOG(
"wait fd:%d", fd);
561 RB_VM_SAVE_MACHINE_CONTEXT(th);
565 if (th->sched.waiting_reason.flags == thread_sched_waiting_none) {
566 th->sched.event_serial++;
569 else if (RUBY_VM_INTERRUPTED(th->ec)) {
570 th->sched.event_serial++;
574 RUBY_DEBUG_LOG(
"sleep");
581 enum rb_thread_status prev_status = th->status;
582 if (prev_status == THREAD_RUNNABLE) th->status = THREAD_STOPPED_FOREVER;
583 thread_sched_wakeup_next_thread(sched, th,
true);
584 thread_sched_wait_running_turn(sched, th,
true, NULL);
585 if (prev_status == THREAD_RUNNABLE) th->status = THREAD_RUNNABLE;
587 RUBY_DEBUG_LOG(
"wakeup");
590 timedout = th->sched.waiting_reason.data.result == 0;
593 timer_thread_cancel_waiting(th);
599 RUBY_DEBUG_LOG(
"did not wait fd:%d reg:%d", fd, (
int)reg);
602 thread_sched_unlock(sched, th);
605 ubf_clear(th,
false);
607 VM_ASSERT(sched->running == th);
609 if (reg == timer_thread_unavailable)
return thread_sched_wait_unavailable;
611 if (reg == timer_thread_already_ready)
return thread_sched_wait_event;
614 if (timedout && RUBY_VM_INTERRUPTED(th->ec))
return thread_sched_wait_unavailable;
615 return timedout ? thread_sched_wait_timeout : thread_sched_wait_event;
621get_sysconf_page_size(
void)
623 static long page_size = 0;
625 if (UNLIKELY(page_size == 0)) {
626 page_size = sysconf(_SC_PAGESIZE);
627 VM_ASSERT(page_size < INT_MAX);
629 return (
int)page_size;
632#define MSTACK_CHUNK_SIZE (512 * 1024 * 1024)
633#define MSTACK_PAGE_SIZE get_sysconf_page_size()
634#define MSTACK_CHUNK_PAGE_NUM (MSTACK_CHUNK_SIZE / MSTACK_PAGE_SIZE - 1)
647static struct nt_stack_chunk_header {
648 struct nt_stack_chunk_header *prev_chunk;
649 struct nt_stack_chunk_header *prev_free_chunk;
655 uint16_t stack_count;
656 uint16_t uninitialized_stack_count;
658 uint16_t free_stack_pos;
659 uint16_t free_stack[];
660} *nt_stack_chunks = NULL,
661 *nt_free_stack_chunks = NULL;
663struct nt_machine_stack_footer {
664 struct nt_stack_chunk_header *ch;
668static rb_nativethread_lock_t nt_machine_stack_lock = RB_NATIVETHREAD_LOCK_INIT;
672nt_machine_stack_atfork(
void)
684nt_vm_stack_area(
const rb_vm_t *vm)
686 return (
size_t)roomof(vm->default_params.thread_vm_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
690nt_machine_stack_area(
const rb_vm_t *vm)
692 return (
size_t)roomof(vm->default_params.thread_machine_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
697nt_thread_stack_size(
void)
700 if (LIKELY(msz > 0))
return msz;
703 msz = nt_vm_stack_area(vm) + MSTACK_PAGE_SIZE + nt_machine_stack_area(vm);
707static struct nt_stack_chunk_header *
708nt_alloc_thread_stack_chunk(
void)
710 const char *m = (
void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
711 if (m == MAP_FAILED) {
715 ruby_annotate_mmap(m, MSTACK_CHUNK_SIZE,
"Ruby:nt_alloc_thread_stack_chunk");
717 size_t msz = nt_thread_stack_size();
718 int header_page_cnt = 1;
719 int stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
720 int ch_size =
sizeof(
struct nt_stack_chunk_header) + sizeof(uint16_t) * stack_count;
722 if (ch_size > MSTACK_PAGE_SIZE * header_page_cnt) {
723 header_page_cnt = (ch_size + MSTACK_PAGE_SIZE - 1) / MSTACK_PAGE_SIZE;
724 stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
727 VM_ASSERT(stack_count <= UINT16_MAX);
730 if (mprotect((
void *)m, (
size_t)header_page_cnt * MSTACK_PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) {
731 munmap((
void *)m, MSTACK_CHUNK_SIZE);
735 struct nt_stack_chunk_header *ch = (
struct nt_stack_chunk_header *)m;
737 ch->start_page = header_page_cnt;
738 ch->prev_chunk = nt_stack_chunks;
739 ch->prev_free_chunk = nt_free_stack_chunks;
740 ch->on_free_list =
true;
741 ch->uninitialized_stack_count = ch->stack_count = (uint16_t)stack_count;
742 ch->free_stack_pos = 0;
744 RUBY_DEBUG_LOG(
"ch:%p start_page:%d stack_cnt:%d stack_size:%d", ch, (
int)ch->start_page, (
int)ch->stack_count, (
int)msz);
750nt_stack_chunk_get_stack_start(
struct nt_stack_chunk_header *ch,
size_t idx)
752 const char *m = (
char *)ch;
753 return (
void *)(m + ch->start_page * MSTACK_PAGE_SIZE + idx * nt_thread_stack_size());
756static struct nt_machine_stack_footer *
757nt_stack_chunk_get_msf(
const rb_vm_t *vm,
const char *mstack)
760 const size_t msz = vm->default_params.thread_machine_stack_size;
761 return (
struct nt_machine_stack_footer *)&mstack[msz -
sizeof(
struct nt_machine_stack_footer)];
765nt_stack_chunk_get_stack(
const rb_vm_t *vm,
struct nt_stack_chunk_header *ch,
size_t idx,
void **vm_stack,
void **machine_stack)
770 const char *vstack, *mstack;
771 const char *guard_page;
772 vstack = nt_stack_chunk_get_stack_start(ch, idx);
773 guard_page = vstack + nt_vm_stack_area(vm);
774 mstack = guard_page + MSTACK_PAGE_SIZE;
776 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(vm, mstack);
781 RUBY_DEBUG_LOG(
"msf:%p vstack:%p-%p guard_page:%p-%p mstack:%p-%p", msf,
782 vstack, (
void *)(guard_page-1),
783 guard_page, (
void *)(mstack-1),
784 mstack, (
void *)(msf));
787 *vm_stack = (
void *)vstack;
788 *machine_stack = (
void *)mstack;
793nt_stack_chunk_dump(
void)
795 struct nt_stack_chunk_header *ch;
798 fprintf(stderr,
"** nt_stack_chunks\n");
799 ch = nt_stack_chunks;
800 for (i=0; ch; i++, ch = ch->prev_chunk) {
801 fprintf(stderr,
"%d %p free_pos:%d\n", i, (
void *)ch, (
int)ch->free_stack_pos);
804 fprintf(stderr,
"** nt_free_stack_chunks\n");
805 ch = nt_free_stack_chunks;
806 for (i=0; ch; i++, ch = ch->prev_free_chunk) {
807 fprintf(stderr,
"%d %p free_pos:%d\n", i, (
void *)ch, (
int)ch->free_stack_pos);
812nt_alloc_stack(
rb_vm_t *vm,
void **vm_stack,
void **machine_stack)
819 if (nt_free_stack_chunks) {
820 struct nt_stack_chunk_header *ch = nt_free_stack_chunks;
821 if (ch->free_stack_pos > 0) {
822 RUBY_DEBUG_LOG(
"free_stack_pos:%d", ch->free_stack_pos);
823 nt_stack_chunk_get_stack(vm, ch, ch->free_stack[--ch->free_stack_pos], vm_stack, machine_stack);
825 else if (ch->uninitialized_stack_count > 0) {
826 RUBY_DEBUG_LOG(
"uninitialized_stack_count:%d", ch->uninitialized_stack_count);
828 size_t idx = ch->stack_count - ch->uninitialized_stack_count--;
832 char *stack_start = nt_stack_chunk_get_stack_start(ch, idx);
833 size_t vm_stack_area = nt_vm_stack_area(vm);
834 size_t mstack_size = nt_thread_stack_size() - vm_stack_area - MSTACK_PAGE_SIZE;
835 char *mstack_start = stack_start + vm_stack_area + MSTACK_PAGE_SIZE;
837 int mstack_flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE;
838#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
839 mstack_flags |= MAP_STACK;
842 if (mprotect(stack_start, vm_stack_area, PROT_READ | PROT_WRITE) != 0 ||
843 mmap(mstack_start, mstack_size, PROT_READ | PROT_WRITE, mstack_flags, -1, 0) == MAP_FAILED) {
845 ch->uninitialized_stack_count++;
848 nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack);
852 nt_free_stack_chunks = ch->prev_free_chunk;
853 ch->prev_free_chunk = NULL;
854 ch->on_free_list =
false;
859 struct nt_stack_chunk_header *p = nt_alloc_thread_stack_chunk();
864 nt_free_stack_chunks = nt_stack_chunks = p;
875nt_madvise_free_or_dontneed(
void *addr,
size_t len)
888#if defined(MADV_FREE)
889 int r = madvise(addr,
len, MADV_FREE);
893#if defined(MADV_DONTNEED)
894 madvise(addr,
len, MADV_DONTNEED);
899nt_free_stack(
void *mstack)
905 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(GET_VM(), mstack);
906 struct nt_stack_chunk_header *ch = msf->ch;
907 int idx = (int)msf->index;
908 void *stack = nt_stack_chunk_get_stack_start(ch, idx);
910 RUBY_DEBUG_LOG(
"stack:%p mstack:%p ch:%p index:%d", stack, mstack, ch, idx);
912 if (!ch->on_free_list) {
913 ch->on_free_list =
true;
914 ch->prev_free_chunk = nt_free_stack_chunks;
915 nt_free_stack_chunks = ch;
917 ch->free_stack[ch->free_stack_pos++] = idx;
920 nt_madvise_free_or_dontneed(stack, nt_thread_stack_size());
926mn_threads_enabled_p(
void)
928 return mn_threads_mode >= 0;
932native_thread_check_and_create_shared(
rb_vm_t *vm)
934 bool need_to_make =
false;
936 if (!mn_threads_enabled_p())
return 0;
938 ractor_sched_lock(vm, NULL);
940 unsigned int schedulable_ractor_cnt = vm->ractor.cnt;
943 if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads)
944 schedulable_ractor_cnt--;
948 while (((
int)snt_cnt < MINIMUM_SNT) ||
949 (snt_cnt < schedulable_ractor_cnt &&
950 snt_cnt < vm->ractor.sched.max_cpu)) {
952 if (prev == snt_cnt) {
960 RUBY_DEBUG_LOG(
"added snt:%u dnt:%u ractor_cnt:%u grq_cnt:%u",
961 vm->ractor.sched.snt_cnt,
962 vm->ractor.sched.dnt_cnt,
964 vm->ractor.sched.grq_cnt);
967 RUBY_DEBUG_LOG(
"snt:%d ractor_cnt:%d", (
int)vm->ractor.sched.snt_cnt, (
int)vm->ractor.cnt);
970 ractor_sched_unlock(vm, NULL);
975 int err = native_thread_create0(nt);
979 ractor_sched_lock(vm, NULL);
981 ractor_sched_unlock(vm, NULL);
982 native_thread_destroy(nt);
997 struct rb_thread_context *tctx = (
struct rb_thread_context *)th->sched.context;
1000 bool last = (th->invoke_type == thread_invoke_type_ractor_proc);
1001 bool is_dnt = th_has_dedicated_nt(th);
1013 bool wake_mn =
false;
1021 VM_ASSERT(sched->running == th);
1024 rb_ractor_living_threads_remove(r, th);
1027 thread_sched_lock(sched, th);
1031 thread_sched_to_dead_common(sched, th);
1038 wake_th = is_dnt ? NULL : sched->running;
1043 wake_mn = (wake_th != NULL && wake_th->nt == NULL);
1046 native_thread_assign(NULL, th);
1047 th->sched.context = NULL;
1055 rb_ractor_set_current_ec(r, NULL);
1061 thread_sched_unlock(sched, th);
1063 VM_ASSERT(sched->running == NULL);
1064 VM_ASSERT(wake_th == NULL);
1067 rb_ractor_living_threads_remove(r, th);
1068 rb_current_ec_set(NULL);
1073 thread_sched_unlock_no_log(sched, th);
1078 thread_sched_lock(sched, NULL);
1079 ractor_sched_enq(vm, r);
1080 thread_sched_unlock(sched, NULL);
1086# define co_start ruby_coroutine_start
1093#ifdef RUBY_ASAN_ENABLED
1094 __sanitizer_finish_switch_fiber(self->fake_stack,
1095 (
const void**)&from->stack_base, &from->stack_size);
1100 VM_ASSERT(th->nt != NULL);
1101 VM_ASSERT(th == sched->running);
1102 VM_ASSERT(sched->lock_owner == NULL);
1106 thread_sched_set_locked(sched, th);
1107 thread_sched_add_running_thread(TH_SCHED(th), th);
1108 thread_sched_unlock(sched, th);
1111 call_thread_start_func_2(th);
1118 struct rb_thread_context *tctx = (
struct rb_thread_context *)self;
1122 tctx->nt->dead_co = &tctx->co;
1123 coroutine_transfer0(&tctx->co, tctx->nt->nt_context,
true);
1125 rb_bug(
"unreachable");
1133 void *vm_stack = NULL, *machine_stack = NULL;
1134 int err = nt_alloc_stack(vm, &vm_stack, &machine_stack);
1135 if (err)
return err;
1137 VM_ASSERT(vm_stack < machine_stack);
1140 size_t vm_stack_words = th->vm->default_params.thread_vm_stack_size/
sizeof(
VALUE);
1141 rb_ec_initialize_vm_stack(th->ec, vm_stack, vm_stack_words);
1144 size_t machine_stack_size = vm->default_params.thread_machine_stack_size -
sizeof(
struct nt_machine_stack_footer);
1145 th->ec->machine.stack_start = (
void *)((uintptr_t)machine_stack + machine_stack_size);
1146 th->ec->machine.stack_maxsize = machine_stack_size;
1147 th->sched.context_stack = machine_stack;
1148 th->sched.context_stack_size = machine_stack_size;
1150 struct rb_thread_context *tctx = ruby_xmalloc(
sizeof(
struct rb_thread_context));
1151 tctx->stack = machine_stack;
1154 th->sched.context = &tctx->co;
1155 coroutine_initialize(&tctx->co, co_start, machine_stack, machine_stack_size);
1156 tctx->co.argument = th;
1158 RUBY_DEBUG_LOG(
"th:%u vm_stack:%p machine_stack:%p", rb_th_serial(th), vm_stack, machine_stack);
1163 int create_err = native_thread_check_and_create_shared(vm);
1164 if (create_err)
return create_err;
1166 thread_sched_to_ready(TH_SCHED(th), th);
1171#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1179#define FDMAP_CHUNK_BITS 10
1180#define FDMAP_CHUNK_SIZE (1u << FDMAP_CHUNK_BITS)
1181#define FDMAP_CHUNK_MASK (FDMAP_CHUNK_SIZE - 1)
1186fd_waiters_lookup(
int fd,
bool create)
1188 if (fd < 0)
return NULL;
1190 unsigned int ci = (
unsigned int)fd >> FDMAP_CHUNK_BITS;
1191 if (ci >= FDMAP_MAX_CHUNKS)
return NULL;
1195 if (chunk == NULL) {
1196 if (!create)
return NULL;
1198 chunk = calloc(FDMAP_CHUNK_SIZE,
sizeof(*chunk));
1199 if (chunk == NULL) rb_bug(
"fd_waiters_lookup: calloc failed");
1201 for (
unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) {
1202 ccan_list_head_init(&chunk[i].waiters);
1212 return &chunk[(
unsigned int)fd & FDMAP_CHUNK_MASK];
1222 ccan_list_for_each(&e->waiters, w, fd_node) {
1223 want |= (uint32_t)(w->flags & FD_WAIT_IO_MASK);
1230static inline uint64_t
1231fd_event_tag(
int fd, uint32_t generation)
1233 return ((uint64_t)generation << 32) | (uint32_t)fd;
1236#define FD_EVENT_TAG_FD(tag) ((int)((tag) & 0xffffffffu))
1237#define FD_EVENT_TAG_GEN(tag) ((uint32_t)((tag) >> 32))
1244fd_waiters_arm(
int fd,
struct rb_fd_waiters *e, uint32_t want,
bool consumed)
1248 if (want == e->armed_flags && !consumed)
return true;
1251 struct kevent ke[2];
1253 uint32_t add = want & ~e->armed_flags;
1254 uint32_t del = e->armed_flags & ~want;
1255 void *tag = (
void *)(uintptr_t)fd_event_tag(fd, e->generation);
1257 if (del & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); n++; }
1258 if (del & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); n++; }
1260 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1264 rb_bug(
"fd_waiters_arm/kevent delete failed (fd:%d errno:%d)", fd,
errno);
1269 if (add & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_ADD, 0, 0, tag); n++; }
1270 if (add & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_ADD, 0, 0, tag); n++; }
1272 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1279 rb_bug(
"fd_waiters_arm/kevent add failed (fd:%d errno:%d)", fd,
errno);
1282#elif HAVE_SYS_EPOLL_H
1291 if (!consumed && e->registered) {
1292 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
1299 perror(
"epoll_ctl");
1300 rb_bug(
"fd_waiters_arm/epoll_ctl disarm failed (fd:%d errno:%d)", fd,
errno);
1303 e->registered =
false;
1311 uint32_t epoll_events = EPOLLONESHOT;
1312 if (want & thread_sched_waiting_io_read) epoll_events |= EPOLLIN;
1313 if (want & thread_sched_waiting_io_write) epoll_events |= EPOLLOUT;
1315 struct epoll_event event = {
1316 .events = epoll_events,
1317 .data = { .u64 = fd_event_tag(fd, e->generation) },
1320 int op = e->registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
1322 if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) {
1326 if (op == EPOLL_CTL_MOD &&
1327 epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) {
1330 e->registered =
false;
1334 if (op == EPOLL_CTL_ADD &&
1335 epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &event) == 0) {
1342 e->registered =
false;
1345 perror(
"epoll_ctl");
1346 rb_bug(
"fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op,
errno);
1349 e->registered =
true;
1351# error "neither kqueue nor epoll"
1354 e->armed_flags = want;
1359fd_ready_nonblock(
int fd,
short events)
1361 struct pollfd pfd = {
1368 return poll(&pfd, 1, 0) > 0 && !(pfd.revents & POLLNVAL);
1372verify_waiting_list(
void)
1374#if VM_CHECK_MODE > 0
1377 for (
int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
1378 const struct timer_wheel_level *lv = &timer_th.wheel[lvl];
1380 for (
int slot = 0; slot < TIMER_WHEEL_SLOTS; slot++) {
1381 bool occupied = (lv->occupied >> slot) & 1;
1382 VM_ASSERT(occupied == !ccan_list_empty(&lv->slots[slot]));
1384 ccan_list_for_each(&lv->slots[slot], w, node) {
1386 if (!(w->flags & FD_WAIT_IO_MASK)) {
1387 VM_ASSERT(w->flags & thread_sched_waiting_timeout);
1388 VM_ASSERT(w->data.timeout != 0);
1390 VM_ASSERT(w->wheel_lvl == lvl);
1391 VM_ASSERT(w->wheel_slot == slot);
1408static enum thread_sched_waiting_flag
1409kqueue_translate_filter_to_flags(int16_t filter)
1413 return thread_sched_waiting_io_read;
1415 return thread_sched_waiting_io_write;
1417 return thread_sched_waiting_timeout;
1419 rb_bug(
"kevent filter:%d not supported", filter);
1426 struct timespec calculated_timeout;
1428 int timeout_ms = timer_thread_set_timeout(vm);
1430 if (timeout_ms > 0) {
1431 calculated_timeout.tv_sec = timeout_ms / 1000;
1432 calculated_timeout.tv_nsec = (timeout_ms % 1000) * 1000000;
1433 timeout = &calculated_timeout;
1435 else if (timeout_ms == 0) {
1438 memset(&calculated_timeout, 0,
sizeof(
struct timespec));
1439 timeout = &calculated_timeout;
1442 return kevent(timer_th.event_fd, NULL, 0, timer_th.finished_events, KQUEUE_EVENTS_MAX, timeout);
1448 if ((timer_th.event_fd = kqueue()) == -1) rb_bug(
"kqueue creation failed (errno:%d)",
errno);
1449 int flags = fcntl(timer_th.event_fd, F_GETFD);
1451 rb_bug(
"kqueue GETFD failed (errno:%d)",
errno);
1454 flags |= FD_CLOEXEC;
1455 if (fcntl(timer_th.event_fd, F_SETFD, flags) == -1) {
1456 rb_bug(
"kqueue SETFD failed (errno:%d)",
errno);
1463static enum timer_thread_register_result
1464timer_thread_register_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial)
1466 RUBY_DEBUG_LOG(
"th:%u fd:%d flag:%d rel:%lu", rb_th_serial(th), fd, flags, rel ? (
unsigned long)*rel : 0);
1468 VM_ASSERT(th == NULL || TH_SCHED(th)->running == th);
1469 VM_ASSERT(flags != 0);
1471 rb_hrtime_t abs = 0;
1475 flags |= thread_sched_waiting_timeout;
1478 return timer_thread_already_ready;
1482 if (flags & thread_sched_waiting_timeout) {
1483 VM_ASSERT(rel != NULL);
1484 abs = rb_hrtime_add(rb_hrtime_now(), *rel);
1487 if (flags & thread_sched_waiting_io_read) {
1488 if (!(flags & thread_sched_waiting_io_force) && fd_ready_nonblock(fd, POLLIN)) {
1489 RUBY_DEBUG_LOG(
"fd readable");
1490 return timer_thread_already_ready;
1495 if (flags & thread_sched_waiting_io_write) {
1496 if (!(flags & thread_sched_waiting_io_force) && fd_ready_nonblock(fd, POLLOUT)) {
1497 RUBY_DEBUG_LOG(
"fd writable");
1498 return timer_thread_already_ready;
1503 if (flags & FD_WAIT_IO_MASK) {
1509 fd_shard_unlock(fd);
1510 return timer_thread_unavailable;
1515 if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK),
false)) {
1516 fd_shard_unlock(fd);
1517 return timer_thread_unavailable;
1521 ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node);
1523 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1524 th->sched.waiting_reason.flags = flags;
1525 th->sched.waiting_reason.data.timeout = abs;
1526 th->sched.waiting_reason.data.fd = fd;
1527 th->sched.waiting_reason.data.result = 0;
1528 th->sched.waiting_reason.data.event_serial = event_serial;
1531 RUBY_DEBUG_LOG(
"abs:%lu", (
unsigned long)abs);
1532 VM_ASSERT(flags & thread_sched_waiting_timeout);
1536 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1537 timer_wheel_insert(&th->sched.waiting_reason);
1538 verify_waiting_list();
1539 if (timer_th.next_expiry < prev_expiry) {
1541 timer_thread_wakeup_force();
1547 RUBY_DEBUG_LOG(
"armed fd:%d want:%u", fd, e->armed_flags);
1549 fd_shard_unlock(fd);
1553 VM_ASSERT(abs != 0 && (flags & thread_sched_waiting_timeout));
1557 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1558 th->sched.waiting_reason.flags = flags;
1559 th->sched.waiting_reason.data.timeout = abs;
1560 th->sched.waiting_reason.data.fd = fd;
1561 th->sched.waiting_reason.data.result = 0;
1562 th->sched.waiting_reason.data.event_serial = event_serial;
1564 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1565 timer_wheel_insert(&th->sched.waiting_reason);
1566 verify_waiting_list();
1567 if (timer_th.next_expiry < prev_expiry) {
1568 timer_thread_wakeup_force();
1574 VM_ASSERT(abs == 0);
1577 return timer_thread_registered;
1583timer_thread_unregister_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags)
1585 if (!(th->sched.waiting_reason.flags & FD_WAIT_IO_MASK)) {
1589 RUBY_DEBUG_LOG(
"th:%u fd:%d", rb_th_serial(th), fd);
1591 ccan_list_del_init(&th->sched.waiting_reason.fd_node);
1595 fd_waiters_arm(fd, e, fd_waiters_union(e),
false);
1602timer_thread_arm_comm_pipe(
void)
1604 int fd = timer_th.comm_fds[0];
1608 EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, (
void *)(uintptr_t)fd_event_tag(fd, 0));
1609 if (kevent(timer_th.event_fd, &ke, 1, NULL, 0, NULL) == -1) {
1610 rb_bug(
"timer_thread_arm_comm_pipe/kevent failed (errno:%d)",
errno);
1612#elif HAVE_SYS_EPOLL_H
1613 struct epoll_event event = {
1615 .data = { .u64 = fd_event_tag(fd, 0) },
1617 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
1618 rb_bug(
"timer_thread_arm_comm_pipe/epoll_ctl failed (errno:%d)",
errno);
1621# error "neither kqueue nor epoll"
1626timer_thread_setup_mn(
void)
1630 RUBY_DEBUG_LOG(
"kqueue_fd:%d", timer_th.event_fd);
1632 if ((timer_th.event_fd = epoll_create1(EPOLL_CLOEXEC)) == -1) rb_bug(
"epoll_create (errno:%d)",
errno);
1633 RUBY_DEBUG_LOG(
"epoll_fd:%d", timer_th.event_fd);
1635 RUBY_DEBUG_LOG(
"comm_fds:%d/%d", timer_th.comm_fds[0], timer_th.comm_fds[1]);
1637 timer_thread_arm_comm_pipe();
1644 int r = kqueue_wait(vm);
1646 int r = epoll_wait(timer_th.event_fd, timer_th.finished_events, EPOLL_EVENTS_MAX, timer_thread_set_timeout(vm));
1652#define FD_WAKE_BATCH 16
1659timer_thread_wake_fd_waiters(
int fd, uint32_t generation, uint32_t wake_flags,
int result)
1661 struct timer_wake batch[FD_WAKE_BATCH];
1663 if (wake_flags == 0)
return;
1675 if (e != NULL && e->generation == generation) {
1678 ccan_list_for_each_safe(&e->waiters, w, nxt, fd_node) {
1679 if (!(w->flags & wake_flags))
continue;
1681 if (n == FD_WAKE_BATCH) {
1686 ccan_list_del_init(&w->fd_node);
1688 if (w->flags & thread_sched_waiting_timeout) {
1699 batch[n].th = thread_sched_waiting_thread(w);
1700 batch[n].serial = w->data.event_serial;
1701 timer_wake_pending_inc(batch[n].th);
1704 w->flags = thread_sched_waiting_none;
1706 w->data.result = result;
1711 fd_waiters_arm(fd, e, fd_waiters_union(e),
true);
1714 fd_shard_unlock(fd);
1716 for (
int i = 0; i < n; i++) {
1717 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
1719 timer_wake_pending_clear(batch, n);
1740timer_thread_polling(
rb_vm_t *vm)
1742 int r = event_wait(vm);
1744 RUBY_DEBUG_LOG(
"r:%d errno:%d", r,
errno);
1748 RUBY_DEBUG_LOG(
"timeout%s",
"");
1750 ractor_sched_lock(vm, NULL);
1753 timer_thread_check_timeslice(vm);
1756 if (vm->ractor.sched.grq_cnt > 0) {
1757 RUBY_DEBUG_LOG(
"GRQ cnt: %u", vm->ractor.sched.grq_cnt);
1761 ractor_sched_unlock(vm, NULL);
1764 native_thread_check_and_create_shared(vm);
1774 perror(
"event_wait");
1775 rb_bug(
"event_wait errno:%d",
errno);
1780 RUBY_DEBUG_LOG(
"%d event(s)", r);
1783 for (
int i=0; i<r; i++) {
1784 uint64_t tag = (uint64_t)(uintptr_t)timer_th.finished_events[i].udata;
1785 int fd = (int)timer_th.finished_events[i].ident;
1786 int16_t filter = timer_th.finished_events[i].filter;
1788 if (fd == timer_th.comm_fds[0]) {
1789 RUBY_DEBUG_LOG(
"comm from fd:%d", timer_th.comm_fds[1]);
1790 consume_communication_pipe(timer_th.comm_fds[0]);
1794 uint32_t wake_flags = kqueue_translate_filter_to_flags(filter) & FD_WAIT_IO_MASK;
1795 if (timer_th.finished_events[i].flags & (EV_EOF | EV_ERROR)) {
1796 wake_flags = FD_WAIT_IO_MASK;
1799 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, filter);
1801#elif HAVE_SYS_EPOLL_H
1802 for (
int i=0; i<r; i++) {
1803 uint64_t tag = timer_th.finished_events[i].data.u64;
1804 int fd = FD_EVENT_TAG_FD(tag);
1805 uint32_t events = timer_th.finished_events[i].events;
1807 if (fd == timer_th.comm_fds[0]) {
1808 RUBY_DEBUG_LOG(
"comm from fd:%d", timer_th.comm_fds[1]);
1809 consume_communication_pipe(timer_th.comm_fds[0]);
1813 RUBY_DEBUG_LOG(
"io event. fd:%d event:%s%s%s%s%s%s", fd,
1814 (events & EPOLLIN) ?
"in/" :
"",
1815 (events & EPOLLOUT) ?
"out/" :
"",
1816 (events & EPOLLRDHUP) ?
"RDHUP/" :
"",
1817 (events & EPOLLPRI) ?
"pri/" :
"",
1818 (events & EPOLLERR) ?
"err/" :
"",
1819 (events & EPOLLHUP) ?
"hup/" :
"");
1821 uint32_t wake_flags = 0;
1822 if (events & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) wake_flags |= thread_sched_waiting_io_read;
1823 if (events & EPOLLOUT) wake_flags |= thread_sched_waiting_io_write;
1825 if (events & (EPOLLERR | EPOLLHUP)) wake_flags |= FD_WAIT_IO_MASK;
1827 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, (
int)events);
1830# error "neither kqueue nor epoll"
1840timer_thread_setup_mn(
void)
1846timer_thread_polling(
rb_vm_t *vm)
1848 int timeout = timer_thread_set_timeout(vm);
1850 struct pollfd pfd = {
1851 .fd = timer_th.comm_fds[0],
1855 int r = poll(&pfd, 1, timeout);
1859 ractor_sched_lock(vm, NULL);
1862 timer_thread_check_timeslice(vm);
1864 ractor_sched_unlock(vm, NULL);
1874 rb_bug(
"poll errno:%d",
errno);
1879 consume_communication_pipe(timer_th.comm_fds[0]);
1883 rb_bug(
"unreachbale");
1890 rb_bug(
"unreachable");
1893static enum thread_sched_wait_result
1894thread_sched_wait_events(
struct rb_thread_sched *sched,
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
1896 rb_bug(
"unreachable");
1902ractor_sched_timeout_arm(
rb_thread_t *th,
const rb_hrtime_t *rel)
1904 rb_bug(
"unreachable");
1910 rb_bug(
"unreachable");
1914timer_wheel_timeout(
int timeout)
1926timer_thread_check_timeout(
rb_vm_t *vm)
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
#define RUBY_ATOMIC_PTR_LOAD(var)
Identical to RUBY_ATOMIC_LOAD, except it expects its arguments are void*.
#define RUBY_ATOMIC_INC(var)
Atomically increments the value pointed by var.
#define RUBY_ATOMIC_PTR_CAS(var, oldval, newval)
Identical to RUBY_ATOMIC_CAS, except it expects its arguments are void*.
#define RUBY_ATOMIC_CAS(var, oldval, newval)
Atomic compare-and-swap.
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
#define RUBY_ATOMIC_DEC(var)
Atomically decrements the value pointed by var.
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
#define RUBY_ATOMIC_PTR_SET(var, val)
Identical to RUBY_ATOMIC_SET, except it expects its arguments are void*.
int len
Length of the buffer.
#define RUBY_INTERNAL_THREAD_EVENT_RESUMED
Triggered when a thread successfully acquired the GVL.
#define RUBY_INTERNAL_THREAD_EVENT_SUSPENDED
Triggered when a thread released the GVL.
#define RBIMPL_ATTR_MAYBE_UNUSED()
Wraps (or simulates) [[maybe_unused]]
#define errno
Ractor-aware version of errno.
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_cond_broadcast(rb_nativethread_cond_t *cond)
Signals a condition variable.
void rb_native_mutex_initialize(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_initialize.
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_unlock.
void rb_native_cond_signal(rb_nativethread_cond_t *cond)
Signals a condition variable.
void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex)
Waits for the passed condition variable to be signalled.
uintptr_t VALUE
Type that represents a Ruby object.