Ruby 4.1.0dev (2026-08-18 revision e569048b8a829d5ae65fab4f8243e0ecd5c62338)
thread_pthread_mn.c (e569048b8a829d5ae65fab4f8243e0ecd5c62338)
1// included by "thread_pthread.c"
2
3#if USE_MN_THREADS
4
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);
7#endif
8
9static bool
10timer_thread_check_exceed(rb_hrtime_t abs, rb_hrtime_t now)
11{
12 return abs <= now;
13}
14
15static rb_thread_t *
16thread_sched_waiting_thread(struct rb_thread_sched_waiting *w)
17{
18 if (w) {
19 return (rb_thread_t *)((size_t)w - offsetof(rb_thread_t, sched.waiting_reason));
20 }
21 else {
22 return NULL;
23 }
24}
25
26#define TIMER_WHEEL_NO_EXPIRY RB_HRTIME_MAX
27#ifndef TIMER_WHEEL_TICK_MS
28#define TIMER_WHEEL_TICK_MS 1 // L0 slot width; coarser trades sleep accuracy for fewer drains
29#endif
30
31/* Timer wheel over the timed waiters. Every operation runs under
32 * timer_th.waiting_lock.
33 *
34 * Level L buckets deadlines into 64 slots of 64^L ms each; a deadline is
35 * placed by its distance from the drain cursor, so its slot is always
36 * strictly ahead of the cursor at that level. The drain refines a
37 * not-yet-due waiter onto a finer level instead of cascading whole slots,
38 * so one waiter moves at most once per level over its lifetime.
39 *
40 * This is the hierarchical timing wheel of Varghese & Lauck, "Hashed and
41 * Hierarchical Timing Wheels" (SOSP '87). */
42
43static uint64_t
44timer_wheel_tick(rb_hrtime_t hrt)
45{
46 return hrt / (RB_HRTIME_PER_MSEC * TIMER_WHEEL_TICK_MS);
47}
48
49static inline int
50timer_wheel_ctz64(uint64_t v)
51{
52#if defined(__GNUC__) || defined(__clang__)
53 return __builtin_ctzll(v);
54#else
55 int n = 0;
56 while (!(v & 1)) { v >>= 1; n++; }
57 return n;
58#endif
59}
60
61static int
62timer_wheel_level(uint64_t dist_ms)
63{
64 if (dist_ms < ((uint64_t)1 << TIMER_WHEEL_SLOT_BITS)) return 0;
65 if (dist_ms < ((uint64_t)1 << 2 * TIMER_WHEEL_SLOT_BITS)) return 1;
66 if (dist_ms < ((uint64_t)1 << 3 * TIMER_WHEEL_SLOT_BITS)) return 2;
67 return TIMER_WHEEL_LEVELS - 1;
68}
69
70static void
71timer_wheel_insert(struct rb_thread_sched_waiting *w)
72{
73 uint64_t dl_tick = timer_wheel_tick(w->data.timeout);
74 uint64_t cur = timer_th.wheel_cursor_tick;
75
76 /* A deadline at or behind the cursor parks one tick ahead: its own slot
77 * is already drained and would otherwise wait out a full wheel turn. */
78 uint64_t target = dl_tick > cur ? dl_tick : cur + 1;
79 int lvl = timer_wheel_level(target - cur);
80 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
81 uint64_t slot_tick = target >> shift;
82
83 if (lvl == TIMER_WHEEL_LEVELS - 1) {
84 /* Beyond the wheel range: park on the farthest slot; the drain
85 * re-inserts it with the then-smaller distance. */
86 uint64_t far = (cur >> shift) + TIMER_WHEEL_SLOTS - 1;
87 if (slot_tick > far) slot_tick = far;
88 }
89
90 int slot = (int)(slot_tick & (TIMER_WHEEL_SLOTS - 1));
91
92 ccan_list_add_tail(&timer_th.wheel[lvl].slots[slot], &w->node);
93 timer_th.wheel[lvl].occupied |= UINT64_C(1) << slot;
94 w->wheel_lvl = (uint8_t)lvl;
95 w->wheel_slot = (uint8_t)slot;
96
97 if (w->data.timeout < timer_th.next_expiry) {
98 timer_th.next_expiry = w->data.timeout;
99 }
100}
101
102// Unlink a waiter from the wheel slot or the untimed list it is on.
103static void
104timer_wheel_del(struct rb_thread_sched_waiting *w)
105{
106 ccan_list_del_init(&w->node);
107
108 if (w->flags & thread_sched_waiting_timeout) {
109 struct timer_wheel_level *lv = &timer_th.wheel[w->wheel_lvl];
110 if (ccan_list_empty(&lv->slots[w->wheel_slot])) {
111 lv->occupied &= ~(UINT64_C(1) << w->wheel_slot);
112 }
113 }
114}
115
116/* A lower bound on the earliest deadline: the start time of the nearest
117 * occupied slot per level. Never later than any real deadline, so waking
118 * by it is at worst early, which is harmless. */
119static rb_hrtime_t
120timer_wheel_next_expiry(void)
121{
122 rb_hrtime_t best = TIMER_WHEEL_NO_EXPIRY;
123
124 for (int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
125 uint64_t occ = timer_th.wheel[lvl].occupied;
126 if (!occ) continue;
127
128 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
129 uint64_t cur_tick = timer_th.wheel_cursor_tick >> shift;
130 unsigned base = (unsigned)((cur_tick + 1) & (TIMER_WHEEL_SLOTS - 1));
131 // rotate so bit k = slot for tick cur_tick+1+k
132 uint64_t rot = (occ >> base) | (base ? (occ << (TIMER_WHEEL_SLOTS - base)) : 0);
133 uint64_t tick = cur_tick + 1 + timer_wheel_ctz64(rot);
134 rb_hrtime_t start = (rb_hrtime_t)(tick << shift) * RB_HRTIME_PER_MSEC * TIMER_WHEEL_TICK_MS;
135
136 if (start < best) best = start;
137 }
138
139 return best;
140}
141
142/* Drain every slot whose tick moved behind `now`, collecting due waiters
143 * onto `expired` and re-bucketing not-yet-due ones onto a finer level.
144 * timer_th.waiting_lock must be held. */
145static void
146timer_wheel_drain(rb_hrtime_t now, uint64_t now_tick, struct ccan_list_head *expired)
147{
148 uint64_t prev_tick = timer_th.wheel_cursor_tick;
149 timer_th.wheel_cursor_tick = now_tick; // re-inserts below map against the new cursor
150 /* A deadline inside the current tick parks one tick ahead, so its slot
151 * start is later than the deadline. Keep the exact value: the recompute
152 * below only knows slot starts, and next_expiry must not exceed a deadline. */
153 rb_hrtime_t reinserted = TIMER_WHEEL_NO_EXPIRY;
154
155 for (int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
156 int shift = lvl * TIMER_WHEEL_SLOT_BITS;
157 uint64_t from = prev_tick >> shift;
158 uint64_t to = now_tick >> shift;
159
160 if (to == from) break; // no boundary crossed; coarser levels crossed none either
161
162 uint64_t steps = to - from;
163 if (steps > TIMER_WHEEL_SLOTS) steps = TIMER_WHEEL_SLOTS;
164 struct timer_wheel_level *lv = &timer_th.wheel[lvl];
165
166 for (uint64_t tick = to - steps + 1; tick <= to; tick++) {
167 int slot = (int)(tick & (TIMER_WHEEL_SLOTS - 1));
168
169 if (!(lv->occupied & (UINT64_C(1) << slot))) continue;
170 lv->occupied &= ~(UINT64_C(1) << slot);
171
172 /* Detach first: a far-future waiter re-clamped by the insert below
173 * can land back on this very slot and must not be drained again. */
174 struct ccan_list_head pending;
175 ccan_list_head_init(&pending);
176 ccan_list_append_list(&pending, &lv->slots[slot]);
177
178 struct rb_thread_sched_waiting *w;
179 while ((w = ccan_list_pop(&pending, struct rb_thread_sched_waiting, node)) != NULL) {
180 if (timer_thread_check_exceed(w->data.timeout, now)) {
181 rb_thread_t *th = thread_sched_waiting_thread(w);
182
183 RUBY_DEBUG_LOG("wakeup th:%u", rb_th_serial(th));
184
185#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
186 // An fd+timeout waiter is also on its fd's waiter list.
187 timer_thread_unregister_waiting(th, w->data.fd, w->flags);
188#endif
189 /* flags stay set until the wakeup below takes them under
190 * the lock: a waiter whose flags are already cleared may
191 * run and re-register through this same `w`, which would
192 * relink the node we are still holding on `expired`. */
193 w->data.result = 0;
194
195 ccan_list_add_tail(expired, &w->node);
196 }
197 else {
198 /* arrived early: the distance shrank, so this lands on a
199 * finer level (or a farther slot of the coarsest one) */
200 if (w->data.timeout < reinserted) reinserted = w->data.timeout;
201 timer_wheel_insert(w);
202 }
203 }
204 }
205 }
206
207 timer_th.next_expiry = timer_wheel_next_expiry();
208 if (reinserted < timer_th.next_expiry) timer_th.next_expiry = reinserted;
209}
210
211/* Merge the wheel's next expiry into the poll timeout (ms; -1 = none).
212 * Checked even when the scheduler has other work (grq_cnt > 0). */
213static int
214timer_wheel_timeout(int timeout)
215{
216 rb_native_mutex_lock(&timer_th.waiting_lock);
217 {
218 if (timer_th.next_expiry != TIMER_WHEEL_NO_EXPIRY) {
219 rb_hrtime_t now = rb_hrtime_now();
220 rb_hrtime_t hrrel = rb_hrtime_sub(timer_th.next_expiry, now);
221
222 RUBY_DEBUG_LOG("now:%lu rel:%lu", (unsigned long)now, (unsigned long)hrrel);
223
224 rb_hrtime_t msec = (hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC;
225 // A deadline further away than INT_MAX ms must clamp, not truncate:
226 // a negative timeout would be an untimed epoll_wait.
227 int thread_timeout = msec > INT_MAX ? INT_MAX : (int)msec; // ms
228
229 // Use minimum of scheduler timeout and thread sleep timeout
230 if (timeout < 0 || thread_timeout < timeout) {
231 timeout = thread_timeout;
232 }
233 }
234 }
235 rb_native_mutex_unlock(&timer_th.waiting_lock);
236
237 return timeout;
238}
239
240static void
241timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th, uint32_t event_serial)
242{
243 if (sched->running != th && th->sched.event_serial == event_serial) {
244 thread_sched_to_ready_common(sched, th, true, false);
245 }
246}
247
248static void
249timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial)
250{
251 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
252 struct rb_thread_sched *sched = TH_SCHED(th);
253
254 thread_sched_lock(sched, th);
255 {
256 timer_thread_wakeup_thread_locked(sched, th, event_serial);
257 }
258 thread_sched_unlock(sched, th);
259}
260
261#define TIMEOUT_WAKE_BATCH 16
262
263static void
264timer_thread_check_timeout(rb_vm_t *vm)
265{
266 rb_hrtime_t now = rb_hrtime_now();
267 uint64_t now_tick = timer_wheel_tick(now);
268 struct ccan_list_head expired;
269
270 ccan_list_head_init(&expired);
271
272 struct timeout_wake { rb_thread_t *th; uint32_t serial; } batch[TIMEOUT_WAKE_BATCH];
273 bool more = true;
274
275 while (more) {
276 int n = 0;
277
278 rb_native_mutex_lock(&timer_th.waiting_lock);
279 {
280 // A second pass finds the cursor already at now_tick and drains nothing.
281 if (now_tick > timer_th.wheel_cursor_tick) {
282 timer_wheel_drain(now, now_tick, &expired);
283 }
284
285 struct rb_thread_sched_waiting *w;
286 while (n < TIMEOUT_WAKE_BATCH &&
287 (w = ccan_list_pop(&expired, struct rb_thread_sched_waiting, node)) != NULL) {
288 // Name the thread and its serial here, then release it: once the
289 // flags are clear the thread may run and re-register through `w`,
290 // and a serial read after that would match the new registration.
291 batch[n].th = thread_sched_waiting_thread(w);
292 batch[n].serial = w->data.event_serial;
293 w->flags = thread_sched_waiting_none;
294 n++;
295 }
296 more = !ccan_list_empty(&expired);
297 }
298 rb_native_mutex_unlock(&timer_th.waiting_lock);
299
300 for (int i = 0; i < n; i++) {
301 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
302 }
303 }
304}
305
306static bool
307timer_thread_cancel_waiting(rb_thread_t *th)
308{
309 bool canceled = false;
310
311 rb_native_mutex_lock(&timer_th.waiting_lock);
312 {
313 if (th->sched.waiting_reason.flags) {
314 canceled = true;
315 timer_wheel_del(&th->sched.waiting_reason);
316 timer_thread_unregister_waiting(th, th->sched.waiting_reason.data.fd, th->sched.waiting_reason.flags);
317 th->sched.waiting_reason.flags = thread_sched_waiting_none;
318 }
319 }
320 rb_native_mutex_unlock(&timer_th.waiting_lock);
321
322 return canceled;
323}
324
325static void
326ubf_event_waiting(void *ptr)
327{
328 rb_thread_t *th = (rb_thread_t *)ptr;
329 struct rb_thread_sched *sched = TH_SCHED(th);
330
331 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
332
333 VM_ASSERT(th->nt == NULL || !th_has_dedicated_nt(th));
334
335 // only once. it is safe because th->interrupt_lock is already acquired.
336 th->unblock.func = NULL;
337 th->unblock.arg = NULL;
338
339 thread_sched_lock(sched, th);
340 {
341 bool canceled = timer_thread_cancel_waiting(th);
342
343 if (sched->running == th) {
344 RUBY_DEBUG_LOG("not waiting yet");
345 }
346 else if (canceled) {
347 thread_sched_to_ready_common(sched, th, true, false);
348 }
349 else {
350 RUBY_DEBUG_LOG("already not waiting");
351 }
352 }
353 thread_sched_unlock(sched, th);
354}
355
356// Why timer_thread_register_waiting() did or did not take over the wait. Both
357// "not registered" cases used to share one value; only the first means ready.
358enum timer_thread_register_result {
359 timer_thread_registered, // the timer thread owns this wait now
360 timer_thread_already_ready, // no wait needed: the fd is ready, or no timeout
361 timer_thread_unavailable, // cannot be registered; the caller must fall back
362};
363
364static enum timer_thread_register_result
365timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial);
366
367// return how the wait ended; see enum thread_sched_wait_result
368static enum thread_sched_wait_result
369thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
370{
371 VM_ASSERT(!th_has_dedicated_nt(th)); // on SNT
372
373 volatile bool timedout = false, need_cancel = false;
374 volatile enum timer_thread_register_result reg = timer_thread_unavailable;
375
376 uint32_t event_serial = ++th->sched.event_serial; // overflow is okay
377
378
379 thread_sched_lock(sched, th);
380 {
381 // NOTE: there's a lock ordering inversion here with the ubf call, but it's benign.
382 if (ubf_set(th, ubf_event_waiting, (void *)th, NULL)) {
383 // Already interrupted: report an event so the caller retries and then
384 // processes the interrupt, as it always has.
385 thread_sched_unlock(sched, th);
386 return thread_sched_wait_event;
387 }
388
389 reg = timer_thread_register_waiting(th, fd, events, rel, event_serial);
390
391 if (reg == timer_thread_registered) {
392 RUBY_DEBUG_LOG("wait fd:%d", fd);
393
394 RB_VM_SAVE_MACHINE_CONTEXT(th);
395
396 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
397
398 if (th->sched.waiting_reason.flags == thread_sched_waiting_none) {
399 th->sched.event_serial++;
400 // timer thread has dequeued us already, but it won't try to wake us because we bumped our serial
401 }
402 else if (RUBY_VM_INTERRUPTED(th->ec)) {
403 th->sched.event_serial++; // make sure timer thread doesn't try to wake us
404 need_cancel = true;
405 }
406 else {
407 RUBY_DEBUG_LOG("sleep");
408
409 // A sleeper's status belongs to the caller: sleep_hrtime
410 // re-sleeps while it stays THREAD_STOPPED and only a waker may
411 // change it, as with native_cond_sleep on a dedicated nt. An
412 // io wait enters as THREAD_RUNNABLE and shows "sleep" while
413 // parked, as a dedicated nt's blocking region does.
414 enum rb_thread_status prev_status = th->status;
415 if (prev_status == THREAD_RUNNABLE) th->status = THREAD_STOPPED_FOREVER;
416 thread_sched_wakeup_next_thread(sched, th, true);
417 thread_sched_wait_running_turn(sched, th, true);
418 if (prev_status == THREAD_RUNNABLE) th->status = THREAD_RUNNABLE;
419
420 RUBY_DEBUG_LOG("wakeup");
421 }
422
423 timedout = th->sched.waiting_reason.data.result == 0;
424
425 if (need_cancel) {
426 timer_thread_cancel_waiting(th);
427 }
428 }
429 else {
430 // Ready right now, or not registerable at all -- only the former may
431 // be reported as readiness.
432 RUBY_DEBUG_LOG("did not wait fd:%d reg:%d", fd, (int)reg);
433 }
434 }
435 thread_sched_unlock(sched, th);
436
437 // if ubf triggered between sched unlock and ubf clear, sched->running == th here
438 ubf_clear(th, false);
439
440 VM_ASSERT(sched->running == th);
441
442 if (reg == timer_thread_unavailable) return thread_sched_wait_unavailable;
443 // A ready fd never registered, so it never timed out either.
444 if (reg == timer_thread_already_ready) return thread_sched_wait_event;
445 return timedout ? thread_sched_wait_timeout : thread_sched_wait_event;
446}
447
449
450static int
451get_sysconf_page_size(void)
452{
453 static long page_size = 0;
454
455 if (UNLIKELY(page_size == 0)) {
456 page_size = sysconf(_SC_PAGESIZE);
457 VM_ASSERT(page_size < INT_MAX);
458 }
459 return (int)page_size;
460}
461
462#define MSTACK_CHUNK_SIZE (512 * 1024 * 1024) // 512MB
463#define MSTACK_PAGE_SIZE get_sysconf_page_size()
464#define MSTACK_CHUNK_PAGE_NUM (MSTACK_CHUNK_SIZE / MSTACK_PAGE_SIZE - 1) // 1 is start redzone
465
466// 512MB chunk
467// 131,072 pages (> 65,536)
468// Head pages hold the chunk header (see start_page); stacks follow.
469
470/*
471 * <--> machine stack + vm stack
472 * ----------------------------------
473 * |HD...|RZ| ... |RZ| ... ... |RZ|
474 * <------------- 512MB ------------->
475 */
476
477static struct nt_stack_chunk_header {
478 struct nt_stack_chunk_header *prev_chunk;
479 struct nt_stack_chunk_header *prev_free_chunk;
480 // prev_free_chunk == NULL cannot double as the membership test: the free
481 // list's tail also has it NULL, and re-pushing the tail self-cycles it.
482 bool on_free_list;
483
484 uint16_t start_page;
485 uint16_t stack_count;
486 uint16_t uninitialized_stack_count;
487
488 uint16_t free_stack_pos;
489 uint16_t free_stack[];
490} *nt_stack_chunks = NULL,
491 *nt_free_stack_chunks = NULL;
492
493struct nt_machine_stack_footer {
494 struct nt_stack_chunk_header *ch;
495 size_t index;
496};
497
498static rb_nativethread_lock_t nt_machine_stack_lock = RB_NATIVETHREAD_LOCK_INIT;
499
500// The holder at the fork moment does not exist in the child; start over.
501static void
502nt_machine_stack_atfork(void)
503{
504 rb_native_mutex_initialize(&nt_machine_stack_lock);
505}
506
507#include <sys/mman.h>
508
509// Page-align the configured sizes: the layout puts a guard page and a
510// MAP_FIXED machine stack behind the VM stack, and both must land on page
511// boundaries whatever RUBY_THREAD_VM_STACK_SIZE and
512// RUBY_THREAD_MACHINE_STACK_SIZE hold (those align only to 4KB).
513static inline size_t
514nt_vm_stack_area(const rb_vm_t *vm)
515{
516 return (size_t)roomof(vm->default_params.thread_vm_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
517}
518
519static inline size_t
520nt_machine_stack_area(const rb_vm_t *vm)
521{
522 return (size_t)roomof(vm->default_params.thread_machine_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
523}
524
525// vm stack area + guard page + machine stack area
526static inline size_t
527nt_thread_stack_size(void)
528{
529 static size_t msz;
530 if (LIKELY(msz > 0)) return msz;
531
532 rb_vm_t *vm = GET_VM();
533 msz = nt_vm_stack_area(vm) + MSTACK_PAGE_SIZE + nt_machine_stack_area(vm);
534 return msz;
535}
536
537static struct nt_stack_chunk_header *
538nt_alloc_thread_stack_chunk(void)
539{
540 const char *m = (void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
541 if (m == MAP_FAILED) {
542 return NULL;
543 }
544
545 ruby_annotate_mmap(m, MSTACK_CHUNK_SIZE, "Ruby:nt_alloc_thread_stack_chunk");
546
547 size_t msz = nt_thread_stack_size();
548 int header_page_cnt = 1;
549 int stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
550 int ch_size = sizeof(struct nt_stack_chunk_header) + sizeof(uint16_t) * stack_count;
551
552 if (ch_size > MSTACK_PAGE_SIZE * header_page_cnt) {
553 header_page_cnt = (ch_size + MSTACK_PAGE_SIZE - 1) / MSTACK_PAGE_SIZE;
554 stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
555 }
556
557 VM_ASSERT(stack_count <= UINT16_MAX);
558
559 // Enable read/write for the header pages
560 if (mprotect((void *)m, (size_t)header_page_cnt * MSTACK_PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) {
561 munmap((void *)m, MSTACK_CHUNK_SIZE);
562 return NULL;
563 }
564
565 struct nt_stack_chunk_header *ch = (struct nt_stack_chunk_header *)m;
566
567 ch->start_page = header_page_cnt;
568 ch->prev_chunk = nt_stack_chunks;
569 ch->prev_free_chunk = nt_free_stack_chunks;
570 ch->on_free_list = true; // the caller makes it the free-list head
571 ch->uninitialized_stack_count = ch->stack_count = (uint16_t)stack_count;
572 ch->free_stack_pos = 0;
573
574 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);
575
576 return ch;
577}
578
579static void *
580nt_stack_chunk_get_stack_start(struct nt_stack_chunk_header *ch, size_t idx)
581{
582 const char *m = (char *)ch;
583 return (void *)(m + ch->start_page * MSTACK_PAGE_SIZE + idx * nt_thread_stack_size());
584}
585
586static struct nt_machine_stack_footer *
587nt_stack_chunk_get_msf(const rb_vm_t *vm, const char *mstack)
588{
589 // TODO: stack direction
590 const size_t msz = vm->default_params.thread_machine_stack_size;
591 return (struct nt_machine_stack_footer *)&mstack[msz - sizeof(struct nt_machine_stack_footer)];
592}
593
594static void
595nt_stack_chunk_get_stack(const rb_vm_t *vm, struct nt_stack_chunk_header *ch, size_t idx, void **vm_stack, void **machine_stack)
596{
597 // TODO: only support stack going down
598 // [VM ... <GUARD> machine stack ...]
599
600 const char *vstack, *mstack;
601 const char *guard_page;
602 vstack = nt_stack_chunk_get_stack_start(ch, idx);
603 guard_page = vstack + nt_vm_stack_area(vm);
604 mstack = guard_page + MSTACK_PAGE_SIZE;
605
606 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(vm, mstack);
607 msf->ch = ch;
608 msf->index = idx;
609
610#if 0
611 RUBY_DEBUG_LOG("msf:%p vstack:%p-%p guard_page:%p-%p mstack:%p-%p", msf,
612 vstack, (void *)(guard_page-1),
613 guard_page, (void *)(mstack-1),
614 mstack, (void *)(msf));
615#endif
616
617 *vm_stack = (void *)vstack;
618 *machine_stack = (void *)mstack;
619}
620
622static void
623nt_stack_chunk_dump(void)
624{
625 struct nt_stack_chunk_header *ch;
626 int i;
627
628 fprintf(stderr, "** nt_stack_chunks\n");
629 ch = nt_stack_chunks;
630 for (i=0; ch; i++, ch = ch->prev_chunk) {
631 fprintf(stderr, "%d %p free_pos:%d\n", i, (void *)ch, (int)ch->free_stack_pos);
632 }
633
634 fprintf(stderr, "** nt_free_stack_chunks\n");
635 ch = nt_free_stack_chunks;
636 for (i=0; ch; i++, ch = ch->prev_free_chunk) {
637 fprintf(stderr, "%d %p free_pos:%d\n", i, (void *)ch, (int)ch->free_stack_pos);
638 }
639}
640
641static int
642nt_alloc_stack(rb_vm_t *vm, void **vm_stack, void **machine_stack)
643{
644 int err = 0;
645
646 rb_native_mutex_lock(&nt_machine_stack_lock);
647 {
648 retry:
649 if (nt_free_stack_chunks) {
650 struct nt_stack_chunk_header *ch = nt_free_stack_chunks;
651 if (ch->free_stack_pos > 0) {
652 RUBY_DEBUG_LOG("free_stack_pos:%d", ch->free_stack_pos);
653 nt_stack_chunk_get_stack(vm, ch, ch->free_stack[--ch->free_stack_pos], vm_stack, machine_stack);
654 }
655 else if (ch->uninitialized_stack_count > 0) {
656 RUBY_DEBUG_LOG("uninitialized_stack_count:%d", ch->uninitialized_stack_count);
657
658 size_t idx = ch->stack_count - ch->uninitialized_stack_count--;
659
660 // The chunk was mapped PROT_NONE; enable the VM stack and
661 // machine stack pages, leaving the guard page as PROT_NONE.
662 char *stack_start = nt_stack_chunk_get_stack_start(ch, idx);
663 size_t vm_stack_area = nt_vm_stack_area(vm);
664 size_t mstack_size = nt_thread_stack_size() - vm_stack_area - MSTACK_PAGE_SIZE;
665 char *mstack_start = stack_start + vm_stack_area + MSTACK_PAGE_SIZE;
666
667 int mstack_flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE;
668#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
669 mstack_flags |= MAP_STACK;
670#endif
671
672 if (mprotect(stack_start, vm_stack_area, PROT_READ | PROT_WRITE) != 0 ||
673 mmap(mstack_start, mstack_size, PROT_READ | PROT_WRITE, mstack_flags, -1, 0) == MAP_FAILED) {
674 err = errno;
675 ch->uninitialized_stack_count++; // the slot was not consumed
676 }
677 else {
678 nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack);
679 }
680 }
681 else {
682 nt_free_stack_chunks = ch->prev_free_chunk;
683 ch->prev_free_chunk = NULL;
684 ch->on_free_list = false;
685 goto retry;
686 }
687 }
688 else {
689 struct nt_stack_chunk_header *p = nt_alloc_thread_stack_chunk();
690 if (p == NULL) {
691 err = errno;
692 }
693 else {
694 nt_free_stack_chunks = nt_stack_chunks = p;
695 goto retry;
696 }
697 }
698 }
699 rb_native_mutex_unlock(&nt_machine_stack_lock);
700
701 return err;
702}
703
704static void
705nt_madvise_free_or_dontneed(void *addr, size_t len)
706{
707 /* There is no real way to perform error handling here. Both MADV_FREE
708 * and MADV_DONTNEED are both documented to pretty much only return EINVAL
709 * for a huge variety of errors. It's indistinguishable if madvise fails
710 * because the parameters were bad, or because the kernel we're running on
711 * does not support the given advice. This kind of free-but-don't-unmap
712 * is best-effort anyway, so don't sweat it.
713 *
714 * n.b. A very common case of "the kernel doesn't support MADV_FREE and
715 * returns EINVAL" is running under the `rr` debugger; it makes all
716 * MADV_FREE calls return EINVAL. */
717
718#if defined(MADV_FREE)
719 int r = madvise(addr, len, MADV_FREE);
720 // Return on success, or else try MADV_DONTNEED
721 if (r == 0) return;
722#endif
723#if defined(MADV_DONTNEED)
724 madvise(addr, len, MADV_DONTNEED);
725#endif
726}
727
728static void
729nt_free_stack(void *mstack)
730{
731 if (!mstack) return;
732
733 rb_native_mutex_lock(&nt_machine_stack_lock);
734 {
735 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(GET_VM(), mstack);
736 struct nt_stack_chunk_header *ch = msf->ch;
737 int idx = (int)msf->index;
738 void *stack = nt_stack_chunk_get_stack_start(ch, idx);
739
740 RUBY_DEBUG_LOG("stack:%p mstack:%p ch:%p index:%d", stack, mstack, ch, idx);
741
742 if (!ch->on_free_list) {
743 ch->on_free_list = true;
744 ch->prev_free_chunk = nt_free_stack_chunks;
745 nt_free_stack_chunks = ch;
746 }
747 ch->free_stack[ch->free_stack_pos++] = idx;
748
749 // clear the stack pages
750 nt_madvise_free_or_dontneed(stack, nt_thread_stack_size());
751 }
752 rb_native_mutex_unlock(&nt_machine_stack_lock);
753}
754
755
756static int
757native_thread_check_and_create_shared(rb_vm_t *vm)
758{
759 bool need_to_make = false;
760
761 ractor_sched_lock(vm, NULL); // NULL: the timer thread also calls this
762 {
763 unsigned int schedulable_ractor_cnt = vm->ractor.cnt;
764 RUBY_ASSERT(schedulable_ractor_cnt >= 1);
765
766 if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads)
767 schedulable_ractor_cnt--; // do not need snt for main ractor
768
769 unsigned int snt_cnt = vm->ractor.sched.snt_cnt;
770 if (((int)snt_cnt < MINIMUM_SNT) ||
771 (snt_cnt < schedulable_ractor_cnt &&
772 snt_cnt < vm->ractor.sched.max_cpu)) {
773
774 RUBY_DEBUG_LOG("added snt:%u dnt:%u ractor_cnt:%u grq_cnt:%u",
775 vm->ractor.sched.snt_cnt,
776 vm->ractor.sched.dnt_cnt,
777 vm->ractor.cnt,
778 vm->ractor.sched.grq_cnt);
779
780 vm->ractor.sched.snt_cnt++;
781 need_to_make = true;
782 }
783 else {
784 RUBY_DEBUG_LOG("snt:%d ractor_cnt:%d", (int)vm->ractor.sched.snt_cnt, (int)vm->ractor.cnt);
785 }
786 }
787 ractor_sched_unlock(vm, NULL);
788
789 if (need_to_make) {
790 struct rb_native_thread *nt = native_thread_alloc();
791 nt->vm = vm;
792 int err = native_thread_create0(nt);
793 if (err) {
794 // Roll back, or this function would conclude forever that the
795 // pool is wide enough and never try again.
796 ractor_sched_lock(vm, NULL);
797 vm->ractor.sched.snt_cnt--;
798 ractor_sched_unlock(vm, NULL);
799 native_thread_destroy(nt);
800 }
801 return err;
802 }
803 else {
804 return 0;
805 }
806}
807
808// A coroutine thread's epilogue, run from thread_start_func_2 while th is
809// still valid. Everything that touches th happens here; co_start then only
810// makes the final transfer, touching the execution-owned tctx alone.
811static void
812coroutine_thread_terminated(rb_thread_t *th)
813{
814 struct rb_thread_context *tctx = (struct rb_thread_context *)th->sched.context;
815 struct rb_thread_sched *sched = TH_SCHED(th);
816 rb_ractor_t *r = th->ractor;
817 bool last = (th->invoke_type == thread_invoke_type_ractor_proc);
818 bool is_dnt = th_has_dedicated_nt(th);
819
820 // VM destruct tears down the heap and then unsets ruby_current_vm_ptr;
821 // this native thread still frees the dead context afterwards (the nt
822 // loop's reclaim uses ruby_xfree and the stack-pool geometry via
823 // GET_VM()). Make destruct wait until the reclaim finished. (Observed:
824 // an assert_separately child exiting right after a Ractor finished
825 // crashed at GET_VM()->default_params, offset 0x2600, on two arches.)
826 RUBY_ATOMIC_INC(th->vm->ractor.sched.winding_cnt);
827
828 rb_thread_t *wake_th;
829
830 // Leave the living set BEFORE handing over the scheduler slot: the
831 // removal's VM-lock work (ractor_check_blocking, a barrier join) then
832 // runs as an ordinary counted running thread. Afterwards th may be
833 // unreachable, but no GC can complete while th still owns the slot
834 // (a barrier waits for it to join), so the handoff below may keep
835 // touching th/sched.
836 //
837 // The Ractor's last thread is the exception and keeps the reverse
838 // order (below): its removal unlinks the Ractor itself, after which
839 // r/sched must not be touched. That order is safe only for it: with
840 // no successor, sched->running stays NULL, so the removal's VM lock
841 // never joins a barrier (vm_need_barrier requires a running thread).
842 VM_ASSERT(sched->running == th); // th owns the slot through the removal
843 if (!last) rb_ractor_living_threads_remove(r, th);
844
845 thread_sched_lock(sched, th);
846 {
847 // designate the successor (running = next from readyq, or NULL); for
848 // a dedicated nt (will_switch false) this also enqueues the Ractor.
849 thread_sched_to_dead_common(sched, th);
850
851 // Only the successor WE designated here may be enqueued by this
852 // epilogue (below). If readyq was empty, running is now NULL and a
853 // waker (e.g. the timer thread) that later installs a runnable
854 // thread enqueues the Ractor itself -- enqueuing "whatever is
855 // running" at that point would duplicate its entry. While running
856 // is non-NULL, nobody else re-assigns it, so wake_th stays valid
857 // until we enqueue.
858 wake_th = is_dnt ? NULL : sched->running;
859
860 tctx->nt = th->nt; // stash the final transfer target for co_start
861 native_thread_assign(NULL, th);
862 th->sched.context = NULL; // the wrapper's dfree must not reclaim tctx
863 }
864 thread_sched_unlock(sched, th);
865
866 if (last) {
867 // The reverse order is safe only with no successor: running == NULL
868 // means the removal's VM lock cannot join a barrier (vm_need_barrier).
869 VM_ASSERT(sched->running == NULL);
870 VM_ASSERT(wake_th == NULL);
871 // Last access to th/r: the removal may unlink the Ractor, after
872 // which the GC may collect th and r.
873 rb_ractor_living_threads_remove(r, th);
874 rb_current_ec_set(NULL); // TLS only; r may be collectable already
875 }
876 else {
877 rb_ractor_set_current_ec(r, NULL); // r alive: it has other threads
878
879 if (wake_th && wake_th->nt == NULL) {
880 // enqueue the successor designated above -- exactly once per
881 // "runnable but unserved" period, by its designator.
882 thread_sched_lock(sched, NULL);
883 ractor_sched_enq(wake_th->vm, r);
884 thread_sched_unlock(sched, NULL);
885 }
886 }
887}
888
889#ifdef __APPLE__
890# define co_start ruby_coroutine_start
891#else
892static
893#endif
894COROUTINE
895co_start(struct coroutine_context *from, struct coroutine_context *self)
896{
897#ifdef RUBY_ASAN_ENABLED
898 __sanitizer_finish_switch_fiber(self->fake_stack,
899 (const void**)&from->stack_base, &from->stack_size);
900#endif
901
902 rb_thread_t *th = (rb_thread_t *)self->argument;
903 struct rb_thread_sched *sched = TH_SCHED(th);
904 VM_ASSERT(th->nt != NULL);
905 VM_ASSERT(th == sched->running);
906 VM_ASSERT(sched->lock_owner == NULL);
907
908 // RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
909
910 thread_sched_set_locked(sched, th);
911 thread_sched_add_running_thread(TH_SCHED(th), th);
912 thread_sched_unlock(sched, th);
913 {
914 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_RESUMED, th);
915 call_thread_start_func_2(th);
916 }
917 // Thread is terminated. coroutine_thread_terminated (run from
918 // thread_start_func_2 while th was still valid) already left the living
919 // set and stashed the transfer target, so th / its Ractor may already be
920 // collected. Only tctx is touched here: mark it dead and transfer back to
921 // this native thread's own context, where the nt loop reclaims tctx.
922 struct rb_thread_context *tctx = (struct rb_thread_context *)self;
923 tctx->dead = true;
924 // Hand this context to the nt's loop, which reclaims it right after the
925 // final transfer returns from switch0.
926 tctx->nt->dead_co = &tctx->co;
927 coroutine_transfer0(&tctx->co, tctx->nt->nt_context, true);
928
929 rb_bug("unreachable");
930}
931
932static int
933native_thread_create_shared(rb_thread_t *th)
934{
935 // setup coroutine
936 rb_vm_t *vm = th->vm;
937 void *vm_stack = NULL, *machine_stack = NULL;
938 int err = nt_alloc_stack(vm, &vm_stack, &machine_stack);
939 if (err) return err;
940
941 VM_ASSERT(vm_stack < machine_stack);
942
943 // setup vm stack
944 size_t vm_stack_words = th->vm->default_params.thread_vm_stack_size/sizeof(VALUE);
945 rb_ec_initialize_vm_stack(th->ec, vm_stack, vm_stack_words);
946
947 // setup machine stack
948 size_t machine_stack_size = vm->default_params.thread_machine_stack_size - sizeof(struct nt_machine_stack_footer);
949 th->ec->machine.stack_start = (void *)((uintptr_t)machine_stack + machine_stack_size);
950 th->ec->machine.stack_maxsize = machine_stack_size; // TODO
951 th->sched.context_stack = machine_stack;
952 th->sched.context_stack_size = machine_stack_size;
953
954 struct rb_thread_context *tctx = ruby_xmalloc(sizeof(struct rb_thread_context));
955 tctx->stack = machine_stack;
956 tctx->dead = false;
957 th->sched.context = &tctx->co;
958 coroutine_initialize(&tctx->co, co_start, machine_stack, machine_stack_size);
959 tctx->co.argument = th;
960
961 RUBY_DEBUG_LOG("th:%u vm_stack:%p machine_stack:%p", rb_th_serial(th), vm_stack, machine_stack);
962
963 // Widen the pool before publishing th. Once ready, a Ractor's thread that
964 // runs to its end frees its own rb_thread_t (rb_ractor_postmortem_free),
965 // and the caller's create-failure path assumes th never became runnable.
966 int create_err = native_thread_check_and_create_shared(vm);
967 if (create_err) return create_err;
968
969 thread_sched_to_ready(TH_SCHED(th), th);
970 return 0;
971}
972
974#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
975
979
980#define FD_WAIT_IO_MASK (thread_sched_waiting_io_read | thread_sched_waiting_io_write)
981
982#define FDMAP_CHUNK_BITS 10
983#define FDMAP_CHUNK_SIZE (1u << FDMAP_CHUNK_BITS)
984#define FDMAP_CHUNK_MASK (FDMAP_CHUNK_SIZE - 1)
985
986// timer_th.waiting_lock must be held.
987static struct rb_fd_waiters *
988fd_waiters_lookup(int fd, bool create)
989{
990 if (fd < 0) return NULL;
991
992 unsigned int ci = (unsigned int)fd >> FDMAP_CHUNK_BITS;
993
994 if (ci >= timer_th.fdmap_nchunks) {
995 if (!create) return NULL;
996
997 unsigned int n = timer_th.fdmap_nchunks ? timer_th.fdmap_nchunks : 8;
998 while (n <= ci) n *= 2;
999
1000 // Only the chunk pointers are reallocated; the entries themselves never
1001 // move, so the list heads inside them stay valid.
1002 struct rb_fd_waiters **chunks = realloc(timer_th.fdmap_chunks, sizeof(*chunks) * n);
1003 if (chunks == NULL) rb_bug("fd_waiters_lookup: realloc failed");
1004
1005 for (unsigned int i = timer_th.fdmap_nchunks; i < n; i++) chunks[i] = NULL;
1006 timer_th.fdmap_chunks = chunks;
1007 timer_th.fdmap_nchunks = n;
1008 }
1009
1010 if (timer_th.fdmap_chunks[ci] == NULL) {
1011 if (!create) return NULL;
1012
1013 struct rb_fd_waiters *chunk = calloc(FDMAP_CHUNK_SIZE, sizeof(*chunk));
1014 if (chunk == NULL) rb_bug("fd_waiters_lookup: calloc failed");
1015
1016 for (unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) {
1017 ccan_list_head_init(&chunk[i].waiters);
1018 }
1019 timer_th.fdmap_chunks[ci] = chunk;
1020 }
1021
1022 return &timer_th.fdmap_chunks[ci][(unsigned int)fd & FDMAP_CHUNK_MASK];
1023}
1024
1025// timer_th.waiting_lock must be held.
1026static uint32_t
1027fd_waiters_union(struct rb_fd_waiters *e)
1028{
1029 uint32_t want = 0;
1030 struct rb_thread_sched_waiting *w;
1031
1032 ccan_list_for_each(&e->waiters, w, fd_node) {
1033 want |= (uint32_t)(w->flags & FD_WAIT_IO_MASK);
1034 }
1035 return want;
1036}
1037
1038// Events name an fd and the generation it was armed with, never a thread: a
1039// stale event then resolves to a table lookup instead of a freed thread.
1040static inline uint64_t
1041fd_event_tag(int fd, uint32_t generation)
1042{
1043 return ((uint64_t)generation << 32) | (uint32_t)fd;
1044}
1045
1046#define FD_EVENT_TAG_FD(tag) ((int)((tag) & 0xffffffffu))
1047#define FD_EVENT_TAG_GEN(tag) ((uint32_t)((tag) >> 32))
1048
1049// Make the backend match `want`. Returns false if the fd cannot be registered
1050// at all (closed, or unsupported by the backend), leaving the entry untouched.
1051// timer_th.waiting_lock must be held.
1052static bool
1053fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
1054{
1055 if (want == e->armed_flags) return true;
1056
1057#if HAVE_SYS_EVENT_H
1058 struct kevent ke[2];
1059 int n = 0;
1060 uint32_t add = want & ~e->armed_flags;
1061 uint32_t del = e->armed_flags & ~want;
1062 void *tag = (void *)(uintptr_t)fd_event_tag(fd, e->generation);
1063
1064 if (del & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); n++; }
1065 if (del & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); n++; }
1066
1067 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1068 // The fd may already be gone; that is not an error for a removal.
1069 if (errno != ENOENT && errno != EBADF) {
1070 perror("kevent");
1071 rb_bug("fd_waiters_arm/kevent delete failed (fd:%d errno:%d)", fd, errno);
1072 }
1073 }
1074
1075 n = 0;
1076 if (add & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_ADD, 0, 0, tag); n++; }
1077 if (add & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_ADD, 0, 0, tag); n++; }
1078
1079 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1080 switch (errno) {
1081 case EBADF:
1082 case EINVAL:
1083 return false;
1084 default:
1085 perror("kevent");
1086 rb_bug("fd_waiters_arm/kevent add failed (fd:%d errno:%d)", fd, errno);
1087 }
1088 }
1089#elif HAVE_SYS_EPOLL_H
1090 if (want == 0) {
1091 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
1092 switch (errno) {
1093 case EBADF:
1094 case ENOENT:
1095 // the fd is already closed or gone from the set
1096 break;
1097 default:
1098 perror("epoll_ctl");
1099 rb_bug("fd_waiters_arm/epoll_ctl del failed (fd:%d errno:%d)", fd, errno);
1100 }
1101 }
1102 // Anything epoll_wait already queued for the old arming is stale now.
1103 e->generation++;
1104 e->armed_flags = 0;
1105 return true;
1106 }
1107
1108 uint32_t epoll_events = 0;
1109 if (want & thread_sched_waiting_io_read) epoll_events |= EPOLLIN;
1110 if (want & thread_sched_waiting_io_write) epoll_events |= EPOLLOUT;
1111
1112 struct epoll_event event = {
1113 .events = epoll_events,
1114 .data = { .u64 = fd_event_tag(fd, e->generation) },
1115 };
1116
1117 int op = e->armed_flags ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
1118
1119 if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) {
1120 switch (errno) {
1121 case ENOENT:
1122 // Not registered after all: the fd was closed and reopened. Add it.
1123 if (op == EPOLL_CTL_MOD &&
1124 epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) {
1125 break;
1126 }
1127 return false;
1128 case EEXIST:
1129 // Likewise in the other direction.
1130 if (op == EPOLL_CTL_ADD &&
1131 epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &event) == 0) {
1132 break;
1133 }
1134 return false;
1135 case EBADF:
1136 case EPERM:
1137 // closed, or the fd does not support epoll
1138 return false;
1139 default:
1140 perror("epoll_ctl");
1141 rb_bug("fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op, errno);
1142 }
1143 }
1144#else
1145# error "neither kqueue nor epoll"
1146#endif
1147
1148 e->armed_flags = want;
1149 return true;
1150}
1151
1152static bool
1153fd_readable_nonblock(int fd)
1154{
1155 struct pollfd pfd = {
1156 .fd = fd,
1157 .events = POLLIN,
1158 };
1159 return poll(&pfd, 1, 0) != 0;
1160}
1161
1162static bool
1163fd_writable_nonblock(int fd)
1164{
1165 struct pollfd pfd = {
1166 .fd = fd,
1167 .events = POLLOUT,
1168 };
1169 return poll(&pfd, 1, 0) != 0;
1170}
1171
1172static void
1173verify_waiting_list(void)
1174{
1175#if VM_CHECK_MODE > 0
1176 struct rb_thread_sched_waiting *w;
1177
1178 for (int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
1179 const struct timer_wheel_level *lv = &timer_th.wheel[lvl];
1180
1181 for (int slot = 0; slot < TIMER_WHEEL_SLOTS; slot++) {
1182 bool occupied = (lv->occupied >> slot) & 1;
1183 VM_ASSERT(occupied == !ccan_list_empty(&lv->slots[slot]));
1184
1185 ccan_list_for_each(&lv->slots[slot], w, node) {
1186 VM_ASSERT(w->flags & thread_sched_waiting_timeout);
1187 VM_ASSERT(w->data.timeout != 0);
1188 VM_ASSERT(w->wheel_lvl == lvl);
1189 VM_ASSERT(w->wheel_slot == slot);
1190 }
1191 }
1192 }
1193
1194 ccan_list_for_each(&timer_th.waiting_untimed, w, node) {
1195 VM_ASSERT(!(w->flags & thread_sched_waiting_timeout));
1196 VM_ASSERT(w->data.timeout == 0);
1197 }
1198#endif
1199}
1200
1201#if HAVE_SYS_EVENT_H // kqueue helpers
1202
1203static enum thread_sched_waiting_flag
1204kqueue_translate_filter_to_flags(int16_t filter)
1205{
1206 switch (filter) {
1207 case EVFILT_READ:
1208 return thread_sched_waiting_io_read;
1209 case EVFILT_WRITE:
1210 return thread_sched_waiting_io_write;
1211 case EVFILT_TIMER:
1212 return thread_sched_waiting_timeout;
1213 default:
1214 rb_bug("kevent filter:%d not supported", filter);
1215 }
1216}
1217
1218static int
1219kqueue_wait(rb_vm_t *vm)
1220{
1221 struct timespec calculated_timeout;
1222 struct timespec *timeout = NULL;
1223 int timeout_ms = timer_thread_set_timeout(vm);
1224
1225 if (timeout_ms > 0) {
1226 calculated_timeout.tv_sec = timeout_ms / 1000;
1227 calculated_timeout.tv_nsec = (timeout_ms % 1000) * 1000000;
1228 timeout = &calculated_timeout;
1229 }
1230 else if (timeout_ms == 0) {
1231 // Relying on the absence of other members of struct timespec is not strictly portable,
1232 // and kevent needs a 0-valued timespec to mean immediate timeout.
1233 memset(&calculated_timeout, 0, sizeof(struct timespec));
1234 timeout = &calculated_timeout;
1235 }
1236
1237 return kevent(timer_th.event_fd, NULL, 0, timer_th.finished_events, KQUEUE_EVENTS_MAX, timeout);
1238}
1239
1240static void
1241kqueue_create(void)
1242{
1243 if ((timer_th.event_fd = kqueue()) == -1) rb_bug("kqueue creation failed (errno:%d)", errno);
1244 int flags = fcntl(timer_th.event_fd, F_GETFD);
1245 if (flags == -1) {
1246 rb_bug("kqueue GETFD failed (errno:%d)", errno);
1247 }
1248
1249 flags |= FD_CLOEXEC;
1250 if (fcntl(timer_th.event_fd, F_SETFD, flags) == -1) {
1251 rb_bug("kqueue SETFD failed (errno:%d)", errno);
1252 }
1253}
1254
1255#endif // HAVE_SYS_EVENT_H
1256
1257// return false if the fd is not waitable or not need to wait.
1258static enum timer_thread_register_result
1259timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial)
1260{
1261 RUBY_DEBUG_LOG("th:%u fd:%d flag:%d rel:%lu", rb_th_serial(th), fd, flags, rel ? (unsigned long)*rel : 0);
1262
1263 VM_ASSERT(th == NULL || TH_SCHED(th)->running == th);
1264 VM_ASSERT(flags != 0);
1265
1266 rb_hrtime_t abs = 0; // 0 means no timeout
1267
1268 if (rel) {
1269 if (*rel > 0) {
1270 flags |= thread_sched_waiting_timeout;
1271 }
1272 else {
1273 return timer_thread_already_ready; // zero timeout: nothing to wait for
1274 }
1275 }
1276
1277 if (flags & thread_sched_waiting_timeout) {
1278 VM_ASSERT(rel != NULL);
1279 abs = rb_hrtime_add(rb_hrtime_now(), *rel);
1280 }
1281
1282 if (flags & thread_sched_waiting_io_read) {
1283 if (!(flags & thread_sched_waiting_io_force) && fd_readable_nonblock(fd)) {
1284 RUBY_DEBUG_LOG("fd_readable_nonblock");
1285 return timer_thread_already_ready;
1286 }
1287 VM_ASSERT(fd >= 0);
1288 }
1289
1290 if (flags & thread_sched_waiting_io_write) {
1291 if (!(flags & thread_sched_waiting_io_force) && fd_writable_nonblock(fd)) {
1292 RUBY_DEBUG_LOG("fd_writable_nonblock");
1293 return timer_thread_already_ready;
1294 }
1295 VM_ASSERT(fd >= 0);
1296 }
1297
1298 rb_native_mutex_lock(&timer_th.waiting_lock);
1299 {
1300 if (flags & FD_WAIT_IO_MASK) {
1301 VM_ASSERT(th != NULL);
1302
1303 struct rb_fd_waiters *e = fd_waiters_lookup(fd, true);
1304
1305 // Arm the union of what this fd's waiters want, so a second waiter
1306 // on the same fd extends the arming instead of colliding with it.
1307 if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK))) {
1308 rb_native_mutex_unlock(&timer_th.waiting_lock);
1309 return timer_thread_unavailable;
1310 }
1311
1312 ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node);
1313 RUBY_DEBUG_LOG("armed fd:%d want:%u", fd, e->armed_flags);
1314 }
1315
1316 if (th) {
1317 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1318
1319 // setup waiting information
1320 {
1321 th->sched.waiting_reason.flags = flags;
1322 th->sched.waiting_reason.data.timeout = abs;
1323 th->sched.waiting_reason.data.fd = fd;
1324 th->sched.waiting_reason.data.result = 0;
1325 th->sched.waiting_reason.data.event_serial = event_serial;
1326 }
1327
1328 if (abs == 0) { // no timeout
1329 VM_ASSERT(!(flags & thread_sched_waiting_timeout));
1330 ccan_list_add_tail(&timer_th.waiting_untimed, &th->sched.waiting_reason.node);
1331 }
1332 else {
1333 RUBY_DEBUG_LOG("abs:%lu", (unsigned long)abs);
1334 VM_ASSERT(flags & thread_sched_waiting_timeout);
1335
1336 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1337 timer_wheel_insert(&th->sched.waiting_reason);
1338
1339 verify_waiting_list();
1340
1341 if (timer_th.next_expiry < prev_expiry) {
1342 // an earlier deadline than the timer thread is armed for
1343 timer_thread_wakeup_force();
1344 }
1345 }
1346 }
1347 else {
1348 VM_ASSERT(abs == 0);
1349 }
1350 }
1351 rb_native_mutex_unlock(&timer_th.waiting_lock);
1352
1353 return timer_thread_registered;
1354}
1355
1356// Drop `th` from its fd's waiter list and re-arm the backend for whoever is
1357// left. timer_th.waiting_lock must be held.
1358static void
1359timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags)
1360{
1361 if (!(th->sched.waiting_reason.flags & FD_WAIT_IO_MASK)) {
1362 return;
1363 }
1364
1365 RUBY_DEBUG_LOG("th:%u fd:%d", rb_th_serial(th), fd);
1366
1367 ccan_list_del_init(&th->sched.waiting_reason.fd_node);
1368
1369 struct rb_fd_waiters *e = fd_waiters_lookup(fd, false);
1370 if (e) {
1371 fd_waiters_arm(fd, e, fd_waiters_union(e));
1372 }
1373}
1374
1375// The timer thread's own wakeup pipe has no waiter: it stays armed forever and
1376// is recognised by its fd when an event arrives.
1377static void
1378timer_thread_arm_comm_pipe(void)
1379{
1380 int fd = timer_th.comm_fds[0];
1381
1382#if HAVE_SYS_EVENT_H
1383 struct kevent ke;
1384 EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, (void *)(uintptr_t)fd_event_tag(fd, 0));
1385 if (kevent(timer_th.event_fd, &ke, 1, NULL, 0, NULL) == -1) {
1386 rb_bug("timer_thread_arm_comm_pipe/kevent failed (errno:%d)", errno);
1387 }
1388#elif HAVE_SYS_EPOLL_H
1389 struct epoll_event event = {
1390 .events = EPOLLIN,
1391 .data = { .u64 = fd_event_tag(fd, 0) },
1392 };
1393 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
1394 rb_bug("timer_thread_arm_comm_pipe/epoll_ctl failed (errno:%d)", errno);
1395 }
1396#else
1397# error "neither kqueue nor epoll"
1398#endif
1399}
1400
1401static void
1402timer_thread_setup_mn(void)
1403{
1404#if HAVE_SYS_EVENT_H
1405 kqueue_create();
1406 RUBY_DEBUG_LOG("kqueue_fd:%d", timer_th.event_fd);
1407#else
1408 if ((timer_th.event_fd = epoll_create1(EPOLL_CLOEXEC)) == -1) rb_bug("epoll_create (errno:%d)", errno);
1409 RUBY_DEBUG_LOG("epoll_fd:%d", timer_th.event_fd);
1410#endif
1411 RUBY_DEBUG_LOG("comm_fds:%d/%d", timer_th.comm_fds[0], timer_th.comm_fds[1]);
1412
1413 timer_thread_arm_comm_pipe();
1414}
1415
1416static int
1417event_wait(rb_vm_t *vm)
1418{
1419#if HAVE_SYS_EVENT_H
1420 int r = kqueue_wait(vm);
1421#else
1422 int r = epoll_wait(timer_th.event_fd, timer_th.finished_events, EPOLL_EVENTS_MAX, timer_thread_set_timeout(vm));
1423#endif
1424 return r;
1425}
1426
1427// How many waiters one pass may unlink before it stops holding waiting_lock.
1428#define FD_WAKE_BATCH 16
1429
1430
1431// Deliver an fd event to every thread waiting for it. Waiters are unlinked
1432// under waiting_lock but woken after releasing it (the scheduler lock is taken
1433// before it, never after); event_serial, captured under the lock, guards them.
1434static void
1435timer_thread_wake_fd_waiters(int fd, uint32_t generation, uint32_t wake_flags, int result)
1436{
1437 struct { rb_thread_t *th; uint32_t serial; } batch[FD_WAKE_BATCH];
1438
1439 if (wake_flags == 0) return;
1440
1441 for (;;) {
1442 int n = 0;
1443 bool more = false;
1444
1445 rb_native_mutex_lock(&timer_th.waiting_lock);
1446 {
1447 struct rb_fd_waiters *e = fd_waiters_lookup(fd, false);
1448
1449 // A newer generation means the fd was disarmed after this event was
1450 // queued, and the number may name a different file by now.
1451 if (e != NULL && e->generation == generation) {
1452 struct rb_thread_sched_waiting *w, *nxt;
1453
1454 ccan_list_for_each_safe(&e->waiters, w, nxt, fd_node) {
1455 if (!(w->flags & wake_flags)) continue;
1456
1457 if (n == FD_WAKE_BATCH) {
1458 more = true;
1459 break;
1460 }
1461
1462 ccan_list_del_init(&w->fd_node);
1463 timer_wheel_del(w); // also leaves the timer wheel
1464
1465 w->flags = thread_sched_waiting_none;
1466 w->data.fd = -1;
1467 w->data.result = result;
1468
1469 batch[n].th = thread_sched_waiting_thread(w);
1470 batch[n].serial = w->data.event_serial;
1471 n++;
1472 }
1473
1474 // Re-arm for whoever is still waiting on this fd (nothing, if
1475 // they all just woke up).
1476 fd_waiters_arm(fd, e, fd_waiters_union(e));
1477 }
1478 }
1479 rb_native_mutex_unlock(&timer_th.waiting_lock);
1480
1481 for (int i = 0; i < n; i++) {
1482 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
1483 }
1484
1485 if (!more) break;
1486 }
1487}
1488
1489/*
1490 * The purpose of the timer thread:
1491 *
1492 * (1) Periodic checking
1493 * (1-1) Provide time slice for active NTs
1494 * (1-2) Check NT shortage
1495 * (1-3) Periodic UBF (global)
1496 * (1-4) Lazy GRQ deq start
1497 * (2) Receive notification
1498 * (2-1) async I/O termination
1499 * (2-2) timeout
1500 * (2-2-1) sleep(n)
1501 * (2-2-2) timeout(n), I/O, ...
1502 */
1503static void
1504timer_thread_polling(rb_vm_t *vm)
1505{
1506 int r = event_wait(vm);
1507
1508 RUBY_DEBUG_LOG("r:%d errno:%d", r, errno);
1509
1510 switch (r) {
1511 case 0: // timeout
1512 RUBY_DEBUG_LOG("timeout%s", "");
1513
1514 ractor_sched_lock(vm, NULL);
1515 {
1516 // (1-1) timeslice
1517 timer_thread_check_timeslice(vm);
1518
1519 // (1-4) lazy grq deq
1520 if (vm->ractor.sched.grq_cnt > 0) {
1521 RUBY_DEBUG_LOG("GRQ cnt: %u", vm->ractor.sched.grq_cnt);
1522 rb_native_cond_signal(&vm->ractor.sched.cond);
1523 }
1524 }
1525 ractor_sched_unlock(vm, NULL);
1526
1527 // (1-2)
1528 native_thread_check_and_create_shared(vm);
1529
1530 break;
1531
1532 case -1:
1533 switch (errno) {
1534 case EINTR:
1535 // simply retry
1536 break;
1537 default:
1538 perror("event_wait");
1539 rb_bug("event_wait errno:%d", errno);
1540 }
1541 break;
1542
1543 default:
1544 RUBY_DEBUG_LOG("%d event(s)", r);
1545
1546#if HAVE_SYS_EVENT_H
1547 for (int i=0; i<r; i++) {
1548 uint64_t tag = (uint64_t)(uintptr_t)timer_th.finished_events[i].udata;
1549 int fd = (int)timer_th.finished_events[i].ident;
1550 int16_t filter = timer_th.finished_events[i].filter;
1551
1552 if (fd == timer_th.comm_fds[0]) {
1553 RUBY_DEBUG_LOG("comm from fd:%d", timer_th.comm_fds[1]);
1554 consume_communication_pipe(timer_th.comm_fds[0]);
1555 continue;
1556 }
1557
1558 uint32_t wake_flags = kqueue_translate_filter_to_flags(filter) & FD_WAIT_IO_MASK;
1559 if (timer_th.finished_events[i].flags & (EV_EOF | EV_ERROR)) {
1560 wake_flags = FD_WAIT_IO_MASK; // end of file or error concerns everyone
1561 }
1562
1563 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, filter);
1564 }
1565#elif HAVE_SYS_EPOLL_H
1566 for (int i=0; i<r; i++) {
1567 uint64_t tag = timer_th.finished_events[i].data.u64;
1568 int fd = FD_EVENT_TAG_FD(tag);
1569 uint32_t events = timer_th.finished_events[i].events;
1570
1571 if (fd == timer_th.comm_fds[0]) {
1572 RUBY_DEBUG_LOG("comm from fd:%d", timer_th.comm_fds[1]);
1573 consume_communication_pipe(timer_th.comm_fds[0]);
1574 continue;
1575 }
1576
1577 RUBY_DEBUG_LOG("io event. fd:%d event:%s%s%s%s%s%s", fd,
1578 (events & EPOLLIN) ? "in/" : "",
1579 (events & EPOLLOUT) ? "out/" : "",
1580 (events & EPOLLRDHUP) ? "RDHUP/" : "",
1581 (events & EPOLLPRI) ? "pri/" : "",
1582 (events & EPOLLERR) ? "err/" : "",
1583 (events & EPOLLHUP) ? "hup/" : "");
1584
1585 uint32_t wake_flags = 0;
1586 if (events & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) wake_flags |= thread_sched_waiting_io_read;
1587 if (events & EPOLLOUT) wake_flags |= thread_sched_waiting_io_write;
1588 // An error or hangup ends every wait on this fd, in either direction.
1589 if (events & (EPOLLERR | EPOLLHUP)) wake_flags |= FD_WAIT_IO_MASK;
1590
1591 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, (int)events);
1592 }
1593#else
1594# error "neither kqueue nor epoll"
1595#endif
1596 }
1597}
1598
1599#endif // HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1600
1601#else // USE_MN_THREADS
1602
1603static void
1604timer_thread_setup_mn(void)
1605{
1606 // do nothing
1607}
1608
1609static void
1610timer_thread_polling(rb_vm_t *vm)
1611{
1612 int timeout = timer_thread_set_timeout(vm);
1613
1614 struct pollfd pfd = {
1615 .fd = timer_th.comm_fds[0],
1616 .events = POLLIN,
1617 };
1618
1619 int r = poll(&pfd, 1, timeout);
1620
1621 switch (r) {
1622 case 0: // timeout
1623 ractor_sched_lock(vm, NULL);
1624 {
1625 // (1-1) timeslice
1626 timer_thread_check_timeslice(vm);
1627 }
1628 ractor_sched_unlock(vm, NULL);
1629 break;
1630
1631 case -1: // error
1632 switch (errno) {
1633 case EINTR:
1634 // simply retry
1635 break;
1636 default:
1637 perror("poll");
1638 rb_bug("poll errno:%d", errno);
1639 break;
1640 }
1641
1642 case 1:
1643 consume_communication_pipe(timer_th.comm_fds[0]);
1644 break;
1645
1646 default:
1647 rb_bug("unreachbale");
1648 }
1649}
1650
1651static int
1652native_thread_create_shared(rb_thread_t *th)
1653{
1654 rb_bug("unreachable");
1655}
1656
1657static enum thread_sched_wait_result
1658thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
1659{
1660 rb_bug("unreachable");
1661}
1662
1663static int
1664timer_wheel_timeout(int timeout)
1665{
1666 return timeout; // no M:N threads, no timed waiters
1667}
1668
1669static void
1670timer_thread_check_timeout(rb_vm_t *vm)
1671{
1672 // no M:N threads, no timed waiters
1673}
1674
1675#endif // USE_MN_THREADS
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define RUBY_ATOMIC_INC(var)
Atomically increments the value pointed by var.
Definition atomic.h:214
int len
Length of the buffer.
Definition io.h:8
#define RUBY_INTERNAL_THREAD_EVENT_RESUMED
Triggered when a thread successfully acquired the GVL.
Definition thread.h:249
#define RUBY_INTERNAL_THREAD_EVENT_SUSPENDED
Triggered when a thread released the GVL.
Definition thread.h:256
#define RBIMPL_ATTR_MAYBE_UNUSED()
Wraps (or simulates) [[maybe_unused]]
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
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.
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40