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