5#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
6static void timer_thread_unregister_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags);
10timer_thread_check_exceed(rb_hrtime_t abs, rb_hrtime_t now)
26#define FD_WAIT_IO_MASK (thread_sched_waiting_io_read | thread_sched_waiting_io_write)
38fd_shard_unlock(
int fd)
43#define TIMER_WHEEL_NO_EXPIRY RB_HRTIME_MAX
44#ifndef TIMER_WHEEL_TICK_MS
45#define TIMER_WHEEL_TICK_MS 1
61timer_wheel_tick(rb_hrtime_t hrt)
63 return hrt / (RB_HRTIME_PER_MSEC * TIMER_WHEEL_TICK_MS);
67timer_wheel_ctz64(uint64_t v)
69#if defined(__GNUC__) || defined(__clang__)
70 return __builtin_ctzll(v);
73 while (!(v & 1)) { v >>= 1; n++; }
79timer_wheel_level(uint64_t dist_ms)
81 if (dist_ms < ((uint64_t)1 << TIMER_WHEEL_SLOT_BITS))
return 0;
82 if (dist_ms < ((uint64_t)1 << 2 * TIMER_WHEEL_SLOT_BITS))
return 1;
83 if (dist_ms < ((uint64_t)1 << 3 * TIMER_WHEEL_SLOT_BITS))
return 2;
84 return TIMER_WHEEL_LEVELS - 1;
90 uint64_t dl_tick = timer_wheel_tick(w->data.timeout);
91 uint64_t cur = timer_th.wheel_cursor_tick;
95 uint64_t target = dl_tick > cur ? dl_tick : cur + 1;
96 int lvl = timer_wheel_level(target - cur);
97 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
98 uint64_t slot_tick = target >> shift;
100 if (lvl == TIMER_WHEEL_LEVELS - 1) {
103 uint64_t far = (cur >> shift) + TIMER_WHEEL_SLOTS - 1;
104 if (slot_tick > far) slot_tick = far;
107 int slot = (int)(slot_tick & (TIMER_WHEEL_SLOTS - 1));
109 ccan_list_add_tail(&timer_th.wheel[lvl].slots[slot], &w->node);
110 timer_th.wheel[lvl].occupied |= UINT64_C(1) << slot;
111 w->wheel_lvl = (uint8_t)lvl;
112 w->wheel_slot = (uint8_t)slot;
114 if (w->data.timeout < timer_th.next_expiry) {
115 timer_th.next_expiry = w->data.timeout;
123 ccan_list_del_init(&w->node);
125 if (w->flags & thread_sched_waiting_timeout) {
126 struct timer_wheel_level *lv = &timer_th.wheel[w->wheel_lvl];
127 if (ccan_list_empty(&lv->slots[w->wheel_slot])) {
128 lv->occupied &= ~(UINT64_C(1) << w->wheel_slot);
137timer_wheel_next_expiry(
void)
139 rb_hrtime_t best = TIMER_WHEEL_NO_EXPIRY;
141 for (
int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
142 uint64_t occ = timer_th.wheel[lvl].occupied;
145 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
146 uint64_t cur_tick = timer_th.wheel_cursor_tick >> shift;
147 unsigned base = (unsigned)((cur_tick + 1) & (TIMER_WHEEL_SLOTS - 1));
149 uint64_t rot = (occ >> base) | (base ? (occ << (TIMER_WHEEL_SLOTS - base)) : 0);
150 uint64_t tick = cur_tick + 1 + timer_wheel_ctz64(rot);
151 rb_hrtime_t start = (rb_hrtime_t)(tick << shift) * RB_HRTIME_PER_MSEC * TIMER_WHEEL_TICK_MS;
153 if (start < best) best = start;
163timer_wheel_drain(rb_hrtime_t now, uint64_t now_tick,
struct ccan_list_head *expired)
165 uint64_t prev_tick = timer_th.wheel_cursor_tick;
166 timer_th.wheel_cursor_tick = now_tick;
170 rb_hrtime_t reinserted = TIMER_WHEEL_NO_EXPIRY;
172 for (
int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
173 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
174 uint64_t from = prev_tick >> shift;
175 uint64_t to = now_tick >> shift;
177 if (to == from)
break;
179 uint64_t steps = to - from;
180 if (steps > TIMER_WHEEL_SLOTS) steps = TIMER_WHEEL_SLOTS;
181 struct timer_wheel_level *lv = &timer_th.wheel[lvl];
183 for (uint64_t tick = to - steps + 1; tick <= to; tick++) {
184 int slot = (int)(tick & (TIMER_WHEEL_SLOTS - 1));
186 if (!(lv->occupied & (UINT64_C(1) << slot)))
continue;
187 lv->occupied &= ~(UINT64_C(1) << slot);
191 struct ccan_list_head pending;
192 ccan_list_head_init(&pending);
193 ccan_list_append_list(&pending, &lv->slots[slot]);
197 if (timer_thread_check_exceed(w->data.timeout, now)) {
198 RUBY_DEBUG_LOG(
"expired th:%u", rb_th_serial(thread_sched_waiting_thread(w)));
205 ccan_list_add_tail(expired, &w->node);
210 if (w->data.timeout < reinserted) reinserted = w->data.timeout;
211 timer_wheel_insert(w);
217 timer_th.next_expiry = timer_wheel_next_expiry();
218 if (reinserted < timer_th.next_expiry) timer_th.next_expiry = reinserted;
224timer_wheel_timeout(
int timeout)
228 if (timer_th.next_expiry != TIMER_WHEEL_NO_EXPIRY) {
229 rb_hrtime_t now = rb_hrtime_now();
230 rb_hrtime_t hrrel = rb_hrtime_sub(timer_th.next_expiry, now);
232 RUBY_DEBUG_LOG(
"now:%lu rel:%lu", (
unsigned long)now, (
unsigned long)hrrel);
234 rb_hrtime_t msec = (hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC;
237 int thread_timeout = msec > INT_MAX ? INT_MAX : (int)msec;
240 if (timeout < 0 || thread_timeout < timeout) {
241 timeout = thread_timeout;
253 if (sched->running != th && th->sched.event_serial == event_serial) {
254 thread_sched_to_ready_common(sched, th,
true,
false);
259timer_thread_wakeup_thread(
rb_thread_t *th, uint32_t event_serial)
261 RUBY_DEBUG_LOG(
"th:%u", rb_th_serial(th));
264 thread_sched_lock(sched, th);
266 timer_thread_wakeup_thread_locked(sched, th, event_serial);
268 thread_sched_unlock(sched, th);
271#define TIMEOUT_WAKE_BATCH 16
274struct timer_wake {
rb_thread_t *th; uint32_t serial; };
284 th->sched.wake_pending_cnt++;
292 VM_ASSERT(th->sched.wake_pending_cnt > 0);
293 th->sched.wake_pending_cnt--;
299timer_wake_pending_clear(
struct timer_wake *batch,
int n)
301 for (
int i = 0; i < n; i++) {
302 timer_wake_pending_dec(batch[i].th);
311 if (!TIMER_THREAD_CREATED_P())
return;
314 while (th->sched.wake_pending_cnt > 0) {
321timer_thread_check_timeout(
rb_vm_t *vm)
323 rb_hrtime_t now = rb_hrtime_now();
324 uint64_t now_tick = timer_wheel_tick(now);
325 struct ccan_list_head expired;
327 ccan_list_head_init(&expired);
329 struct timer_wake batch[TIMEOUT_WAKE_BATCH];
334 struct {
rb_thread_t *th; uint32_t serial;
int fd; } io_claims[TIMEOUT_WAKE_BATCH];
340 if (now_tick > timer_th.wheel_cursor_tick) {
341 timer_wheel_drain(now, now_tick, &expired);
345 while (n + n_io < TIMEOUT_WAKE_BATCH &&
352 ccan_list_node_init(&w->node);
354 if (w->flags & FD_WAIT_IO_MASK) {
359 io_claims[n_io].th = thread_sched_waiting_thread(w);
360 io_claims[n_io].serial = w->data.event_serial;
361 io_claims[n_io].fd = w->data.fd;
362 timer_wake_pending_inc(io_claims[n_io].th);
367 batch[n].th = thread_sched_waiting_thread(w);
368 batch[n].serial = w->data.event_serial;
369 timer_wake_pending_inc(batch[n].th);
370 w->flags = thread_sched_waiting_none;
375 more = !ccan_list_empty(&expired);
379 for (
int i = 0; i < n; i++) {
380 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
382 timer_wake_pending_clear(batch, n);
385 for (
int i = 0; i < n_io; i++) {
387 int fd = io_claims[i].fd;
388 bool claimed =
false;
394 if (w->flags != thread_sched_waiting_none &&
395 w->data.event_serial == io_claims[i].serial) {
396 VM_ASSERT(w->data.fd == fd);
397 timer_thread_unregister_waiting(th, fd, w->flags);
398 w->flags = thread_sched_waiting_none;
406 timer_thread_wakeup_thread(th, io_claims[i].serial);
408 timer_wake_pending_dec(th);
420 enum thread_sched_waiting_flag flags = w->flags;
422 if (flags == thread_sched_waiting_none) {
425 else if (flags & FD_WAIT_IO_MASK) {
429 if ((w->flags & FD_WAIT_IO_MASK) && w->data.fd == fd) {
430 if (w->flags & thread_sched_waiting_timeout) {
435 timer_thread_unregister_waiting(th, fd, w->flags);
436 w->flags = thread_sched_waiting_none;
444 if (w->flags && !(w->flags & FD_WAIT_IO_MASK)) {
446 w->flags = thread_sched_waiting_none;
457ubf_event_waiting(
void *ptr)
462 RUBY_DEBUG_LOG(
"th:%u", rb_th_serial(th));
464 VM_ASSERT(th->nt == NULL || !th_has_dedicated_nt(th));
467 th->unblock.func = NULL;
468 th->unblock.arg = NULL;
470 thread_sched_lock(sched, th);
472 bool canceled = timer_thread_cancel_waiting(th);
474 if (sched->running == th) {
475 RUBY_DEBUG_LOG(
"not waiting yet");
478 thread_sched_to_ready_common(sched, th,
true,
false);
481 RUBY_DEBUG_LOG(
"already not waiting");
484 thread_sched_unlock(sched, th);
489enum timer_thread_register_result {
490 timer_thread_registered,
491 timer_thread_already_ready,
492 timer_thread_unavailable,
495static enum timer_thread_register_result
496timer_thread_register_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial);
501ractor_sched_timeout_arm(
rb_thread_t *th,
const rb_hrtime_t *rel)
503 rb_hrtime_t rel_copy = *rel;
505 return timer_thread_register_waiting(th, -1, thread_sched_waiting_timeout, &rel_copy,
506 ++th->sched.event_serial) == timer_thread_registered;
513 return timer_thread_cancel_waiting(th);
517static enum thread_sched_wait_result
518thread_sched_wait_events(
struct rb_thread_sched *sched,
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
520 VM_ASSERT(!th_has_dedicated_nt(th));
522 volatile bool timedout =
false, need_cancel =
false;
523 volatile enum timer_thread_register_result reg = timer_thread_unavailable;
525 uint32_t event_serial = ++th->sched.event_serial;
528 thread_sched_lock(sched, th);
531 if (ubf_set(th, ubf_event_waiting, (
void *)th, NULL)) {
534 thread_sched_unlock(sched, th);
535 return thread_sched_wait_event;
538 reg = timer_thread_register_waiting(th, fd, events, rel, event_serial);
540 if (reg == timer_thread_registered) {
541 RUBY_DEBUG_LOG(
"wait fd:%d", fd);
543 RB_VM_SAVE_MACHINE_CONTEXT(th);
547 if (th->sched.waiting_reason.flags == thread_sched_waiting_none) {
548 th->sched.event_serial++;
551 else if (RUBY_VM_INTERRUPTED(th->ec)) {
552 th->sched.event_serial++;
556 RUBY_DEBUG_LOG(
"sleep");
563 enum rb_thread_status prev_status = th->status;
564 if (prev_status == THREAD_RUNNABLE) th->status = THREAD_STOPPED_FOREVER;
565 thread_sched_wakeup_next_thread(sched, th,
true);
566 thread_sched_wait_running_turn(sched, th,
true, NULL);
567 if (prev_status == THREAD_RUNNABLE) th->status = THREAD_RUNNABLE;
569 RUBY_DEBUG_LOG(
"wakeup");
572 timedout = th->sched.waiting_reason.data.result == 0;
575 timer_thread_cancel_waiting(th);
581 RUBY_DEBUG_LOG(
"did not wait fd:%d reg:%d", fd, (
int)reg);
584 thread_sched_unlock(sched, th);
587 ubf_clear(th,
false);
589 VM_ASSERT(sched->running == th);
591 if (reg == timer_thread_unavailable)
return thread_sched_wait_unavailable;
593 if (reg == timer_thread_already_ready)
return thread_sched_wait_event;
594 return timedout ? thread_sched_wait_timeout : thread_sched_wait_event;
600get_sysconf_page_size(
void)
602 static long page_size = 0;
604 if (UNLIKELY(page_size == 0)) {
605 page_size = sysconf(_SC_PAGESIZE);
606 VM_ASSERT(page_size < INT_MAX);
608 return (
int)page_size;
611#define MSTACK_CHUNK_SIZE (512 * 1024 * 1024)
612#define MSTACK_PAGE_SIZE get_sysconf_page_size()
613#define MSTACK_CHUNK_PAGE_NUM (MSTACK_CHUNK_SIZE / MSTACK_PAGE_SIZE - 1)
626static struct nt_stack_chunk_header {
627 struct nt_stack_chunk_header *prev_chunk;
628 struct nt_stack_chunk_header *prev_free_chunk;
634 uint16_t stack_count;
635 uint16_t uninitialized_stack_count;
637 uint16_t free_stack_pos;
638 uint16_t free_stack[];
639} *nt_stack_chunks = NULL,
640 *nt_free_stack_chunks = NULL;
642struct nt_machine_stack_footer {
643 struct nt_stack_chunk_header *ch;
647static rb_nativethread_lock_t nt_machine_stack_lock = RB_NATIVETHREAD_LOCK_INIT;
651nt_machine_stack_atfork(
void)
663nt_vm_stack_area(
const rb_vm_t *vm)
665 return (
size_t)roomof(vm->default_params.thread_vm_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
669nt_machine_stack_area(
const rb_vm_t *vm)
671 return (
size_t)roomof(vm->default_params.thread_machine_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
676nt_thread_stack_size(
void)
679 if (LIKELY(msz > 0))
return msz;
682 msz = nt_vm_stack_area(vm) + MSTACK_PAGE_SIZE + nt_machine_stack_area(vm);
686static struct nt_stack_chunk_header *
687nt_alloc_thread_stack_chunk(
void)
689 const char *m = (
void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
690 if (m == MAP_FAILED) {
694 ruby_annotate_mmap(m, MSTACK_CHUNK_SIZE,
"Ruby:nt_alloc_thread_stack_chunk");
696 size_t msz = nt_thread_stack_size();
697 int header_page_cnt = 1;
698 int stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
699 int ch_size =
sizeof(
struct nt_stack_chunk_header) + sizeof(uint16_t) * stack_count;
701 if (ch_size > MSTACK_PAGE_SIZE * header_page_cnt) {
702 header_page_cnt = (ch_size + MSTACK_PAGE_SIZE - 1) / MSTACK_PAGE_SIZE;
703 stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
706 VM_ASSERT(stack_count <= UINT16_MAX);
709 if (mprotect((
void *)m, (
size_t)header_page_cnt * MSTACK_PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) {
710 munmap((
void *)m, MSTACK_CHUNK_SIZE);
714 struct nt_stack_chunk_header *ch = (
struct nt_stack_chunk_header *)m;
716 ch->start_page = header_page_cnt;
717 ch->prev_chunk = nt_stack_chunks;
718 ch->prev_free_chunk = nt_free_stack_chunks;
719 ch->on_free_list =
true;
720 ch->uninitialized_stack_count = ch->stack_count = (uint16_t)stack_count;
721 ch->free_stack_pos = 0;
723 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);
729nt_stack_chunk_get_stack_start(
struct nt_stack_chunk_header *ch,
size_t idx)
731 const char *m = (
char *)ch;
732 return (
void *)(m + ch->start_page * MSTACK_PAGE_SIZE + idx * nt_thread_stack_size());
735static struct nt_machine_stack_footer *
736nt_stack_chunk_get_msf(
const rb_vm_t *vm,
const char *mstack)
739 const size_t msz = vm->default_params.thread_machine_stack_size;
740 return (
struct nt_machine_stack_footer *)&mstack[msz -
sizeof(
struct nt_machine_stack_footer)];
744nt_stack_chunk_get_stack(
const rb_vm_t *vm,
struct nt_stack_chunk_header *ch,
size_t idx,
void **vm_stack,
void **machine_stack)
749 const char *vstack, *mstack;
750 const char *guard_page;
751 vstack = nt_stack_chunk_get_stack_start(ch, idx);
752 guard_page = vstack + nt_vm_stack_area(vm);
753 mstack = guard_page + MSTACK_PAGE_SIZE;
755 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(vm, mstack);
760 RUBY_DEBUG_LOG(
"msf:%p vstack:%p-%p guard_page:%p-%p mstack:%p-%p", msf,
761 vstack, (
void *)(guard_page-1),
762 guard_page, (
void *)(mstack-1),
763 mstack, (
void *)(msf));
766 *vm_stack = (
void *)vstack;
767 *machine_stack = (
void *)mstack;
772nt_stack_chunk_dump(
void)
774 struct nt_stack_chunk_header *ch;
777 fprintf(stderr,
"** nt_stack_chunks\n");
778 ch = nt_stack_chunks;
779 for (i=0; ch; i++, ch = ch->prev_chunk) {
780 fprintf(stderr,
"%d %p free_pos:%d\n", i, (
void *)ch, (
int)ch->free_stack_pos);
783 fprintf(stderr,
"** nt_free_stack_chunks\n");
784 ch = nt_free_stack_chunks;
785 for (i=0; ch; i++, ch = ch->prev_free_chunk) {
786 fprintf(stderr,
"%d %p free_pos:%d\n", i, (
void *)ch, (
int)ch->free_stack_pos);
791nt_alloc_stack(
rb_vm_t *vm,
void **vm_stack,
void **machine_stack)
798 if (nt_free_stack_chunks) {
799 struct nt_stack_chunk_header *ch = nt_free_stack_chunks;
800 if (ch->free_stack_pos > 0) {
801 RUBY_DEBUG_LOG(
"free_stack_pos:%d", ch->free_stack_pos);
802 nt_stack_chunk_get_stack(vm, ch, ch->free_stack[--ch->free_stack_pos], vm_stack, machine_stack);
804 else if (ch->uninitialized_stack_count > 0) {
805 RUBY_DEBUG_LOG(
"uninitialized_stack_count:%d", ch->uninitialized_stack_count);
807 size_t idx = ch->stack_count - ch->uninitialized_stack_count--;
811 char *stack_start = nt_stack_chunk_get_stack_start(ch, idx);
812 size_t vm_stack_area = nt_vm_stack_area(vm);
813 size_t mstack_size = nt_thread_stack_size() - vm_stack_area - MSTACK_PAGE_SIZE;
814 char *mstack_start = stack_start + vm_stack_area + MSTACK_PAGE_SIZE;
816 int mstack_flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE;
817#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
818 mstack_flags |= MAP_STACK;
821 if (mprotect(stack_start, vm_stack_area, PROT_READ | PROT_WRITE) != 0 ||
822 mmap(mstack_start, mstack_size, PROT_READ | PROT_WRITE, mstack_flags, -1, 0) == MAP_FAILED) {
824 ch->uninitialized_stack_count++;
827 nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack);
831 nt_free_stack_chunks = ch->prev_free_chunk;
832 ch->prev_free_chunk = NULL;
833 ch->on_free_list =
false;
838 struct nt_stack_chunk_header *p = nt_alloc_thread_stack_chunk();
843 nt_free_stack_chunks = nt_stack_chunks = p;
854nt_madvise_free_or_dontneed(
void *addr,
size_t len)
867#if defined(MADV_FREE)
868 int r = madvise(addr,
len, MADV_FREE);
872#if defined(MADV_DONTNEED)
873 madvise(addr,
len, MADV_DONTNEED);
878nt_free_stack(
void *mstack)
884 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(GET_VM(), mstack);
885 struct nt_stack_chunk_header *ch = msf->ch;
886 int idx = (int)msf->index;
887 void *stack = nt_stack_chunk_get_stack_start(ch, idx);
889 RUBY_DEBUG_LOG(
"stack:%p mstack:%p ch:%p index:%d", stack, mstack, ch, idx);
891 if (!ch->on_free_list) {
892 ch->on_free_list =
true;
893 ch->prev_free_chunk = nt_free_stack_chunks;
894 nt_free_stack_chunks = ch;
896 ch->free_stack[ch->free_stack_pos++] = idx;
899 nt_madvise_free_or_dontneed(stack, nt_thread_stack_size());
906native_thread_check_and_create_shared(
rb_vm_t *vm)
908 bool need_to_make =
false;
910 ractor_sched_lock(vm, NULL);
912 unsigned int schedulable_ractor_cnt = vm->ractor.cnt;
915 if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads)
916 schedulable_ractor_cnt--;
920 while (((
int)snt_cnt < MINIMUM_SNT) ||
921 (snt_cnt < schedulable_ractor_cnt &&
922 snt_cnt < vm->ractor.sched.max_cpu)) {
924 if (prev == snt_cnt) {
932 RUBY_DEBUG_LOG(
"added snt:%u dnt:%u ractor_cnt:%u grq_cnt:%u",
933 vm->ractor.sched.snt_cnt,
934 vm->ractor.sched.dnt_cnt,
936 vm->ractor.sched.grq_cnt);
939 RUBY_DEBUG_LOG(
"snt:%d ractor_cnt:%d", (
int)vm->ractor.sched.snt_cnt, (
int)vm->ractor.cnt);
942 ractor_sched_unlock(vm, NULL);
947 int err = native_thread_create0(nt);
951 ractor_sched_lock(vm, NULL);
953 ractor_sched_unlock(vm, NULL);
954 native_thread_destroy(nt);
969 struct rb_thread_context *tctx = (
struct rb_thread_context *)th->sched.context;
972 bool last = (th->invoke_type == thread_invoke_type_ractor_proc);
973 bool is_dnt = th_has_dedicated_nt(th);
985 bool wake_mn =
false;
993 VM_ASSERT(sched->running == th);
996 rb_ractor_living_threads_remove(r, th);
999 thread_sched_lock(sched, th);
1003 thread_sched_to_dead_common(sched, th);
1010 wake_th = is_dnt ? NULL : sched->running;
1015 wake_mn = (wake_th != NULL && wake_th->nt == NULL);
1018 native_thread_assign(NULL, th);
1019 th->sched.context = NULL;
1027 rb_ractor_set_current_ec(r, NULL);
1033 thread_sched_unlock(sched, th);
1035 VM_ASSERT(sched->running == NULL);
1036 VM_ASSERT(wake_th == NULL);
1039 rb_ractor_living_threads_remove(r, th);
1040 rb_current_ec_set(NULL);
1045 thread_sched_unlock_no_log(sched, th);
1050 thread_sched_lock(sched, NULL);
1051 ractor_sched_enq(vm, r);
1052 thread_sched_unlock(sched, NULL);
1058# define co_start ruby_coroutine_start
1065#ifdef RUBY_ASAN_ENABLED
1066 __sanitizer_finish_switch_fiber(self->fake_stack,
1067 (
const void**)&from->stack_base, &from->stack_size);
1072 VM_ASSERT(th->nt != NULL);
1073 VM_ASSERT(th == sched->running);
1074 VM_ASSERT(sched->lock_owner == NULL);
1078 thread_sched_set_locked(sched, th);
1079 thread_sched_add_running_thread(TH_SCHED(th), th);
1080 thread_sched_unlock(sched, th);
1083 call_thread_start_func_2(th);
1090 struct rb_thread_context *tctx = (
struct rb_thread_context *)self;
1094 tctx->nt->dead_co = &tctx->co;
1095 coroutine_transfer0(&tctx->co, tctx->nt->nt_context,
true);
1097 rb_bug(
"unreachable");
1105 void *vm_stack = NULL, *machine_stack = NULL;
1106 int err = nt_alloc_stack(vm, &vm_stack, &machine_stack);
1107 if (err)
return err;
1109 VM_ASSERT(vm_stack < machine_stack);
1112 size_t vm_stack_words = th->vm->default_params.thread_vm_stack_size/
sizeof(
VALUE);
1113 rb_ec_initialize_vm_stack(th->ec, vm_stack, vm_stack_words);
1116 size_t machine_stack_size = vm->default_params.thread_machine_stack_size -
sizeof(
struct nt_machine_stack_footer);
1117 th->ec->machine.stack_start = (
void *)((uintptr_t)machine_stack + machine_stack_size);
1118 th->ec->machine.stack_maxsize = machine_stack_size;
1119 th->sched.context_stack = machine_stack;
1120 th->sched.context_stack_size = machine_stack_size;
1122 struct rb_thread_context *tctx = ruby_xmalloc(
sizeof(
struct rb_thread_context));
1123 tctx->stack = machine_stack;
1126 th->sched.context = &tctx->co;
1127 coroutine_initialize(&tctx->co, co_start, machine_stack, machine_stack_size);
1128 tctx->co.argument = th;
1130 RUBY_DEBUG_LOG(
"th:%u vm_stack:%p machine_stack:%p", rb_th_serial(th), vm_stack, machine_stack);
1135 int create_err = native_thread_check_and_create_shared(vm);
1136 if (create_err)
return create_err;
1138 thread_sched_to_ready(TH_SCHED(th), th);
1143#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1151#define FDMAP_CHUNK_BITS 10
1152#define FDMAP_CHUNK_SIZE (1u << FDMAP_CHUNK_BITS)
1153#define FDMAP_CHUNK_MASK (FDMAP_CHUNK_SIZE - 1)
1158fd_waiters_lookup(
int fd,
bool create)
1160 if (fd < 0)
return NULL;
1162 unsigned int ci = (
unsigned int)fd >> FDMAP_CHUNK_BITS;
1163 if (ci >= FDMAP_MAX_CHUNKS)
return NULL;
1167 if (chunk == NULL) {
1168 if (!create)
return NULL;
1170 chunk = calloc(FDMAP_CHUNK_SIZE,
sizeof(*chunk));
1171 if (chunk == NULL) rb_bug(
"fd_waiters_lookup: calloc failed");
1173 for (
unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) {
1174 ccan_list_head_init(&chunk[i].waiters);
1184 return &chunk[(
unsigned int)fd & FDMAP_CHUNK_MASK];
1194 ccan_list_for_each(&e->waiters, w, fd_node) {
1195 want |= (uint32_t)(w->flags & FD_WAIT_IO_MASK);
1202static inline uint64_t
1203fd_event_tag(
int fd, uint32_t generation)
1205 return ((uint64_t)generation << 32) | (uint32_t)fd;
1208#define FD_EVENT_TAG_FD(tag) ((int)((tag) & 0xffffffffu))
1209#define FD_EVENT_TAG_GEN(tag) ((uint32_t)((tag) >> 32))
1215fd_waiters_arm(
int fd,
struct rb_fd_waiters *e, uint32_t want)
1217 if (want == e->armed_flags)
return true;
1220 struct kevent ke[2];
1222 uint32_t add = want & ~e->armed_flags;
1223 uint32_t del = e->armed_flags & ~want;
1224 void *tag = (
void *)(uintptr_t)fd_event_tag(fd, e->generation);
1226 if (del & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); n++; }
1227 if (del & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); n++; }
1229 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1233 rb_bug(
"fd_waiters_arm/kevent delete failed (fd:%d errno:%d)", fd,
errno);
1238 if (add & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_ADD, 0, 0, tag); n++; }
1239 if (add & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_ADD, 0, 0, tag); n++; }
1241 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1248 rb_bug(
"fd_waiters_arm/kevent add failed (fd:%d errno:%d)", fd,
errno);
1251#elif HAVE_SYS_EPOLL_H
1253 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
1260 perror(
"epoll_ctl");
1261 rb_bug(
"fd_waiters_arm/epoll_ctl del failed (fd:%d errno:%d)", fd,
errno);
1270 uint32_t epoll_events = 0;
1271 if (want & thread_sched_waiting_io_read) epoll_events |= EPOLLIN;
1272 if (want & thread_sched_waiting_io_write) epoll_events |= EPOLLOUT;
1274 struct epoll_event event = {
1275 .events = epoll_events,
1276 .data = { .u64 = fd_event_tag(fd, e->generation) },
1279 int op = e->armed_flags ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
1281 if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) {
1285 if (op == EPOLL_CTL_MOD &&
1286 epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) {
1292 if (op == EPOLL_CTL_ADD &&
1293 epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &event) == 0) {
1302 perror(
"epoll_ctl");
1303 rb_bug(
"fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op,
errno);
1307# error "neither kqueue nor epoll"
1310 e->armed_flags = want;
1315fd_readable_nonblock(
int fd)
1317 struct pollfd pfd = {
1321 return poll(&pfd, 1, 0) != 0;
1325fd_writable_nonblock(
int fd)
1327 struct pollfd pfd = {
1331 return poll(&pfd, 1, 0) != 0;
1335verify_waiting_list(
void)
1337#if VM_CHECK_MODE > 0
1340 for (
int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
1341 const struct timer_wheel_level *lv = &timer_th.wheel[lvl];
1343 for (
int slot = 0; slot < TIMER_WHEEL_SLOTS; slot++) {
1344 bool occupied = (lv->occupied >> slot) & 1;
1345 VM_ASSERT(occupied == !ccan_list_empty(&lv->slots[slot]));
1347 ccan_list_for_each(&lv->slots[slot], w, node) {
1349 if (!(w->flags & FD_WAIT_IO_MASK)) {
1350 VM_ASSERT(w->flags & thread_sched_waiting_timeout);
1351 VM_ASSERT(w->data.timeout != 0);
1353 VM_ASSERT(w->wheel_lvl == lvl);
1354 VM_ASSERT(w->wheel_slot == slot);
1363static enum thread_sched_waiting_flag
1364kqueue_translate_filter_to_flags(int16_t filter)
1368 return thread_sched_waiting_io_read;
1370 return thread_sched_waiting_io_write;
1372 return thread_sched_waiting_timeout;
1374 rb_bug(
"kevent filter:%d not supported", filter);
1381 struct timespec calculated_timeout;
1383 int timeout_ms = timer_thread_set_timeout(vm);
1385 if (timeout_ms > 0) {
1386 calculated_timeout.tv_sec = timeout_ms / 1000;
1387 calculated_timeout.tv_nsec = (timeout_ms % 1000) * 1000000;
1388 timeout = &calculated_timeout;
1390 else if (timeout_ms == 0) {
1393 memset(&calculated_timeout, 0,
sizeof(
struct timespec));
1394 timeout = &calculated_timeout;
1397 return kevent(timer_th.event_fd, NULL, 0, timer_th.finished_events, KQUEUE_EVENTS_MAX, timeout);
1403 if ((timer_th.event_fd = kqueue()) == -1) rb_bug(
"kqueue creation failed (errno:%d)",
errno);
1404 int flags = fcntl(timer_th.event_fd, F_GETFD);
1406 rb_bug(
"kqueue GETFD failed (errno:%d)",
errno);
1409 flags |= FD_CLOEXEC;
1410 if (fcntl(timer_th.event_fd, F_SETFD, flags) == -1) {
1411 rb_bug(
"kqueue SETFD failed (errno:%d)",
errno);
1418static enum timer_thread_register_result
1419timer_thread_register_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial)
1421 RUBY_DEBUG_LOG(
"th:%u fd:%d flag:%d rel:%lu", rb_th_serial(th), fd, flags, rel ? (
unsigned long)*rel : 0);
1423 VM_ASSERT(th == NULL || TH_SCHED(th)->running == th);
1424 VM_ASSERT(flags != 0);
1426 rb_hrtime_t abs = 0;
1430 flags |= thread_sched_waiting_timeout;
1433 return timer_thread_already_ready;
1437 if (flags & thread_sched_waiting_timeout) {
1438 VM_ASSERT(rel != NULL);
1439 abs = rb_hrtime_add(rb_hrtime_now(), *rel);
1442 if (flags & thread_sched_waiting_io_read) {
1443 if (!(flags & thread_sched_waiting_io_force) && fd_readable_nonblock(fd)) {
1444 RUBY_DEBUG_LOG(
"fd_readable_nonblock");
1445 return timer_thread_already_ready;
1450 if (flags & thread_sched_waiting_io_write) {
1451 if (!(flags & thread_sched_waiting_io_force) && fd_writable_nonblock(fd)) {
1452 RUBY_DEBUG_LOG(
"fd_writable_nonblock");
1453 return timer_thread_already_ready;
1458 if (flags & FD_WAIT_IO_MASK) {
1464 fd_shard_unlock(fd);
1465 return timer_thread_unavailable;
1470 if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK))) {
1471 fd_shard_unlock(fd);
1472 return timer_thread_unavailable;
1476 ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node);
1478 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1479 th->sched.waiting_reason.flags = flags;
1480 th->sched.waiting_reason.data.timeout = abs;
1481 th->sched.waiting_reason.data.fd = fd;
1482 th->sched.waiting_reason.data.result = 0;
1483 th->sched.waiting_reason.data.event_serial = event_serial;
1486 RUBY_DEBUG_LOG(
"abs:%lu", (
unsigned long)abs);
1487 VM_ASSERT(flags & thread_sched_waiting_timeout);
1491 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1492 timer_wheel_insert(&th->sched.waiting_reason);
1493 verify_waiting_list();
1494 if (timer_th.next_expiry < prev_expiry) {
1496 timer_thread_wakeup_force();
1502 RUBY_DEBUG_LOG(
"armed fd:%d want:%u", fd, e->armed_flags);
1504 fd_shard_unlock(fd);
1508 VM_ASSERT(abs != 0 && (flags & thread_sched_waiting_timeout));
1512 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1513 th->sched.waiting_reason.flags = flags;
1514 th->sched.waiting_reason.data.timeout = abs;
1515 th->sched.waiting_reason.data.fd = fd;
1516 th->sched.waiting_reason.data.result = 0;
1517 th->sched.waiting_reason.data.event_serial = event_serial;
1519 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1520 timer_wheel_insert(&th->sched.waiting_reason);
1521 verify_waiting_list();
1522 if (timer_th.next_expiry < prev_expiry) {
1523 timer_thread_wakeup_force();
1529 VM_ASSERT(abs == 0);
1532 return timer_thread_registered;
1538timer_thread_unregister_waiting(
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag flags)
1540 if (!(th->sched.waiting_reason.flags & FD_WAIT_IO_MASK)) {
1544 RUBY_DEBUG_LOG(
"th:%u fd:%d", rb_th_serial(th), fd);
1546 ccan_list_del_init(&th->sched.waiting_reason.fd_node);
1550 fd_waiters_arm(fd, e, fd_waiters_union(e));
1557timer_thread_arm_comm_pipe(
void)
1559 int fd = timer_th.comm_fds[0];
1563 EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, (
void *)(uintptr_t)fd_event_tag(fd, 0));
1564 if (kevent(timer_th.event_fd, &ke, 1, NULL, 0, NULL) == -1) {
1565 rb_bug(
"timer_thread_arm_comm_pipe/kevent failed (errno:%d)",
errno);
1567#elif HAVE_SYS_EPOLL_H
1568 struct epoll_event event = {
1570 .data = { .u64 = fd_event_tag(fd, 0) },
1572 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
1573 rb_bug(
"timer_thread_arm_comm_pipe/epoll_ctl failed (errno:%d)",
errno);
1576# error "neither kqueue nor epoll"
1581timer_thread_setup_mn(
void)
1585 RUBY_DEBUG_LOG(
"kqueue_fd:%d", timer_th.event_fd);
1587 if ((timer_th.event_fd = epoll_create1(EPOLL_CLOEXEC)) == -1) rb_bug(
"epoll_create (errno:%d)",
errno);
1588 RUBY_DEBUG_LOG(
"epoll_fd:%d", timer_th.event_fd);
1590 RUBY_DEBUG_LOG(
"comm_fds:%d/%d", timer_th.comm_fds[0], timer_th.comm_fds[1]);
1592 timer_thread_arm_comm_pipe();
1599 int r = kqueue_wait(vm);
1601 int r = epoll_wait(timer_th.event_fd, timer_th.finished_events, EPOLL_EVENTS_MAX, timer_thread_set_timeout(vm));
1607#define FD_WAKE_BATCH 16
1614timer_thread_wake_fd_waiters(
int fd, uint32_t generation, uint32_t wake_flags,
int result)
1616 struct timer_wake batch[FD_WAKE_BATCH];
1618 if (wake_flags == 0)
return;
1630 if (e != NULL && e->generation == generation) {
1633 ccan_list_for_each_safe(&e->waiters, w, nxt, fd_node) {
1634 if (!(w->flags & wake_flags))
continue;
1636 if (n == FD_WAKE_BATCH) {
1641 ccan_list_del_init(&w->fd_node);
1643 if (w->flags & thread_sched_waiting_timeout) {
1654 batch[n].th = thread_sched_waiting_thread(w);
1655 batch[n].serial = w->data.event_serial;
1656 timer_wake_pending_inc(batch[n].th);
1659 w->flags = thread_sched_waiting_none;
1661 w->data.result = result;
1666 fd_waiters_arm(fd, e, fd_waiters_union(e));
1669 fd_shard_unlock(fd);
1671 for (
int i = 0; i < n; i++) {
1672 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
1674 timer_wake_pending_clear(batch, n);
1695timer_thread_polling(
rb_vm_t *vm)
1697 int r = event_wait(vm);
1699 RUBY_DEBUG_LOG(
"r:%d errno:%d", r,
errno);
1703 RUBY_DEBUG_LOG(
"timeout%s",
"");
1705 ractor_sched_lock(vm, NULL);
1708 timer_thread_check_timeslice(vm);
1711 if (vm->ractor.sched.grq_cnt > 0) {
1712 RUBY_DEBUG_LOG(
"GRQ cnt: %u", vm->ractor.sched.grq_cnt);
1716 ractor_sched_unlock(vm, NULL);
1719 native_thread_check_and_create_shared(vm);
1729 perror(
"event_wait");
1730 rb_bug(
"event_wait errno:%d",
errno);
1735 RUBY_DEBUG_LOG(
"%d event(s)", r);
1738 for (
int i=0; i<r; i++) {
1739 uint64_t tag = (uint64_t)(uintptr_t)timer_th.finished_events[i].udata;
1740 int fd = (int)timer_th.finished_events[i].ident;
1741 int16_t filter = timer_th.finished_events[i].filter;
1743 if (fd == timer_th.comm_fds[0]) {
1744 RUBY_DEBUG_LOG(
"comm from fd:%d", timer_th.comm_fds[1]);
1745 consume_communication_pipe(timer_th.comm_fds[0]);
1749 uint32_t wake_flags = kqueue_translate_filter_to_flags(filter) & FD_WAIT_IO_MASK;
1750 if (timer_th.finished_events[i].flags & (EV_EOF | EV_ERROR)) {
1751 wake_flags = FD_WAIT_IO_MASK;
1754 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, filter);
1756#elif HAVE_SYS_EPOLL_H
1757 for (
int i=0; i<r; i++) {
1758 uint64_t tag = timer_th.finished_events[i].data.u64;
1759 int fd = FD_EVENT_TAG_FD(tag);
1760 uint32_t events = timer_th.finished_events[i].events;
1762 if (fd == timer_th.comm_fds[0]) {
1763 RUBY_DEBUG_LOG(
"comm from fd:%d", timer_th.comm_fds[1]);
1764 consume_communication_pipe(timer_th.comm_fds[0]);
1768 RUBY_DEBUG_LOG(
"io event. fd:%d event:%s%s%s%s%s%s", fd,
1769 (events & EPOLLIN) ?
"in/" :
"",
1770 (events & EPOLLOUT) ?
"out/" :
"",
1771 (events & EPOLLRDHUP) ?
"RDHUP/" :
"",
1772 (events & EPOLLPRI) ?
"pri/" :
"",
1773 (events & EPOLLERR) ?
"err/" :
"",
1774 (events & EPOLLHUP) ?
"hup/" :
"");
1776 uint32_t wake_flags = 0;
1777 if (events & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) wake_flags |= thread_sched_waiting_io_read;
1778 if (events & EPOLLOUT) wake_flags |= thread_sched_waiting_io_write;
1780 if (events & (EPOLLERR | EPOLLHUP)) wake_flags |= FD_WAIT_IO_MASK;
1782 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, (
int)events);
1785# error "neither kqueue nor epoll"
1795timer_thread_setup_mn(
void)
1801timer_thread_polling(
rb_vm_t *vm)
1803 int timeout = timer_thread_set_timeout(vm);
1805 struct pollfd pfd = {
1806 .fd = timer_th.comm_fds[0],
1810 int r = poll(&pfd, 1, timeout);
1814 ractor_sched_lock(vm, NULL);
1817 timer_thread_check_timeslice(vm);
1819 ractor_sched_unlock(vm, NULL);
1829 rb_bug(
"poll errno:%d",
errno);
1834 consume_communication_pipe(timer_th.comm_fds[0]);
1838 rb_bug(
"unreachbale");
1845 rb_bug(
"unreachable");
1848static enum thread_sched_wait_result
1849thread_sched_wait_events(
struct rb_thread_sched *sched,
rb_thread_t *th,
int fd,
enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
1851 rb_bug(
"unreachable");
1857ractor_sched_timeout_arm(
rb_thread_t *th,
const rb_hrtime_t *rel)
1859 rb_bug(
"unreachable");
1865 rb_bug(
"unreachable");
1869timer_wheel_timeout(
int timeout)
1881timer_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.