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;
612 return timedout ? thread_sched_wait_timeout : thread_sched_wait_event;
618get_sysconf_page_size(
void)
620 static long page_size = 0;
622 if (UNLIKELY(page_size == 0)) {
623 page_size = sysconf(_SC_PAGESIZE);
624 VM_ASSERT(page_size < INT_MAX);
626 return (
int)page_size;
629#define MSTACK_CHUNK_SIZE (512 * 1024 * 1024)
630#define MSTACK_PAGE_SIZE get_sysconf_page_size()
631#define MSTACK_CHUNK_PAGE_NUM (MSTACK_CHUNK_SIZE / MSTACK_PAGE_SIZE - 1)
644static struct nt_stack_chunk_header {
645 struct nt_stack_chunk_header *prev_chunk;
646 struct nt_stack_chunk_header *prev_free_chunk;
652 uint16_t stack_count;
653 uint16_t uninitialized_stack_count;
655 uint16_t free_stack_pos;
656 uint16_t free_stack[];
657} *nt_stack_chunks = NULL,
658 *nt_free_stack_chunks = NULL;
660struct nt_machine_stack_footer {
661 struct nt_stack_chunk_header *ch;
665static rb_nativethread_lock_t nt_machine_stack_lock = RB_NATIVETHREAD_LOCK_INIT;
669nt_machine_stack_atfork(
void)
681nt_vm_stack_area(
const rb_vm_t *vm)
683 return (
size_t)roomof(vm->default_params.thread_vm_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
687nt_machine_stack_area(
const rb_vm_t *vm)
689 return (
size_t)roomof(vm->default_params.thread_machine_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
694nt_thread_stack_size(
void)
697 if (LIKELY(msz > 0))
return msz;
700 msz = nt_vm_stack_area(vm) + MSTACK_PAGE_SIZE + nt_machine_stack_area(vm);
704static struct nt_stack_chunk_header *
705nt_alloc_thread_stack_chunk(
void)
707 const char *m = (
void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
708 if (m == MAP_FAILED) {
712 ruby_annotate_mmap(m, MSTACK_CHUNK_SIZE,
"Ruby:nt_alloc_thread_stack_chunk");
714 size_t msz = nt_thread_stack_size();
715 int header_page_cnt = 1;
716 int stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
717 int ch_size =
sizeof(
struct nt_stack_chunk_header) + sizeof(uint16_t) * stack_count;
719 if (ch_size > MSTACK_PAGE_SIZE * header_page_cnt) {
720 header_page_cnt = (ch_size + MSTACK_PAGE_SIZE - 1) / MSTACK_PAGE_SIZE;
721 stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
724 VM_ASSERT(stack_count <= UINT16_MAX);
727 if (mprotect((
void *)m, (
size_t)header_page_cnt * MSTACK_PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) {
728 munmap((
void *)m, MSTACK_CHUNK_SIZE);
732 struct nt_stack_chunk_header *ch = (
struct nt_stack_chunk_header *)m;
734 ch->start_page = header_page_cnt;
735 ch->prev_chunk = nt_stack_chunks;
736 ch->prev_free_chunk = nt_free_stack_chunks;
737 ch->on_free_list =
true;
738 ch->uninitialized_stack_count = ch->stack_count = (uint16_t)stack_count;
739 ch->free_stack_pos = 0;
741 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);
747nt_stack_chunk_get_stack_start(
struct nt_stack_chunk_header *ch,
size_t idx)
749 const char *m = (
char *)ch;
750 return (
void *)(m + ch->start_page * MSTACK_PAGE_SIZE + idx * nt_thread_stack_size());
753static struct nt_machine_stack_footer *
754nt_stack_chunk_get_msf(
const rb_vm_t *vm,
const char *mstack)
757 const size_t msz = vm->default_params.thread_machine_stack_size;
758 return (
struct nt_machine_stack_footer *)&mstack[msz -
sizeof(
struct nt_machine_stack_footer)];
762nt_stack_chunk_get_stack(
const rb_vm_t *vm,
struct nt_stack_chunk_header *ch,
size_t idx,
void **vm_stack,
void **machine_stack)
767 const char *vstack, *mstack;
768 const char *guard_page;
769 vstack = nt_stack_chunk_get_stack_start(ch, idx);
770 guard_page = vstack + nt_vm_stack_area(vm);
771 mstack = guard_page + MSTACK_PAGE_SIZE;
773 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(vm, mstack);
778 RUBY_DEBUG_LOG(
"msf:%p vstack:%p-%p guard_page:%p-%p mstack:%p-%p", msf,
779 vstack, (
void *)(guard_page-1),
780 guard_page, (
void *)(mstack-1),
781 mstack, (
void *)(msf));
784 *vm_stack = (
void *)vstack;
785 *machine_stack = (
void *)mstack;
790nt_stack_chunk_dump(
void)
792 struct nt_stack_chunk_header *ch;
795 fprintf(stderr,
"** nt_stack_chunks\n");
796 ch = nt_stack_chunks;
797 for (i=0; ch; i++, ch = ch->prev_chunk) {
798 fprintf(stderr,
"%d %p free_pos:%d\n", i, (
void *)ch, (
int)ch->free_stack_pos);
801 fprintf(stderr,
"** nt_free_stack_chunks\n");
802 ch = nt_free_stack_chunks;
803 for (i=0; ch; i++, ch = ch->prev_free_chunk) {
804 fprintf(stderr,
"%d %p free_pos:%d\n", i, (
void *)ch, (
int)ch->free_stack_pos);
809nt_alloc_stack(
rb_vm_t *vm,
void **vm_stack,
void **machine_stack)
816 if (nt_free_stack_chunks) {
817 struct nt_stack_chunk_header *ch = nt_free_stack_chunks;
818 if (ch->free_stack_pos > 0) {
819 RUBY_DEBUG_LOG(
"free_stack_pos:%d", ch->free_stack_pos);
820 nt_stack_chunk_get_stack(vm, ch, ch->free_stack[--ch->free_stack_pos], vm_stack, machine_stack);
822 else if (ch->uninitialized_stack_count > 0) {
823 RUBY_DEBUG_LOG(
"uninitialized_stack_count:%d", ch->uninitialized_stack_count);
825 size_t idx = ch->stack_count - ch->uninitialized_stack_count--;
829 char *stack_start = nt_stack_chunk_get_stack_start(ch, idx);
830 size_t vm_stack_area = nt_vm_stack_area(vm);
831 size_t mstack_size = nt_thread_stack_size() - vm_stack_area - MSTACK_PAGE_SIZE;
832 char *mstack_start = stack_start + vm_stack_area + MSTACK_PAGE_SIZE;
834 int mstack_flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE;
835#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
836 mstack_flags |= MAP_STACK;
839 if (mprotect(stack_start, vm_stack_area, PROT_READ | PROT_WRITE) != 0 ||
840 mmap(mstack_start, mstack_size, PROT_READ | PROT_WRITE, mstack_flags, -1, 0) == MAP_FAILED) {
842 ch->uninitialized_stack_count++;
845 nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack);
849 nt_free_stack_chunks = ch->prev_free_chunk;
850 ch->prev_free_chunk = NULL;
851 ch->on_free_list =
false;
856 struct nt_stack_chunk_header *p = nt_alloc_thread_stack_chunk();
861 nt_free_stack_chunks = nt_stack_chunks = p;
872nt_madvise_free_or_dontneed(
void *addr,
size_t len)
885#if defined(MADV_FREE)
886 int r = madvise(addr,
len, MADV_FREE);
890#if defined(MADV_DONTNEED)
891 madvise(addr,
len, MADV_DONTNEED);
896nt_free_stack(
void *mstack)
902 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(GET_VM(), mstack);
903 struct nt_stack_chunk_header *ch = msf->ch;
904 int idx = (int)msf->index;
905 void *stack = nt_stack_chunk_get_stack_start(ch, idx);
907 RUBY_DEBUG_LOG(
"stack:%p mstack:%p ch:%p index:%d", stack, mstack, ch, idx);
909 if (!ch->on_free_list) {
910 ch->on_free_list =
true;
911 ch->prev_free_chunk = nt_free_stack_chunks;
912 nt_free_stack_chunks = ch;
914 ch->free_stack[ch->free_stack_pos++] = idx;
917 nt_madvise_free_or_dontneed(stack, nt_thread_stack_size());
924native_thread_check_and_create_shared(
rb_vm_t *vm)
926 bool need_to_make =
false;
928 ractor_sched_lock(vm, NULL);
930 unsigned int schedulable_ractor_cnt = vm->ractor.cnt;
933 if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads)
934 schedulable_ractor_cnt--;
938 while (((
int)snt_cnt < MINIMUM_SNT) ||
939 (snt_cnt < schedulable_ractor_cnt &&
940 snt_cnt < vm->ractor.sched.max_cpu)) {
942 if (prev == snt_cnt) {
950 RUBY_DEBUG_LOG(
"added snt:%u dnt:%u ractor_cnt:%u grq_cnt:%u",
951 vm->ractor.sched.snt_cnt,
952 vm->ractor.sched.dnt_cnt,
954 vm->ractor.sched.grq_cnt);
957 RUBY_DEBUG_LOG(
"snt:%d ractor_cnt:%d", (
int)vm->ractor.sched.snt_cnt, (
int)vm->ractor.cnt);
960 ractor_sched_unlock(vm, NULL);
965 int err = native_thread_create0(nt);
969 ractor_sched_lock(vm, NULL);
971 ractor_sched_unlock(vm, NULL);
972 native_thread_destroy(nt);
987 struct rb_thread_context *tctx = (
struct rb_thread_context *)th->sched.context;
990 bool last = (th->invoke_type == thread_invoke_type_ractor_proc);
991 bool is_dnt = th_has_dedicated_nt(th);
1003 bool wake_mn =
false;
1011 VM_ASSERT(sched->running == th);
1014 rb_ractor_living_threads_remove(r, th);
1017 thread_sched_lock(sched, th);
1021 thread_sched_to_dead_common(sched, th);
1028 wake_th = is_dnt ? NULL : sched->running;
1033 wake_mn = (wake_th != NULL && wake_th->nt == NULL);
1036 native_thread_assign(NULL, th);
1037 th->sched.context = NULL;
1045 rb_ractor_set_current_ec(r, NULL);
1051 thread_sched_unlock(sched, th);
1053 VM_ASSERT(sched->running == NULL);
1054 VM_ASSERT(wake_th == NULL);
1057 rb_ractor_living_threads_remove(r, th);
1058 rb_current_ec_set(NULL);
1063 thread_sched_unlock_no_log(sched, th);
1068 thread_sched_lock(sched, NULL);
1069 ractor_sched_enq(vm, r);
1070 thread_sched_unlock(sched, NULL);
1076# define co_start ruby_coroutine_start
1083#ifdef RUBY_ASAN_ENABLED
1084 __sanitizer_finish_switch_fiber(self->fake_stack,
1085 (
const void**)&from->stack_base, &from->stack_size);
1090 VM_ASSERT(th->nt != NULL);
1091 VM_ASSERT(th == sched->running);
1092 VM_ASSERT(sched->lock_owner == NULL);
1096 thread_sched_set_locked(sched, th);
1097 thread_sched_add_running_thread(TH_SCHED(th), th);
1098 thread_sched_unlock(sched, th);
1101 call_thread_start_func_2(th);
1108 struct rb_thread_context *tctx = (
struct rb_thread_context *)self;
1112 tctx->nt->dead_co = &tctx->co;
1113 coroutine_transfer0(&tctx->co, tctx->nt->nt_context,
true);
1115 rb_bug(
"unreachable");
1123 void *vm_stack = NULL, *machine_stack = NULL;
1124 int err = nt_alloc_stack(vm, &vm_stack, &machine_stack);
1125 if (err)
return err;
1127 VM_ASSERT(vm_stack < machine_stack);
1130 size_t vm_stack_words = th->vm->default_params.thread_vm_stack_size/
sizeof(
VALUE);
1131 rb_ec_initialize_vm_stack(th->ec, vm_stack, vm_stack_words);
1134 size_t machine_stack_size = vm->default_params.thread_machine_stack_size -
sizeof(
struct nt_machine_stack_footer);
1135 th->ec->machine.stack_start = (
void *)((uintptr_t)machine_stack + machine_stack_size);
1136 th->ec->machine.stack_maxsize = machine_stack_size;
1137 th->sched.context_stack = machine_stack;
1138 th->sched.context_stack_size = machine_stack_size;
1140 struct rb_thread_context *tctx = ruby_xmalloc(
sizeof(
struct rb_thread_context));
1141 tctx->stack = machine_stack;
1144 th->sched.context = &tctx->co;
1145 coroutine_initialize(&tctx->co, co_start, machine_stack, machine_stack_size);
1146 tctx->co.argument = th;
1148 RUBY_DEBUG_LOG(
"th:%u vm_stack:%p machine_stack:%p", rb_th_serial(th), vm_stack, machine_stack);
1153 int create_err = native_thread_check_and_create_shared(vm);
1154 if (create_err)
return create_err;
1156 thread_sched_to_ready(TH_SCHED(th), th);
1161#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1169#define FDMAP_CHUNK_BITS 10
1170#define FDMAP_CHUNK_SIZE (1u << FDMAP_CHUNK_BITS)
1171#define FDMAP_CHUNK_MASK (FDMAP_CHUNK_SIZE - 1)
1176fd_waiters_lookup(
int fd,
bool create)
1178 if (fd < 0)
return NULL;
1180 unsigned int ci = (
unsigned int)fd >> FDMAP_CHUNK_BITS;
1181 if (ci >= FDMAP_MAX_CHUNKS)
return NULL;
1185 if (chunk == NULL) {
1186 if (!create)
return NULL;
1188 chunk = calloc(FDMAP_CHUNK_SIZE,
sizeof(*chunk));
1189 if (chunk == NULL) rb_bug(
"fd_waiters_lookup: calloc failed");
1191 for (
unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) {
1192 ccan_list_head_init(&chunk[i].waiters);
1202 return &chunk[(
unsigned int)fd & FDMAP_CHUNK_MASK];
1212 ccan_list_for_each(&e->waiters, w, fd_node) {
1213 want |= (uint32_t)(w->flags & FD_WAIT_IO_MASK);
1220static inline uint64_t
1221fd_event_tag(
int fd, uint32_t generation)
1223 return ((uint64_t)generation << 32) | (uint32_t)fd;
1226#define FD_EVENT_TAG_FD(tag) ((int)((tag) & 0xffffffffu))
1227#define FD_EVENT_TAG_GEN(tag) ((uint32_t)((tag) >> 32))
1233fd_waiters_arm(
int fd,
struct rb_fd_waiters *e, uint32_t want)
1235 if (want == e->armed_flags)
return true;
1238 struct kevent ke[2];
1240 uint32_t add = want & ~e->armed_flags;
1241 uint32_t del = e->armed_flags & ~want;
1242 void *tag = (
void *)(uintptr_t)fd_event_tag(fd, e->generation);
1244 if (del & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); n++; }
1245 if (del & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); n++; }
1247 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1251 rb_bug(
"fd_waiters_arm/kevent delete failed (fd:%d errno:%d)", fd,
errno);
1256 if (add & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_ADD, 0, 0, tag); n++; }
1257 if (add & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_ADD, 0, 0, tag); n++; }
1259 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1266 rb_bug(
"fd_waiters_arm/kevent add failed (fd:%d errno:%d)", fd,
errno);
1269#elif HAVE_SYS_EPOLL_H
1271 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
1278 perror(
"epoll_ctl");
1279 rb_bug(
"fd_waiters_arm/epoll_ctl del failed (fd:%d errno:%d)", fd,
errno);
1288 uint32_t epoll_events = 0;
1289 if (want & thread_sched_waiting_io_read) epoll_events |= EPOLLIN;
1290 if (want & thread_sched_waiting_io_write) epoll_events |= EPOLLOUT;
1292 struct epoll_event event = {
1293 .events = epoll_events,
1294 .data = { .u64 = fd_event_tag(fd, e->generation) },
1297 int op = e->armed_flags ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
1299 if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) {
1303 if (op == EPOLL_CTL_MOD &&
1304 epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) {
1310 if (op == EPOLL_CTL_ADD &&
1311 epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &event) == 0) {
1320 perror(
"epoll_ctl");
1321 rb_bug(
"fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op,
errno);
1325# error "neither kqueue nor epoll"
1328 e->armed_flags = want;
1333fd_readable_nonblock(
int fd)
1335 struct pollfd pfd = {
1339 return poll(&pfd, 1, 0) != 0;
1343fd_writable_nonblock(
int fd)
1345 struct pollfd pfd = {
1349 return poll(&pfd, 1, 0) != 0;
1353verify_waiting_list(
void)
1355#if VM_CHECK_MODE > 0
1358 for (
int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
1359 const struct timer_wheel_level *lv = &timer_th.wheel[lvl];
1361 for (
int slot = 0; slot < TIMER_WHEEL_SLOTS; slot++) {
1362 bool occupied = (lv->occupied >> slot) & 1;
1363 VM_ASSERT(occupied == !ccan_list_empty(&lv->slots[slot]));
1365 ccan_list_for_each(&lv->slots[slot], w, node) {
1367 if (!(w->flags & FD_WAIT_IO_MASK)) {
1368 VM_ASSERT(w->flags & thread_sched_waiting_timeout);
1369 VM_ASSERT(w->data.timeout != 0);
1371 VM_ASSERT(w->wheel_lvl == lvl);
1372 VM_ASSERT(w->wheel_slot == slot);
1389static enum thread_sched_waiting_flag
1390kqueue_translate_filter_to_flags(int16_t filter)
1394 return thread_sched_waiting_io_read;
1396 return thread_sched_waiting_io_write;
1398 return thread_sched_waiting_timeout;
1400 rb_bug(
"kevent filter:%d not supported", filter);
1407 struct timespec calculated_timeout;
1409 int timeout_ms = timer_thread_set_timeout(vm);
1411 if (timeout_ms > 0) {
1412 calculated_timeout.tv_sec = timeout_ms / 1000;
1413 calculated_timeout.tv_nsec = (timeout_ms % 1000) * 1000000;
1414 timeout = &calculated_timeout;
1416 else if (timeout_ms == 0) {
1419 memset(&calculated_timeout, 0,
sizeof(
struct timespec));
1420 timeout = &calculated_timeout;
1423 return kevent(timer_th.event_fd, NULL, 0, timer_th.finished_events, KQUEUE_EVENTS_MAX, timeout);
1429 if ((timer_th.event_fd = kqueue()) == -1) rb_bug(
"kqueue creation failed (errno:%d)",
errno);
1430 int flags = fcntl(timer_th.event_fd, F_GETFD);
1432 rb_bug(
"kqueue GETFD failed (errno:%d)",
errno);
1435 flags |= FD_CLOEXEC;
1436 if (fcntl(timer_th.event_fd, F_SETFD, flags) == -1) {
1437 rb_bug(
"kqueue SETFD failed (errno:%d)",
errno);
1444static enum timer_thread_register_result
1445timer_thread_register_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial)
1447 RUBY_DEBUG_LOG(
"th:%u fd:%d flag:%d rel:%lu", rb_th_serial(th), fd, flags, rel ? (
unsigned long)*rel : 0);
1449 VM_ASSERT(th == NULL || TH_SCHED(th)->running == th);
1450 VM_ASSERT(flags != 0);
1452 rb_hrtime_t abs = 0;
1456 flags |= thread_sched_waiting_timeout;
1459 return timer_thread_already_ready;
1463 if (flags & thread_sched_waiting_timeout) {
1464 VM_ASSERT(rel != NULL);
1465 abs = rb_hrtime_add(rb_hrtime_now(), *rel);
1468 if (flags & thread_sched_waiting_io_read) {
1469 if (!(flags & thread_sched_waiting_io_force) && fd_readable_nonblock(fd)) {
1470 RUBY_DEBUG_LOG(
"fd_readable_nonblock");
1471 return timer_thread_already_ready;
1476 if (flags & thread_sched_waiting_io_write) {
1477 if (!(flags & thread_sched_waiting_io_force) && fd_writable_nonblock(fd)) {
1478 RUBY_DEBUG_LOG(
"fd_writable_nonblock");
1479 return timer_thread_already_ready;
1484 if (flags & FD_WAIT_IO_MASK) {
1490 fd_shard_unlock(fd);
1491 return timer_thread_unavailable;
1496 if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK))) {
1497 fd_shard_unlock(fd);
1498 return timer_thread_unavailable;
1502 ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node);
1504 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1505 th->sched.waiting_reason.flags = flags;
1506 th->sched.waiting_reason.data.timeout = abs;
1507 th->sched.waiting_reason.data.fd = fd;
1508 th->sched.waiting_reason.data.result = 0;
1509 th->sched.waiting_reason.data.event_serial = event_serial;
1512 RUBY_DEBUG_LOG(
"abs:%lu", (
unsigned long)abs);
1513 VM_ASSERT(flags & thread_sched_waiting_timeout);
1517 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1518 timer_wheel_insert(&th->sched.waiting_reason);
1519 verify_waiting_list();
1520 if (timer_th.next_expiry < prev_expiry) {
1522 timer_thread_wakeup_force();
1528 RUBY_DEBUG_LOG(
"armed fd:%d want:%u", fd, e->armed_flags);
1530 fd_shard_unlock(fd);
1534 VM_ASSERT(abs != 0 && (flags & thread_sched_waiting_timeout));
1538 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1539 th->sched.waiting_reason.flags = flags;
1540 th->sched.waiting_reason.data.timeout = abs;
1541 th->sched.waiting_reason.data.fd = fd;
1542 th->sched.waiting_reason.data.result = 0;
1543 th->sched.waiting_reason.data.event_serial = event_serial;
1545 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1546 timer_wheel_insert(&th->sched.waiting_reason);
1547 verify_waiting_list();
1548 if (timer_th.next_expiry < prev_expiry) {
1549 timer_thread_wakeup_force();
1555 VM_ASSERT(abs == 0);
1558 return timer_thread_registered;
1564timer_thread_unregister_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags)
1566 if (!(th->sched.waiting_reason.flags & FD_WAIT_IO_MASK)) {
1570 RUBY_DEBUG_LOG(
"th:%u fd:%d", rb_th_serial(th), fd);
1572 ccan_list_del_init(&th->sched.waiting_reason.fd_node);
1576 fd_waiters_arm(fd, e, fd_waiters_union(e));
1583timer_thread_arm_comm_pipe(
void)
1585 int fd = timer_th.comm_fds[0];
1589 EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, (
void *)(uintptr_t)fd_event_tag(fd, 0));
1590 if (kevent(timer_th.event_fd, &ke, 1, NULL, 0, NULL) == -1) {
1591 rb_bug(
"timer_thread_arm_comm_pipe/kevent failed (errno:%d)",
errno);
1593#elif HAVE_SYS_EPOLL_H
1594 struct epoll_event event = {
1596 .data = { .u64 = fd_event_tag(fd, 0) },
1598 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
1599 rb_bug(
"timer_thread_arm_comm_pipe/epoll_ctl failed (errno:%d)",
errno);
1602# error "neither kqueue nor epoll"
1607timer_thread_setup_mn(
void)
1611 RUBY_DEBUG_LOG(
"kqueue_fd:%d", timer_th.event_fd);
1613 if ((timer_th.event_fd = epoll_create1(EPOLL_CLOEXEC)) == -1) rb_bug(
"epoll_create (errno:%d)",
errno);
1614 RUBY_DEBUG_LOG(
"epoll_fd:%d", timer_th.event_fd);
1616 RUBY_DEBUG_LOG(
"comm_fds:%d/%d", timer_th.comm_fds[0], timer_th.comm_fds[1]);
1618 timer_thread_arm_comm_pipe();
1625 int r = kqueue_wait(vm);
1627 int r = epoll_wait(timer_th.event_fd, timer_th.finished_events, EPOLL_EVENTS_MAX, timer_thread_set_timeout(vm));
1633#define FD_WAKE_BATCH 16
1640timer_thread_wake_fd_waiters(
int fd, uint32_t generation, uint32_t wake_flags,
int result)
1642 struct timer_wake batch[FD_WAKE_BATCH];
1644 if (wake_flags == 0)
return;
1656 if (e != NULL && e->generation == generation) {
1659 ccan_list_for_each_safe(&e->waiters, w, nxt, fd_node) {
1660 if (!(w->flags & wake_flags))
continue;
1662 if (n == FD_WAKE_BATCH) {
1667 ccan_list_del_init(&w->fd_node);
1669 if (w->flags & thread_sched_waiting_timeout) {
1680 batch[n].th = thread_sched_waiting_thread(w);
1681 batch[n].serial = w->data.event_serial;
1682 timer_wake_pending_inc(batch[n].th);
1685 w->flags = thread_sched_waiting_none;
1687 w->data.result = result;
1692 fd_waiters_arm(fd, e, fd_waiters_union(e));
1695 fd_shard_unlock(fd);
1697 for (
int i = 0; i < n; i++) {
1698 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
1700 timer_wake_pending_clear(batch, n);
1721timer_thread_polling(
rb_vm_t *vm)
1723 int r = event_wait(vm);
1725 RUBY_DEBUG_LOG(
"r:%d errno:%d", r,
errno);
1729 RUBY_DEBUG_LOG(
"timeout%s",
"");
1731 ractor_sched_lock(vm, NULL);
1734 timer_thread_check_timeslice(vm);
1737 if (vm->ractor.sched.grq_cnt > 0) {
1738 RUBY_DEBUG_LOG(
"GRQ cnt: %u", vm->ractor.sched.grq_cnt);
1742 ractor_sched_unlock(vm, NULL);
1745 native_thread_check_and_create_shared(vm);
1755 perror(
"event_wait");
1756 rb_bug(
"event_wait errno:%d",
errno);
1761 RUBY_DEBUG_LOG(
"%d event(s)", r);
1764 for (
int i=0; i<r; i++) {
1765 uint64_t tag = (uint64_t)(uintptr_t)timer_th.finished_events[i].udata;
1766 int fd = (int)timer_th.finished_events[i].ident;
1767 int16_t filter = timer_th.finished_events[i].filter;
1769 if (fd == timer_th.comm_fds[0]) {
1770 RUBY_DEBUG_LOG(
"comm from fd:%d", timer_th.comm_fds[1]);
1771 consume_communication_pipe(timer_th.comm_fds[0]);
1775 uint32_t wake_flags = kqueue_translate_filter_to_flags(filter) & FD_WAIT_IO_MASK;
1776 if (timer_th.finished_events[i].flags & (EV_EOF | EV_ERROR)) {
1777 wake_flags = FD_WAIT_IO_MASK;
1780 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, filter);
1782#elif HAVE_SYS_EPOLL_H
1783 for (
int i=0; i<r; i++) {
1784 uint64_t tag = timer_th.finished_events[i].data.u64;
1785 int fd = FD_EVENT_TAG_FD(tag);
1786 uint32_t events = timer_th.finished_events[i].events;
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 RUBY_DEBUG_LOG(
"io event. fd:%d event:%s%s%s%s%s%s", fd,
1795 (events & EPOLLIN) ?
"in/" :
"",
1796 (events & EPOLLOUT) ?
"out/" :
"",
1797 (events & EPOLLRDHUP) ?
"RDHUP/" :
"",
1798 (events & EPOLLPRI) ?
"pri/" :
"",
1799 (events & EPOLLERR) ?
"err/" :
"",
1800 (events & EPOLLHUP) ?
"hup/" :
"");
1802 uint32_t wake_flags = 0;
1803 if (events & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) wake_flags |= thread_sched_waiting_io_read;
1804 if (events & EPOLLOUT) wake_flags |= thread_sched_waiting_io_write;
1806 if (events & (EPOLLERR | EPOLLHUP)) wake_flags |= FD_WAIT_IO_MASK;
1808 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, (
int)events);
1811# error "neither kqueue nor epoll"
1821timer_thread_setup_mn(
void)
1827timer_thread_polling(
rb_vm_t *vm)
1829 int timeout = timer_thread_set_timeout(vm);
1831 struct pollfd pfd = {
1832 .fd = timer_th.comm_fds[0],
1836 int r = poll(&pfd, 1, timeout);
1840 ractor_sched_lock(vm, NULL);
1843 timer_thread_check_timeslice(vm);
1845 ractor_sched_unlock(vm, NULL);
1855 rb_bug(
"poll errno:%d",
errno);
1860 consume_communication_pipe(timer_th.comm_fds[0]);
1864 rb_bug(
"unreachbale");
1871 rb_bug(
"unreachable");
1874static enum thread_sched_wait_result
1875thread_sched_wait_events(
struct rb_thread_sched *sched,
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
1877 rb_bug(
"unreachable");
1883ractor_sched_timeout_arm(
rb_thread_t *th,
const rb_hrtime_t *rel)
1885 rb_bug(
"unreachable");
1891 rb_bug(
"unreachable");
1895timer_wheel_timeout(
int timeout)
1907timer_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.