Ruby 4.1.0dev (2026-08-29 revision 46e53809f5a7d05fed8b92ac6bf6a9ca8336a78f)
thread_pthread_mn.c (46e53809f5a7d05fed8b92ac6bf6a9ca8336a78f)
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 FD_WAIT_IO_MASK (thread_sched_waiting_io_read | thread_sched_waiting_io_write)
27
28// Guards the fd map entries and the flags of io waits for its fds. Whoever
29// clears an io wait's flags under its shard owns that wakeup.
30// Lock order: shard -> waiting_lock -> wake_pending_lock.
31static void
32fd_shard_lock(int fd)
33{
34 rb_native_mutex_lock(&timer_th.fd_shard_locks[(unsigned int)fd % IO_WAIT_SHARDS]);
35}
36
37static void
38fd_shard_unlock(int fd)
39{
40 rb_native_mutex_unlock(&timer_th.fd_shard_locks[(unsigned int)fd % IO_WAIT_SHARDS]);
41}
42
43#define TIMER_WHEEL_NO_EXPIRY RB_HRTIME_MAX
44#ifndef TIMER_WHEEL_TICK_MS
45#define TIMER_WHEEL_TICK_MS 1 // L0 slot width; coarser trades sleep accuracy for fewer drains
46#endif
47
48/* Timer wheel over the timed waiters. Every operation runs under
49 * timer_th.waiting_lock.
50 *
51 * Level L buckets deadlines into 64 slots of 64^L ms each; a deadline is
52 * placed by its distance from the drain cursor, so its slot is always
53 * strictly ahead of the cursor at that level. The drain refines a
54 * not-yet-due waiter onto a finer level instead of cascading whole slots,
55 * so one waiter moves at most once per level over its lifetime.
56 *
57 * This is the hierarchical timing wheel of Varghese & Lauck, "Hashed and
58 * Hierarchical Timing Wheels" (SOSP '87). */
59
60static uint64_t
61timer_wheel_tick(rb_hrtime_t hrt)
62{
63 return hrt / (RB_HRTIME_PER_MSEC * TIMER_WHEEL_TICK_MS);
64}
65
66static inline int
67timer_wheel_ctz64(uint64_t v)
68{
69#if defined(__GNUC__) || defined(__clang__)
70 return __builtin_ctzll(v);
71#else
72 int n = 0;
73 while (!(v & 1)) { v >>= 1; n++; }
74 return n;
75#endif
76}
77
78static int
79timer_wheel_level(uint64_t dist_ms)
80{
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;
85}
86
87static void
88timer_wheel_insert(struct rb_thread_sched_waiting *w)
89{
90 uint64_t dl_tick = timer_wheel_tick(w->data.timeout);
91 uint64_t cur = timer_th.wheel_cursor_tick;
92
93 /* A deadline at or behind the cursor parks one tick ahead: its own slot
94 * is already drained and would otherwise wait out a full wheel turn. */
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;
99
100 if (lvl == TIMER_WHEEL_LEVELS - 1) {
101 /* Beyond the wheel range: park on the farthest slot; the drain
102 * re-inserts it with the then-smaller distance. */
103 uint64_t far = (cur >> shift) + TIMER_WHEEL_SLOTS - 1;
104 if (slot_tick > far) slot_tick = far;
105 }
106
107 int slot = (int)(slot_tick & (TIMER_WHEEL_SLOTS - 1));
108
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;
113
114 if (w->data.timeout < timer_th.next_expiry) {
115 timer_th.next_expiry = w->data.timeout;
116 }
117}
118
119// Unlink a waiter from the wheel slot or the untimed list it is on.
120static void
121timer_wheel_del(struct rb_thread_sched_waiting *w)
122{
123 ccan_list_del_init(&w->node);
124
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);
129 }
130 }
131}
132
133/* A lower bound on the earliest deadline: the start time of the nearest
134 * occupied slot per level. Never later than any real deadline, so waking
135 * by it is at worst early, which is harmless. */
136static rb_hrtime_t
137timer_wheel_next_expiry(void)
138{
139 rb_hrtime_t best = TIMER_WHEEL_NO_EXPIRY;
140
141 for (int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
142 uint64_t occ = timer_th.wheel[lvl].occupied;
143 if (!occ) continue;
144
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));
148 // rotate so bit k = slot for tick cur_tick+1+k
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;
152
153 if (start < best) best = start;
154 }
155
156 return best;
157}
158
159/* Drain every slot whose tick moved behind `now`, collecting due waiters
160 * onto `expired` and re-bucketing not-yet-due ones onto a finer level.
161 * timer_th.waiting_lock must be held. */
162static void
163timer_wheel_drain(rb_hrtime_t now, uint64_t now_tick, struct ccan_list_head *expired)
164{
165 uint64_t prev_tick = timer_th.wheel_cursor_tick;
166 timer_th.wheel_cursor_tick = now_tick; // re-inserts below map against the new cursor
167 /* A deadline inside the current tick parks one tick ahead, so its slot
168 * start is later than the deadline. Keep the exact value: the recompute
169 * below only knows slot starts, and next_expiry must not exceed a deadline. */
170 rb_hrtime_t reinserted = TIMER_WHEEL_NO_EXPIRY;
171
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;
176
177 if (to == from) break; // no boundary crossed; coarser levels crossed none either
178
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];
182
183 for (uint64_t tick = to - steps + 1; tick <= to; tick++) {
184 int slot = (int)(tick & (TIMER_WHEEL_SLOTS - 1));
185
186 if (!(lv->occupied & (UINT64_C(1) << slot))) continue;
187 lv->occupied &= ~(UINT64_C(1) << slot);
188
189 /* Detach first: a far-future waiter re-clamped by the insert below
190 * can land back on this very slot and must not be drained again. */
191 struct ccan_list_head pending;
192 ccan_list_head_init(&pending);
193 ccan_list_append_list(&pending, &lv->slots[slot]);
194
195 struct rb_thread_sched_waiting *w;
196 while ((w = ccan_list_pop(&pending, struct rb_thread_sched_waiting, node)) != NULL) {
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)));
199
200 /* flags stay set until the wakeup takes them under the
201 * owning lock (the fd shard for io waits, this one
202 * otherwise): a waiter whose flags are already cleared may
203 * run and re-register through this same `w`, which would
204 * relink the node we are still holding on `expired`. */
205 ccan_list_add_tail(expired, &w->node);
206 }
207 else {
208 /* arrived early: the distance shrank, so this lands on a
209 * finer level (or a farther slot of the coarsest one) */
210 if (w->data.timeout < reinserted) reinserted = w->data.timeout;
211 timer_wheel_insert(w);
212 }
213 }
214 }
215 }
216
217 timer_th.next_expiry = timer_wheel_next_expiry();
218 if (reinserted < timer_th.next_expiry) timer_th.next_expiry = reinserted;
219}
220
221/* Merge the wheel's next expiry into the poll timeout (ms; -1 = none).
222 * Checked even when the scheduler has other work (grq_cnt > 0). */
223static int
224timer_wheel_timeout(int timeout)
225{
226 rb_native_mutex_lock(&timer_th.waiting_lock);
227 {
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);
231
232 RUBY_DEBUG_LOG("now:%lu rel:%lu", (unsigned long)now, (unsigned long)hrrel);
233
234 rb_hrtime_t msec = (hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC;
235 // A deadline further away than INT_MAX ms must clamp, not truncate:
236 // a negative timeout would be an untimed epoll_wait.
237 int thread_timeout = msec > INT_MAX ? INT_MAX : (int)msec; // ms
238
239 // Use minimum of scheduler timeout and thread sleep timeout
240 if (timeout < 0 || thread_timeout < timeout) {
241 timeout = thread_timeout;
242 }
243 }
244 }
245 rb_native_mutex_unlock(&timer_th.waiting_lock);
246
247 return timeout;
248}
249
250static void
251timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th, uint32_t event_serial)
252{
253 if (sched->running != th && th->sched.event_serial == event_serial) {
254 thread_sched_to_ready_common(sched, th, true, false);
255 }
256}
257
258static void
259timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial)
260{
261 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
262 struct rb_thread_sched *sched = TH_SCHED(th);
263
264 thread_sched_lock(sched, th);
265 {
266 timer_thread_wakeup_thread_locked(sched, th, event_serial);
267 }
268 thread_sched_unlock(sched, th);
269}
270
271#define TIMEOUT_WAKE_BATCH 16
272
273// One thread the timer thread is about to wake, with the serial it was armed at.
274struct timer_wake { rb_thread_t *th; uint32_t serial; };
275
276// Count a pending wake against a thread, so a dying thread can wait them out
277// (timer_thread_wake_fence). A count, not a flag: an expiry hold and an fd
278// event's wake can be pending on one thread at once. Take it while the lock
279// that pinned the thread (shard or waiting_lock) is still held.
280static void
281timer_wake_pending_inc(rb_thread_t *th)
282{
283 rb_native_mutex_lock(&timer_th.wake_pending_lock);
284 th->sched.wake_pending_cnt++;
285 rb_native_mutex_unlock(&timer_th.wake_pending_lock);
286}
287
288static void
289timer_wake_pending_dec(rb_thread_t *th)
290{
291 rb_native_mutex_lock(&timer_th.wake_pending_lock);
292 VM_ASSERT(th->sched.wake_pending_cnt > 0);
293 th->sched.wake_pending_cnt--;
294 rb_native_cond_broadcast(&timer_th.wake_pending_cond);
295 rb_native_mutex_unlock(&timer_th.wake_pending_lock);
296}
297
298static void
299timer_wake_pending_clear(struct timer_wake *batch, int n)
300{
301 for (int i = 0; i < n; i++) {
302 timer_wake_pending_dec(batch[i].th);
303 }
304}
305
306// Wait out pending wakes before a thread is freed: they would touch freed
307// memory, or wake a reused thread whose first serial matches a stale entry.
308static void
309timer_thread_wake_fence(rb_thread_t *th)
310{
311 if (!TIMER_THREAD_CREATED_P()) return;
312
313 rb_native_mutex_lock(&timer_th.wake_pending_lock);
314 while (th->sched.wake_pending_cnt > 0) {
315 rb_native_cond_wait(&timer_th.wake_pending_cond, &timer_th.wake_pending_lock);
316 }
317 rb_native_mutex_unlock(&timer_th.wake_pending_lock);
318}
319
320static void
321timer_thread_check_timeout(rb_vm_t *vm)
322{
323 rb_hrtime_t now = rb_hrtime_now();
324 uint64_t now_tick = timer_wheel_tick(now);
325 struct ccan_list_head expired;
326
327 ccan_list_head_init(&expired);
328
329 struct timer_wake batch[TIMEOUT_WAKE_BATCH];
330 bool more = true;
331
332 while (more) {
333 int n = 0;
334 struct { rb_thread_t *th; uint32_t serial; int fd; } io_claims[TIMEOUT_WAKE_BATCH];
335 int n_io = 0;
336
337 rb_native_mutex_lock(&timer_th.waiting_lock);
338 {
339 // A second pass finds the cursor already at now_tick and drains nothing.
340 if (now_tick > timer_th.wheel_cursor_tick) {
341 timer_wheel_drain(now, now_tick, &expired);
342 }
343
344 struct rb_thread_sched_waiting *w;
345 while (n + n_io < TIMEOUT_WAKE_BATCH &&
346 (w = ccan_list_pop(&expired, struct rb_thread_sched_waiting, node)) != NULL) {
347 // Name the thread and its serial here, then release it: once the
348 // flags are clear the thread may run and re-register through `w`,
349 // and a serial read after that would match the new registration.
350 // the pop leaves the node dangling; a concurrent claimer's
351 // wheel del (under this lock) must find it self-linked
352 ccan_list_node_init(&w->node);
353
354 if (w->flags & FD_WAIT_IO_MASK) {
355 // The fd shard owns these flags; claim below, once this
356 // lock is dropped. The pending count pins the thread: it
357 // was parked when we popped its node (a claimed entry
358 // leaves the wheel before its thread can wake).
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);
363 n_io++;
364 }
365 else {
366 // pin before the flags clear; see timer_thread_wake_fd_waiters
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;
371 w->data.result = 0;
372 n++;
373 }
374 }
375 more = !ccan_list_empty(&expired);
376 }
377 rb_native_mutex_unlock(&timer_th.waiting_lock);
378
379 for (int i = 0; i < n; i++) {
380 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
381 }
382 timer_wake_pending_clear(batch, n);
383
384 // The io entries race the fd event and the ubf for their flags.
385 for (int i = 0; i < n_io; i++) {
386 rb_thread_t *th = io_claims[i].th;
387 int fd = io_claims[i].fd;
388 bool claimed = false;
389
390 fd_shard_lock(fd);
391 {
392 struct rb_thread_sched_waiting *w = &th->sched.waiting_reason;
393
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;
399 w->data.result = 0;
400 claimed = true;
401 }
402 }
403 fd_shard_unlock(fd);
404
405 if (claimed) {
406 timer_thread_wakeup_thread(th, io_claims[i].serial);
407 }
408 timer_wake_pending_dec(th);
409 }
410 }
411}
412
413static bool
414timer_thread_cancel_waiting(rb_thread_t *th)
415{
416 struct rb_thread_sched_waiting *w = &th->sched.waiting_reason;
417
418 while (1) {
419 // Racy routing read; the claim is re-verified under the owning lock.
420 enum thread_sched_waiting_flag flags = w->flags;
421
422 if (flags == thread_sched_waiting_none) {
423 return false;
424 }
425 else if (flags & FD_WAIT_IO_MASK) {
426 int fd = w->data.fd;
427
428 fd_shard_lock(fd);
429 if ((w->flags & FD_WAIT_IO_MASK) && w->data.fd == fd) {
430 if (w->flags & thread_sched_waiting_timeout) {
431 rb_native_mutex_lock(&timer_th.waiting_lock);
432 timer_wheel_del(w);
433 rb_native_mutex_unlock(&timer_th.waiting_lock);
434 }
435 timer_thread_unregister_waiting(th, fd, w->flags);
436 w->flags = thread_sched_waiting_none;
437 fd_shard_unlock(fd);
438 return true;
439 }
440 fd_shard_unlock(fd);
441 }
442 else {
443 rb_native_mutex_lock(&timer_th.waiting_lock);
444 if (w->flags && !(w->flags & FD_WAIT_IO_MASK)) {
445 timer_wheel_del(w);
446 w->flags = thread_sched_waiting_none;
447 rb_native_mutex_unlock(&timer_th.waiting_lock);
448 return true;
449 }
450 rb_native_mutex_unlock(&timer_th.waiting_lock);
451 }
452 // lost the routing race; look again
453 }
454}
455
456static void
457ubf_event_waiting(void *ptr)
458{
459 rb_thread_t *th = (rb_thread_t *)ptr;
460 struct rb_thread_sched *sched = TH_SCHED(th);
461
462 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
463
464 VM_ASSERT(th->nt == NULL || !th_has_dedicated_nt(th));
465
466 // only once. it is safe because th->interrupt_lock is already acquired.
467 th->unblock.func = NULL;
468 th->unblock.arg = NULL;
469
470 thread_sched_lock(sched, th);
471 {
472 bool canceled = timer_thread_cancel_waiting(th);
473
474 if (sched->running == th) {
475 RUBY_DEBUG_LOG("not waiting yet");
476 }
477 else if (canceled) {
478 thread_sched_to_ready_common(sched, th, true, false);
479 }
480 else {
481 RUBY_DEBUG_LOG("already not waiting");
482 }
483 }
484 thread_sched_unlock(sched, th);
485}
486
487// Why timer_thread_register_waiting() did or did not take over the wait. Both
488// "not registered" cases used to share one value; only the first means ready.
489enum timer_thread_register_result {
490 timer_thread_registered, // the timer thread owns this wait now
491 timer_thread_already_ready, // no wait needed: the fd is ready, or no timeout
492 timer_thread_unavailable, // cannot be registered; the caller must fall back
493};
494
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);
497
498// Arm a timeout-only wake on the timer thread for a Ractor wait. Returns false
499// if the deadline has already passed, in which case nothing was registered.
500static bool
501ractor_sched_timeout_arm(rb_thread_t *th, const rb_hrtime_t *rel)
502{
503 rb_hrtime_t rel_copy = *rel;
504
505 return timer_thread_register_waiting(th, -1, thread_sched_waiting_timeout, &rel_copy,
506 ++th->sched.event_serial) == timer_thread_registered;
507}
508
509// Returns true if the timeout was still armed, i.e. it did not fire.
510static bool
511ractor_sched_timeout_disarm(rb_thread_t *th)
512{
513 return timer_thread_cancel_waiting(th);
514}
515
516// return how the wait ended; see enum thread_sched_wait_result
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)
519{
520 VM_ASSERT(!th_has_dedicated_nt(th)); // on SNT
521
522 volatile bool timedout = false, need_cancel = false;
523 volatile enum timer_thread_register_result reg = timer_thread_unavailable;
524
525 uint32_t event_serial = ++th->sched.event_serial; // overflow is okay
526
527
528 thread_sched_lock(sched, th);
529 {
530 // NOTE: there's a lock ordering inversion here with the ubf call, but it's benign.
531 if (ubf_set(th, ubf_event_waiting, (void *)th, NULL)) {
532 // Already interrupted: report an event so the caller retries and then
533 // processes the interrupt, as it always has.
534 thread_sched_unlock(sched, th);
535 return thread_sched_wait_event;
536 }
537
538 reg = timer_thread_register_waiting(th, fd, events, rel, event_serial);
539
540 if (reg == timer_thread_registered) {
541 RUBY_DEBUG_LOG("wait fd:%d", fd);
542
543 RB_VM_SAVE_MACHINE_CONTEXT(th);
544
545 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
546
547 if (th->sched.waiting_reason.flags == thread_sched_waiting_none) {
548 th->sched.event_serial++;
549 // timer thread has dequeued us already, but it won't try to wake us because we bumped our serial
550 }
551 else if (RUBY_VM_INTERRUPTED(th->ec)) {
552 th->sched.event_serial++; // make sure timer thread doesn't try to wake us
553 need_cancel = true;
554 }
555 else {
556 RUBY_DEBUG_LOG("sleep");
557
558 // A sleeper's status belongs to the caller: sleep_hrtime
559 // re-sleeps while it stays THREAD_STOPPED and only a waker may
560 // change it, as with native_cond_sleep on a dedicated nt. An
561 // io wait enters as THREAD_RUNNABLE and shows "sleep" while
562 // parked, as a dedicated nt's blocking region does.
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;
568
569 RUBY_DEBUG_LOG("wakeup");
570 }
571
572 timedout = th->sched.waiting_reason.data.result == 0;
573
574 if (need_cancel) {
575 timer_thread_cancel_waiting(th);
576 }
577 }
578 else {
579 // Ready right now, or not registerable at all -- only the former may
580 // be reported as readiness.
581 RUBY_DEBUG_LOG("did not wait fd:%d reg:%d", fd, (int)reg);
582 }
583 }
584 thread_sched_unlock(sched, th);
585
586 // if ubf triggered between sched unlock and ubf clear, sched->running == th here
587 ubf_clear(th, false);
588
589 VM_ASSERT(sched->running == th);
590
591 if (reg == timer_thread_unavailable) return thread_sched_wait_unavailable;
592 // A ready fd never registered, so it never timed out either.
593 if (reg == timer_thread_already_ready) return thread_sched_wait_event;
594 return timedout ? thread_sched_wait_timeout : thread_sched_wait_event;
595}
596
598
599static int
600get_sysconf_page_size(void)
601{
602 static long page_size = 0;
603
604 if (UNLIKELY(page_size == 0)) {
605 page_size = sysconf(_SC_PAGESIZE);
606 VM_ASSERT(page_size < INT_MAX);
607 }
608 return (int)page_size;
609}
610
611#define MSTACK_CHUNK_SIZE (512 * 1024 * 1024) // 512MB
612#define MSTACK_PAGE_SIZE get_sysconf_page_size()
613#define MSTACK_CHUNK_PAGE_NUM (MSTACK_CHUNK_SIZE / MSTACK_PAGE_SIZE - 1) // 1 is start redzone
614
615// 512MB chunk
616// 131,072 pages (> 65,536)
617// Head pages hold the chunk header (see start_page); stacks follow.
618
619/*
620 * <--> machine stack + vm stack
621 * ----------------------------------
622 * |HD...|RZ| ... |RZ| ... ... |RZ|
623 * <------------- 512MB ------------->
624 */
625
626static struct nt_stack_chunk_header {
627 struct nt_stack_chunk_header *prev_chunk;
628 struct nt_stack_chunk_header *prev_free_chunk;
629 // prev_free_chunk == NULL cannot double as the membership test: the free
630 // list's tail also has it NULL, and re-pushing the tail self-cycles it.
631 bool on_free_list;
632
633 uint16_t start_page;
634 uint16_t stack_count;
635 uint16_t uninitialized_stack_count;
636
637 uint16_t free_stack_pos;
638 uint16_t free_stack[];
639} *nt_stack_chunks = NULL,
640 *nt_free_stack_chunks = NULL;
641
642struct nt_machine_stack_footer {
643 struct nt_stack_chunk_header *ch;
644 size_t index;
645};
646
647static rb_nativethread_lock_t nt_machine_stack_lock = RB_NATIVETHREAD_LOCK_INIT;
648
649// The holder at the fork moment does not exist in the child; start over.
650static void
651nt_machine_stack_atfork(void)
652{
653 rb_native_mutex_initialize(&nt_machine_stack_lock);
654}
655
656#include <sys/mman.h>
657
658// Page-align the configured sizes: the layout puts a guard page and a
659// MAP_FIXED machine stack behind the VM stack, and both must land on page
660// boundaries whatever RUBY_THREAD_VM_STACK_SIZE and
661// RUBY_THREAD_MACHINE_STACK_SIZE hold (those align only to 4KB).
662static inline size_t
663nt_vm_stack_area(const rb_vm_t *vm)
664{
665 return (size_t)roomof(vm->default_params.thread_vm_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
666}
667
668static inline size_t
669nt_machine_stack_area(const rb_vm_t *vm)
670{
671 return (size_t)roomof(vm->default_params.thread_machine_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
672}
673
674// vm stack area + guard page + machine stack area
675static inline size_t
676nt_thread_stack_size(void)
677{
678 static size_t msz;
679 if (LIKELY(msz > 0)) return msz;
680
681 rb_vm_t *vm = GET_VM();
682 msz = nt_vm_stack_area(vm) + MSTACK_PAGE_SIZE + nt_machine_stack_area(vm);
683 return msz;
684}
685
686static struct nt_stack_chunk_header *
687nt_alloc_thread_stack_chunk(void)
688{
689 const char *m = (void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
690 if (m == MAP_FAILED) {
691 return NULL;
692 }
693
694 ruby_annotate_mmap(m, MSTACK_CHUNK_SIZE, "Ruby:nt_alloc_thread_stack_chunk");
695
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;
700
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;
704 }
705
706 VM_ASSERT(stack_count <= UINT16_MAX);
707
708 // Enable read/write for the header pages
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);
711 return NULL;
712 }
713
714 struct nt_stack_chunk_header *ch = (struct nt_stack_chunk_header *)m;
715
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; // the caller makes it the free-list head
720 ch->uninitialized_stack_count = ch->stack_count = (uint16_t)stack_count;
721 ch->free_stack_pos = 0;
722
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);
724
725 return ch;
726}
727
728static void *
729nt_stack_chunk_get_stack_start(struct nt_stack_chunk_header *ch, size_t idx)
730{
731 const char *m = (char *)ch;
732 return (void *)(m + ch->start_page * MSTACK_PAGE_SIZE + idx * nt_thread_stack_size());
733}
734
735static struct nt_machine_stack_footer *
736nt_stack_chunk_get_msf(const rb_vm_t *vm, const char *mstack)
737{
738 // TODO: stack direction
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)];
741}
742
743static void
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)
745{
746 // TODO: only support stack going down
747 // [VM ... <GUARD> machine stack ...]
748
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;
754
755 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(vm, mstack);
756 msf->ch = ch;
757 msf->index = idx;
758
759#if 0
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));
764#endif
765
766 *vm_stack = (void *)vstack;
767 *machine_stack = (void *)mstack;
768}
769
771static void
772nt_stack_chunk_dump(void)
773{
774 struct nt_stack_chunk_header *ch;
775 int i;
776
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);
781 }
782
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);
787 }
788}
789
790static int
791nt_alloc_stack(rb_vm_t *vm, void **vm_stack, void **machine_stack)
792{
793 int err = 0;
794
795 rb_native_mutex_lock(&nt_machine_stack_lock);
796 {
797 retry:
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);
803 }
804 else if (ch->uninitialized_stack_count > 0) {
805 RUBY_DEBUG_LOG("uninitialized_stack_count:%d", ch->uninitialized_stack_count);
806
807 size_t idx = ch->stack_count - ch->uninitialized_stack_count--;
808
809 // The chunk was mapped PROT_NONE; enable the VM stack and
810 // machine stack pages, leaving the guard page as PROT_NONE.
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;
815
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;
819#endif
820
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) {
823 err = errno;
824 ch->uninitialized_stack_count++; // the slot was not consumed
825 }
826 else {
827 nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack);
828 }
829 }
830 else {
831 nt_free_stack_chunks = ch->prev_free_chunk;
832 ch->prev_free_chunk = NULL;
833 ch->on_free_list = false;
834 goto retry;
835 }
836 }
837 else {
838 struct nt_stack_chunk_header *p = nt_alloc_thread_stack_chunk();
839 if (p == NULL) {
840 err = errno;
841 }
842 else {
843 nt_free_stack_chunks = nt_stack_chunks = p;
844 goto retry;
845 }
846 }
847 }
848 rb_native_mutex_unlock(&nt_machine_stack_lock);
849
850 return err;
851}
852
853static void
854nt_madvise_free_or_dontneed(void *addr, size_t len)
855{
856 /* There is no real way to perform error handling here. Both MADV_FREE
857 * and MADV_DONTNEED are both documented to pretty much only return EINVAL
858 * for a huge variety of errors. It's indistinguishable if madvise fails
859 * because the parameters were bad, or because the kernel we're running on
860 * does not support the given advice. This kind of free-but-don't-unmap
861 * is best-effort anyway, so don't sweat it.
862 *
863 * n.b. A very common case of "the kernel doesn't support MADV_FREE and
864 * returns EINVAL" is running under the `rr` debugger; it makes all
865 * MADV_FREE calls return EINVAL. */
866
867#if defined(MADV_FREE)
868 int r = madvise(addr, len, MADV_FREE);
869 // Return on success, or else try MADV_DONTNEED
870 if (r == 0) return;
871#endif
872#if defined(MADV_DONTNEED)
873 madvise(addr, len, MADV_DONTNEED);
874#endif
875}
876
877static void
878nt_free_stack(void *mstack)
879{
880 if (!mstack) return;
881
882 rb_native_mutex_lock(&nt_machine_stack_lock);
883 {
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);
888
889 RUBY_DEBUG_LOG("stack:%p mstack:%p ch:%p index:%d", stack, mstack, ch, idx);
890
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;
895 }
896 ch->free_stack[ch->free_stack_pos++] = idx;
897
898 // clear the stack pages
899 nt_madvise_free_or_dontneed(stack, nt_thread_stack_size());
900 }
901 rb_native_mutex_unlock(&nt_machine_stack_lock);
902}
903
904
905static int
906native_thread_check_and_create_shared(rb_vm_t *vm)
907{
908 bool need_to_make = false;
909
910 ractor_sched_lock(vm, NULL); // NULL: the timer thread also calls this
911 {
912 unsigned int schedulable_ractor_cnt = vm->ractor.cnt;
913 RUBY_ASSERT(schedulable_ractor_cnt >= 1);
914
915 if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads)
916 schedulable_ractor_cnt--; // do not need snt for main ractor
917
918 // CAS keeps a concurrent rejoin from pushing snt_cnt past the cap
919 rb_atomic_t snt_cnt = RUBY_ATOMIC_LOAD(vm->ractor.sched.snt_cnt);
920 while (((int)snt_cnt < MINIMUM_SNT) ||
921 (snt_cnt < schedulable_ractor_cnt &&
922 snt_cnt < vm->ractor.sched.max_cpu)) {
923 rb_atomic_t prev = RUBY_ATOMIC_CAS(vm->ractor.sched.snt_cnt, snt_cnt, snt_cnt + 1);
924 if (prev == snt_cnt) {
925 need_to_make = true;
926 break;
927 }
928 snt_cnt = prev;
929 }
930
931 if (need_to_make) {
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,
935 vm->ractor.cnt,
936 vm->ractor.sched.grq_cnt);
937 }
938 else {
939 RUBY_DEBUG_LOG("snt:%d ractor_cnt:%d", (int)vm->ractor.sched.snt_cnt, (int)vm->ractor.cnt);
940 }
941 }
942 ractor_sched_unlock(vm, NULL);
943
944 if (need_to_make) {
945 struct rb_native_thread *nt = native_thread_alloc();
946 nt->vm = vm;
947 int err = native_thread_create0(nt);
948 if (err) {
949 // Roll back, or this function would conclude forever that the
950 // pool is wide enough and never try again.
951 ractor_sched_lock(vm, NULL);
952 RUBY_ATOMIC_DEC(vm->ractor.sched.snt_cnt);
953 ractor_sched_unlock(vm, NULL);
954 native_thread_destroy(nt);
955 }
956 return err;
957 }
958 else {
959 return 0;
960 }
961}
962
963// A coroutine thread's epilogue, run from thread_start_func_2 while th is
964// still valid. Everything that touches th happens here; co_start then only
965// makes the final transfer, touching the execution-owned tctx alone.
966static void
967coroutine_thread_terminated(rb_thread_t *th)
968{
969 struct rb_thread_context *tctx = (struct rb_thread_context *)th->sched.context;
970 struct rb_thread_sched *sched = TH_SCHED(th);
971 rb_ractor_t *r = th->ractor;
972 bool last = (th->invoke_type == thread_invoke_type_ractor_proc);
973 bool is_dnt = th_has_dedicated_nt(th);
974
975 // VM destruct tears down the heap and then unsets ruby_current_vm_ptr;
976 // this native thread still frees the dead context afterwards (the nt
977 // loop's reclaim uses ruby_xfree and the stack-pool geometry via
978 // GET_VM()). Make destruct wait until the reclaim finished. (Observed:
979 // an assert_separately child exiting right after a Ractor finished
980 // crashed at GET_VM()->default_params, offset 0x2600, on two arches.)
981 rb_vm_t *const vm = th->vm; // survives th; the tail below must not read th
982 RUBY_ATOMIC_INC(vm->ractor.sched.winding_cnt);
983
984 rb_thread_t *wake_th;
985 bool wake_mn = false;
986
987 // Leave the living set here, while th is still barrier-registered and no
988 // successor can run: the GC's root scan walks r->threads.set without the
989 // Ractor lock, so the unlink must not race with it. Off the set th would
990 // be unreachable although the handoff below keeps using it (and the GC can
991 // run: to_dead_common() deregisters th, so no barrier waits for it) --
992 // dying_th keeps it marked until its last use.
993 VM_ASSERT(sched->running == th); // th owns the slot through the handoff
994 if (!last) {
995 RUBY_ATOMIC_PTR_SET(r->threads.dying_th, th);
996 rb_ractor_living_threads_remove(r, th);
997 }
998
999 thread_sched_lock(sched, th);
1000 {
1001 // designate the successor (running = next from readyq, or NULL); for
1002 // a dedicated nt (will_switch false) this also enqueues the Ractor.
1003 thread_sched_to_dead_common(sched, th);
1004
1005 // Only the successor WE designated here may be enqueued by this
1006 // epilogue (below). If readyq was empty, running is now NULL and a
1007 // waker (e.g. the timer thread) that later installs a runnable
1008 // thread enqueues the Ractor itself -- enqueuing "whatever is
1009 // running" at that point would duplicate its entry.
1010 wake_th = is_dnt ? NULL : sched->running;
1011 // Read wake_th->nt under the lock: a dedicated successor was already
1012 // woken by to_dead_common and may die (freeing wake_th) as soon as we
1013 // unlock. An M:N successor (nt == NULL) cannot run or be assigned an
1014 // nt before our enqueue below, so the value cannot go stale.
1015 wake_mn = (wake_th != NULL && wake_th->nt == NULL);
1016
1017 tctx->nt = th->nt; // stash the final transfer target for co_start
1018 native_thread_assign(NULL, th);
1019 th->sched.context = NULL; // the wrapper's dfree must not reclaim tctx
1020
1021 if (!last) {
1022 // Still under the sched lock: a successor (even a dedicated one
1023 // woken by to_dead_common) starts by taking it, so it cannot
1024 // observe or overwrite these until we unlock. th was last used
1025 // above and running_ec no longer points into it; now it may be
1026 // collected.
1027 rb_ractor_set_current_ec(r, NULL); // r alive: it has other threads
1028 VM_ASSERT(RUBY_ATOMIC_PTR_LOAD(r->threads.dying_th) == th);
1029 RUBY_ATOMIC_PTR_SET(r->threads.dying_th, NULL);
1030 }
1031 }
1032 if (last) {
1033 thread_sched_unlock(sched, th); // th is still on the living set here
1034
1035 VM_ASSERT(sched->running == NULL);
1036 VM_ASSERT(wake_th == NULL);
1037 // Last access to th/r: the removal may unlink the Ractor, after
1038 // which the GC may collect th and r.
1039 rb_ractor_living_threads_remove(r, th);
1040 rb_current_ec_set(NULL); // TLS only; r may be collectable already
1041 }
1042 else {
1043 // th lost its root at the clear above; the plain unlock's debug log
1044 // would read th->serial.
1045 thread_sched_unlock_no_log(sched, th);
1046
1047 if (wake_mn) {
1048 // enqueue the successor designated above -- exactly once per
1049 // "runnable but unserved" period, by its designator.
1050 thread_sched_lock(sched, NULL);
1051 ractor_sched_enq(vm, r);
1052 thread_sched_unlock(sched, NULL);
1053 }
1054 }
1055}
1056
1057#ifdef __APPLE__
1058# define co_start ruby_coroutine_start
1059#else
1060static
1061#endif
1062COROUTINE
1063co_start(struct coroutine_context *from, struct coroutine_context *self)
1064{
1065#ifdef RUBY_ASAN_ENABLED
1066 __sanitizer_finish_switch_fiber(self->fake_stack,
1067 (const void**)&from->stack_base, &from->stack_size);
1068#endif
1069
1070 rb_thread_t *th = (rb_thread_t *)self->argument;
1071 struct rb_thread_sched *sched = TH_SCHED(th);
1072 VM_ASSERT(th->nt != NULL);
1073 VM_ASSERT(th == sched->running);
1074 VM_ASSERT(sched->lock_owner == NULL);
1075
1076 // RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
1077
1078 thread_sched_set_locked(sched, th);
1079 thread_sched_add_running_thread(TH_SCHED(th), th);
1080 thread_sched_unlock(sched, th);
1081 {
1082 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_RESUMED, th);
1083 call_thread_start_func_2(th);
1084 }
1085 // Thread is terminated. coroutine_thread_terminated (run from
1086 // thread_start_func_2 while th was still valid) already left the living
1087 // set and stashed the transfer target, so th / its Ractor may already be
1088 // collected. Only tctx is touched here: mark it dead and transfer back to
1089 // this native thread's own context, where the nt loop reclaims tctx.
1090 struct rb_thread_context *tctx = (struct rb_thread_context *)self;
1091 tctx->dead = true;
1092 // Hand this context to the nt's loop, which reclaims it right after the
1093 // final transfer returns from switch0.
1094 tctx->nt->dead_co = &tctx->co;
1095 coroutine_transfer0(&tctx->co, tctx->nt->nt_context, true);
1096
1097 rb_bug("unreachable");
1098}
1099
1100static int
1101native_thread_create_shared(rb_thread_t *th)
1102{
1103 // setup coroutine
1104 rb_vm_t *vm = th->vm;
1105 void *vm_stack = NULL, *machine_stack = NULL;
1106 int err = nt_alloc_stack(vm, &vm_stack, &machine_stack);
1107 if (err) return err;
1108
1109 VM_ASSERT(vm_stack < machine_stack);
1110
1111 // setup vm 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);
1114
1115 // setup machine stack
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; // TODO
1119 th->sched.context_stack = machine_stack;
1120 th->sched.context_stack_size = machine_stack_size;
1121
1122 struct rb_thread_context *tctx = ruby_xmalloc(sizeof(struct rb_thread_context));
1123 tctx->stack = machine_stack;
1124 tctx->dead = false;
1125 tctx->nt = NULL;
1126 th->sched.context = &tctx->co;
1127 coroutine_initialize(&tctx->co, co_start, machine_stack, machine_stack_size);
1128 tctx->co.argument = th;
1129
1130 RUBY_DEBUG_LOG("th:%u vm_stack:%p machine_stack:%p", rb_th_serial(th), vm_stack, machine_stack);
1131
1132 // Widen the pool before publishing th. Once ready, a Ractor's thread that
1133 // runs to its end frees its own rb_thread_t (rb_ractor_postmortem_free),
1134 // and the caller's create-failure path assumes th never became runnable.
1135 int create_err = native_thread_check_and_create_shared(vm);
1136 if (create_err) return create_err;
1137
1138 thread_sched_to_ready(TH_SCHED(th), th);
1139 return 0;
1140}
1141
1143#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1144
1148
1149// (FD_WAIT_IO_MASK and the fd shard helpers are defined near the top.)
1150
1151#define FDMAP_CHUNK_BITS 10
1152#define FDMAP_CHUNK_SIZE (1u << FDMAP_CHUNK_BITS)
1153#define FDMAP_CHUNK_MASK (FDMAP_CHUNK_SIZE - 1)
1154
1155// Callable under any fd shard lock: a chunk spans every shard, so the chunk
1156// table is not guarded by them; slots install by CAS and are never freed.
1157static struct rb_fd_waiters *
1158fd_waiters_lookup(int fd, bool create)
1159{
1160 if (fd < 0) return NULL;
1161
1162 unsigned int ci = (unsigned int)fd >> FDMAP_CHUNK_BITS;
1163 if (ci >= FDMAP_MAX_CHUNKS) return NULL; // the caller falls back to a blocking wait
1164
1165 struct rb_fd_waiters *chunk = RUBY_ATOMIC_PTR_LOAD(timer_th.fdmap_chunks[ci]);
1166
1167 if (chunk == NULL) {
1168 if (!create) return NULL;
1169
1170 chunk = calloc(FDMAP_CHUNK_SIZE, sizeof(*chunk));
1171 if (chunk == NULL) rb_bug("fd_waiters_lookup: calloc failed");
1172
1173 for (unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) {
1174 ccan_list_head_init(&chunk[i].waiters);
1175 }
1176
1177 struct rb_fd_waiters *prev = RUBY_ATOMIC_PTR_CAS(timer_th.fdmap_chunks[ci], NULL, chunk);
1178 if (prev != NULL) {
1179 free(chunk); // another shard installed it first
1180 chunk = prev;
1181 }
1182 }
1183
1184 return &chunk[(unsigned int)fd & FDMAP_CHUNK_MASK];
1185}
1186
1187// The fd's shard lock must be held.
1188static uint32_t
1189fd_waiters_union(struct rb_fd_waiters *e)
1190{
1191 uint32_t want = 0;
1192 struct rb_thread_sched_waiting *w;
1193
1194 ccan_list_for_each(&e->waiters, w, fd_node) {
1195 want |= (uint32_t)(w->flags & FD_WAIT_IO_MASK);
1196 }
1197 return want;
1198}
1199
1200// Events name an fd and the generation it was armed with, never a thread: a
1201// stale event then resolves to a table lookup instead of a freed thread.
1202static inline uint64_t
1203fd_event_tag(int fd, uint32_t generation)
1204{
1205 return ((uint64_t)generation << 32) | (uint32_t)fd;
1206}
1207
1208#define FD_EVENT_TAG_FD(tag) ((int)((tag) & 0xffffffffu))
1209#define FD_EVENT_TAG_GEN(tag) ((uint32_t)((tag) >> 32))
1210
1211// Make the backend match `want`. Returns false if the fd cannot be registered
1212// at all (closed, or unsupported by the backend), leaving the entry untouched.
1213// The fd's shard lock must be held.
1214static bool
1215fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want)
1216{
1217 if (want == e->armed_flags) return true;
1218
1219#if HAVE_SYS_EVENT_H
1220 struct kevent ke[2];
1221 int n = 0;
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);
1225
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++; }
1228
1229 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1230 // The fd may already be gone; that is not an error for a removal.
1231 if (errno != ENOENT && errno != EBADF) {
1232 perror("kevent");
1233 rb_bug("fd_waiters_arm/kevent delete failed (fd:%d errno:%d)", fd, errno);
1234 }
1235 }
1236
1237 n = 0;
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++; }
1240
1241 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1242 switch (errno) {
1243 case EBADF:
1244 case EINVAL:
1245 return false;
1246 default:
1247 perror("kevent");
1248 rb_bug("fd_waiters_arm/kevent add failed (fd:%d errno:%d)", fd, errno);
1249 }
1250 }
1251#elif HAVE_SYS_EPOLL_H
1252 if (want == 0) {
1253 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
1254 switch (errno) {
1255 case EBADF:
1256 case ENOENT:
1257 // the fd is already closed or gone from the set
1258 break;
1259 default:
1260 perror("epoll_ctl");
1261 rb_bug("fd_waiters_arm/epoll_ctl del failed (fd:%d errno:%d)", fd, errno);
1262 }
1263 }
1264 // Anything epoll_wait already queued for the old arming is stale now.
1265 e->generation++;
1266 e->armed_flags = 0;
1267 return true;
1268 }
1269
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;
1273
1274 struct epoll_event event = {
1275 .events = epoll_events,
1276 .data = { .u64 = fd_event_tag(fd, e->generation) },
1277 };
1278
1279 int op = e->armed_flags ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
1280
1281 if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) {
1282 switch (errno) {
1283 case ENOENT:
1284 // Not registered after all: the fd was closed and reopened. Add it.
1285 if (op == EPOLL_CTL_MOD &&
1286 epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) {
1287 break;
1288 }
1289 return false;
1290 case EEXIST:
1291 // Likewise in the other direction.
1292 if (op == EPOLL_CTL_ADD &&
1293 epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &event) == 0) {
1294 break;
1295 }
1296 return false;
1297 case EBADF:
1298 case EPERM:
1299 // closed, or the fd does not support epoll
1300 return false;
1301 default:
1302 perror("epoll_ctl");
1303 rb_bug("fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op, errno);
1304 }
1305 }
1306#else
1307# error "neither kqueue nor epoll"
1308#endif
1309
1310 e->armed_flags = want;
1311 return true;
1312}
1313
1314static bool
1315fd_readable_nonblock(int fd)
1316{
1317 struct pollfd pfd = {
1318 .fd = fd,
1319 .events = POLLIN,
1320 };
1321 return poll(&pfd, 1, 0) != 0;
1322}
1323
1324static bool
1325fd_writable_nonblock(int fd)
1326{
1327 struct pollfd pfd = {
1328 .fd = fd,
1329 .events = POLLOUT,
1330 };
1331 return poll(&pfd, 1, 0) != 0;
1332}
1333
1334static void
1335verify_waiting_list(void)
1336{
1337#if VM_CHECK_MODE > 0
1338 struct rb_thread_sched_waiting *w;
1339
1340 for (int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
1341 const struct timer_wheel_level *lv = &timer_th.wheel[lvl];
1342
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]));
1346
1347 ccan_list_for_each(&lv->slots[slot], w, node) {
1348 // an io entry's flags belong to its fd shard: do not read them here
1349 if (!(w->flags & FD_WAIT_IO_MASK)) {
1350 VM_ASSERT(w->flags & thread_sched_waiting_timeout);
1351 VM_ASSERT(w->data.timeout != 0);
1352 }
1353 VM_ASSERT(w->wheel_lvl == lvl);
1354 VM_ASSERT(w->wheel_slot == slot);
1355 }
1356 }
1357 }
1358#endif
1359}
1360
1361#if HAVE_SYS_EVENT_H // kqueue helpers
1362
1363static enum thread_sched_waiting_flag
1364kqueue_translate_filter_to_flags(int16_t filter)
1365{
1366 switch (filter) {
1367 case EVFILT_READ:
1368 return thread_sched_waiting_io_read;
1369 case EVFILT_WRITE:
1370 return thread_sched_waiting_io_write;
1371 case EVFILT_TIMER:
1372 return thread_sched_waiting_timeout;
1373 default:
1374 rb_bug("kevent filter:%d not supported", filter);
1375 }
1376}
1377
1378static int
1379kqueue_wait(rb_vm_t *vm)
1380{
1381 struct timespec calculated_timeout;
1382 struct timespec *timeout = NULL;
1383 int timeout_ms = timer_thread_set_timeout(vm);
1384
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;
1389 }
1390 else if (timeout_ms == 0) {
1391 // Relying on the absence of other members of struct timespec is not strictly portable,
1392 // and kevent needs a 0-valued timespec to mean immediate timeout.
1393 memset(&calculated_timeout, 0, sizeof(struct timespec));
1394 timeout = &calculated_timeout;
1395 }
1396
1397 return kevent(timer_th.event_fd, NULL, 0, timer_th.finished_events, KQUEUE_EVENTS_MAX, timeout);
1398}
1399
1400static void
1401kqueue_create(void)
1402{
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);
1405 if (flags == -1) {
1406 rb_bug("kqueue GETFD failed (errno:%d)", errno);
1407 }
1408
1409 flags |= FD_CLOEXEC;
1410 if (fcntl(timer_th.event_fd, F_SETFD, flags) == -1) {
1411 rb_bug("kqueue SETFD failed (errno:%d)", errno);
1412 }
1413}
1414
1415#endif // HAVE_SYS_EVENT_H
1416
1417// return false if the fd is not waitable or not need to wait.
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)
1420{
1421 RUBY_DEBUG_LOG("th:%u fd:%d flag:%d rel:%lu", rb_th_serial(th), fd, flags, rel ? (unsigned long)*rel : 0);
1422
1423 VM_ASSERT(th == NULL || TH_SCHED(th)->running == th);
1424 VM_ASSERT(flags != 0);
1425
1426 rb_hrtime_t abs = 0; // 0 means no timeout
1427
1428 if (rel) {
1429 if (*rel > 0) {
1430 flags |= thread_sched_waiting_timeout;
1431 }
1432 else {
1433 return timer_thread_already_ready; // zero timeout: nothing to wait for
1434 }
1435 }
1436
1437 if (flags & thread_sched_waiting_timeout) {
1438 VM_ASSERT(rel != NULL);
1439 abs = rb_hrtime_add(rb_hrtime_now(), *rel);
1440 }
1441
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;
1446 }
1447 VM_ASSERT(fd >= 0);
1448 }
1449
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;
1454 }
1455 VM_ASSERT(fd >= 0);
1456 }
1457
1458 if (flags & FD_WAIT_IO_MASK) {
1459 fd_shard_lock(fd);
1460 {
1461 struct rb_fd_waiters *e = fd_waiters_lookup(fd, true);
1462
1463 if (e == NULL) { // fd beyond the map: fall back to a blocking wait
1464 fd_shard_unlock(fd);
1465 return timer_thread_unavailable;
1466 }
1467
1468 // Arm the union of what this fd's waiters want, so a second waiter
1469 // on the same fd extends the arming instead of colliding with it.
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;
1473 }
1474
1475 if (th) {
1476 ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node);
1477
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;
1484
1485 if (abs != 0) {
1486 RUBY_DEBUG_LOG("abs:%lu", (unsigned long)abs);
1487 VM_ASSERT(flags & thread_sched_waiting_timeout);
1488
1489 rb_native_mutex_lock(&timer_th.waiting_lock);
1490 {
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) {
1495 // an earlier deadline than the timer thread is armed for
1496 timer_thread_wakeup_force();
1497 }
1498 }
1499 rb_native_mutex_unlock(&timer_th.waiting_lock);
1500 }
1501 }
1502 RUBY_DEBUG_LOG("armed fd:%d want:%u", fd, e->armed_flags);
1503 }
1504 fd_shard_unlock(fd);
1505 }
1506 else if (th) {
1507 // fd-less timed wait: the wheel lock owns it end to end
1508 VM_ASSERT(abs != 0 && (flags & thread_sched_waiting_timeout));
1509
1510 rb_native_mutex_lock(&timer_th.waiting_lock);
1511 {
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;
1518
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();
1524 }
1525 }
1526 rb_native_mutex_unlock(&timer_th.waiting_lock);
1527 }
1528 else {
1529 VM_ASSERT(abs == 0);
1530 }
1531
1532 return timer_thread_registered;
1533}
1534
1535// Drop `th` from its fd's waiter list and re-arm the backend for whoever is
1536// left. The fd's shard lock must be held.
1537static void
1538timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags)
1539{
1540 if (!(th->sched.waiting_reason.flags & FD_WAIT_IO_MASK)) {
1541 return;
1542 }
1543
1544 RUBY_DEBUG_LOG("th:%u fd:%d", rb_th_serial(th), fd);
1545
1546 ccan_list_del_init(&th->sched.waiting_reason.fd_node);
1547
1548 struct rb_fd_waiters *e = fd_waiters_lookup(fd, false);
1549 if (e) {
1550 fd_waiters_arm(fd, e, fd_waiters_union(e));
1551 }
1552}
1553
1554// The timer thread's own wakeup pipe has no waiter: it stays armed forever and
1555// is recognised by its fd when an event arrives.
1556static void
1557timer_thread_arm_comm_pipe(void)
1558{
1559 int fd = timer_th.comm_fds[0];
1560
1561#if HAVE_SYS_EVENT_H
1562 struct kevent ke;
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);
1566 }
1567#elif HAVE_SYS_EPOLL_H
1568 struct epoll_event event = {
1569 .events = EPOLLIN,
1570 .data = { .u64 = fd_event_tag(fd, 0) },
1571 };
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);
1574 }
1575#else
1576# error "neither kqueue nor epoll"
1577#endif
1578}
1579
1580static void
1581timer_thread_setup_mn(void)
1582{
1583#if HAVE_SYS_EVENT_H
1584 kqueue_create();
1585 RUBY_DEBUG_LOG("kqueue_fd:%d", timer_th.event_fd);
1586#else
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);
1589#endif
1590 RUBY_DEBUG_LOG("comm_fds:%d/%d", timer_th.comm_fds[0], timer_th.comm_fds[1]);
1591
1592 timer_thread_arm_comm_pipe();
1593}
1594
1595static int
1596event_wait(rb_vm_t *vm)
1597{
1598#if HAVE_SYS_EVENT_H
1599 int r = kqueue_wait(vm);
1600#else
1601 int r = epoll_wait(timer_th.event_fd, timer_th.finished_events, EPOLL_EVENTS_MAX, timer_thread_set_timeout(vm));
1602#endif
1603 return r;
1604}
1605
1606// How many waiters one pass may unlink before it stops holding waiting_lock.
1607#define FD_WAKE_BATCH 16
1608
1609
1610// Deliver an fd event to every thread waiting for it. Waiters are unlinked
1611// under waiting_lock but woken after releasing it (the scheduler lock is taken
1612// before it, never after); event_serial, captured under the lock, guards them.
1613static void
1614timer_thread_wake_fd_waiters(int fd, uint32_t generation, uint32_t wake_flags, int result)
1615{
1616 struct timer_wake batch[FD_WAKE_BATCH];
1617
1618 if (wake_flags == 0) return;
1619
1620 while (1) {
1621 int n = 0;
1622 bool more = false;
1623
1624 fd_shard_lock(fd);
1625 {
1626 struct rb_fd_waiters *e = fd_waiters_lookup(fd, false);
1627
1628 // A newer generation means the fd was disarmed after this event was
1629 // queued, and the number may name a different file by now.
1630 if (e != NULL && e->generation == generation) {
1631 struct rb_thread_sched_waiting *w, *nxt;
1632
1633 ccan_list_for_each_safe(&e->waiters, w, nxt, fd_node) {
1634 if (!(w->flags & wake_flags)) continue;
1635
1636 if (n == FD_WAKE_BATCH) {
1637 more = true;
1638 break;
1639 }
1640
1641 ccan_list_del_init(&w->fd_node);
1642
1643 if (w->flags & thread_sched_waiting_timeout) {
1644 // also leaves the timer wheel (or the expiry pass's
1645 // batch, whose claim will then find the flags gone)
1646 rb_native_mutex_lock(&timer_th.waiting_lock);
1647 timer_wheel_del(w);
1648 rb_native_mutex_unlock(&timer_th.waiting_lock);
1649 }
1650
1651 // The pin must be visible before the flags clear: a waiter
1652 // that sees the clear may skip parking, finish and die,
1653 // and the fence only waits on the pending count.
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);
1657 n++;
1658
1659 w->flags = thread_sched_waiting_none;
1660 w->data.fd = -1;
1661 w->data.result = result;
1662 }
1663
1664 // Re-arm for whoever is still waiting on this fd (nothing, if
1665 // they all just woke up).
1666 fd_waiters_arm(fd, e, fd_waiters_union(e));
1667 }
1668 }
1669 fd_shard_unlock(fd);
1670
1671 for (int i = 0; i < n; i++) {
1672 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
1673 }
1674 timer_wake_pending_clear(batch, n);
1675
1676 if (!more) break;
1677 }
1678}
1679
1680/*
1681 * The purpose of the timer thread:
1682 *
1683 * (1) Periodic checking
1684 * (1-1) Provide time slice for active NTs
1685 * (1-2) Check NT shortage
1686 * (1-3) Periodic UBF (global)
1687 * (1-4) Lazy GRQ deq start
1688 * (2) Receive notification
1689 * (2-1) async I/O termination
1690 * (2-2) timeout
1691 * (2-2-1) sleep(n)
1692 * (2-2-2) timeout(n), I/O, ...
1693 */
1694static void
1695timer_thread_polling(rb_vm_t *vm)
1696{
1697 int r = event_wait(vm);
1698
1699 RUBY_DEBUG_LOG("r:%d errno:%d", r, errno);
1700
1701 switch (r) {
1702 case 0: // timeout
1703 RUBY_DEBUG_LOG("timeout%s", "");
1704
1705 ractor_sched_lock(vm, NULL);
1706 {
1707 // (1-1) timeslice
1708 timer_thread_check_timeslice(vm);
1709
1710 // (1-4) lazy grq deq
1711 if (vm->ractor.sched.grq_cnt > 0) {
1712 RUBY_DEBUG_LOG("GRQ cnt: %u", vm->ractor.sched.grq_cnt);
1713 rb_native_cond_signal(&vm->ractor.sched.cond);
1714 }
1715 }
1716 ractor_sched_unlock(vm, NULL);
1717
1718 // (1-2)
1719 native_thread_check_and_create_shared(vm);
1720
1721 break;
1722
1723 case -1:
1724 switch (errno) {
1725 case EINTR:
1726 // simply retry
1727 break;
1728 default:
1729 perror("event_wait");
1730 rb_bug("event_wait errno:%d", errno);
1731 }
1732 break;
1733
1734 default:
1735 RUBY_DEBUG_LOG("%d event(s)", r);
1736
1737#if HAVE_SYS_EVENT_H
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;
1742
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]);
1746 continue;
1747 }
1748
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; // end of file or error concerns everyone
1752 }
1753
1754 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, filter);
1755 }
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;
1761
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]);
1765 continue;
1766 }
1767
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/" : "");
1775
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;
1779 // An error or hangup ends every wait on this fd, in either direction.
1780 if (events & (EPOLLERR | EPOLLHUP)) wake_flags |= FD_WAIT_IO_MASK;
1781
1782 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, (int)events);
1783 }
1784#else
1785# error "neither kqueue nor epoll"
1786#endif
1787 }
1788}
1789
1790#endif // HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1791
1792#else // USE_MN_THREADS
1793
1794static void
1795timer_thread_setup_mn(void)
1796{
1797 // do nothing
1798}
1799
1800static void
1801timer_thread_polling(rb_vm_t *vm)
1802{
1803 int timeout = timer_thread_set_timeout(vm);
1804
1805 struct pollfd pfd = {
1806 .fd = timer_th.comm_fds[0],
1807 .events = POLLIN,
1808 };
1809
1810 int r = poll(&pfd, 1, timeout);
1811
1812 switch (r) {
1813 case 0: // timeout
1814 ractor_sched_lock(vm, NULL);
1815 {
1816 // (1-1) timeslice
1817 timer_thread_check_timeslice(vm);
1818 }
1819 ractor_sched_unlock(vm, NULL);
1820 break;
1821
1822 case -1: // error
1823 switch (errno) {
1824 case EINTR:
1825 // simply retry
1826 break;
1827 default:
1828 perror("poll");
1829 rb_bug("poll errno:%d", errno);
1830 break;
1831 }
1832
1833 case 1:
1834 consume_communication_pipe(timer_th.comm_fds[0]);
1835 break;
1836
1837 default:
1838 rb_bug("unreachbale");
1839 }
1840}
1841
1842static int
1843native_thread_create_shared(rb_thread_t *th)
1844{
1845 rb_bug("unreachable");
1846}
1847
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)
1850{
1851 rb_bug("unreachable");
1852}
1853
1854// Without the wheel every thread is dedicated, so a Ractor wait takes its
1855// deadline on its own condvar and never reaches these.
1856static bool
1857ractor_sched_timeout_arm(rb_thread_t *th, const rb_hrtime_t *rel)
1858{
1859 rb_bug("unreachable");
1860}
1861
1862static bool
1863ractor_sched_timeout_disarm(rb_thread_t *th)
1864{
1865 rb_bug("unreachable");
1866}
1867
1868static int
1869timer_wheel_timeout(int timeout)
1870{
1871 return timeout; // no M:N threads, no timed waiters
1872}
1873
1874static void
1875timer_thread_wake_fence(rb_thread_t *th)
1876{
1877 // no timer wheel, no wake batches
1878}
1879
1880static void
1881timer_thread_check_timeout(rb_vm_t *vm)
1882{
1883 // no M:N threads, no timed waiters
1884}
1885
1886#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_PTR_LOAD(var)
Identical to RUBY_ATOMIC_LOAD, except it expects its arguments are void*.
Definition atomic.h:338
#define RUBY_ATOMIC_INC(var)
Atomically increments the value pointed by var.
Definition atomic.h:214
#define RUBY_ATOMIC_PTR_CAS(var, oldval, newval)
Identical to RUBY_ATOMIC_CAS, except it expects its arguments are void*.
Definition atomic.h:365
#define RUBY_ATOMIC_CAS(var, oldval, newval)
Atomic compare-and-swap.
Definition atomic.h:165
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_DEC(var)
Atomically decrements the value pointed by var.
Definition atomic.h:223
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
Definition atomic.h:175
#define RUBY_ATOMIC_PTR_SET(var, val)
Identical to RUBY_ATOMIC_SET, except it expects its arguments are void*.
Definition atomic.h:351
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_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.
Definition value.h:40