Ruby 4.1.0dev (2026-09-21 revision 61de3dd727146cfcf24e8051595bb2fb842f37aa)
thread_sched_mn.c (61de3dd727146cfcf24e8051595bb2fb842f37aa)
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 // No event fired and we are interrupted: that is an interrupt, not a
613 // timeout. Hand the wait back so the caller runs it and re-polls the fd.
614 if (timedout && RUBY_VM_INTERRUPTED(th->ec)) return thread_sched_wait_unavailable;
615 return timedout ? thread_sched_wait_timeout : thread_sched_wait_event;
616}
617
619
620static int
621get_sysconf_page_size(void)
622{
623 static long page_size = 0;
624
625 if (UNLIKELY(page_size == 0)) {
626 page_size = sysconf(_SC_PAGESIZE);
627 VM_ASSERT(page_size < INT_MAX);
628 }
629 return (int)page_size;
630}
631
632#define MSTACK_CHUNK_SIZE (512 * 1024 * 1024) // 512MB
633#define MSTACK_PAGE_SIZE get_sysconf_page_size()
634#define MSTACK_CHUNK_PAGE_NUM (MSTACK_CHUNK_SIZE / MSTACK_PAGE_SIZE - 1) // 1 is start redzone
635
636// 512MB chunk
637// 131,072 pages (> 65,536)
638// Head pages hold the chunk header (see start_page); stacks follow.
639
640/*
641 * <--> machine stack + vm stack
642 * ----------------------------------
643 * |HD...|RZ| ... |RZ| ... ... |RZ|
644 * <------------- 512MB ------------->
645 */
646
647static struct nt_stack_chunk_header {
648 struct nt_stack_chunk_header *prev_chunk;
649 struct nt_stack_chunk_header *prev_free_chunk;
650 // prev_free_chunk == NULL cannot double as the membership test: the free
651 // list's tail also has it NULL, and re-pushing the tail self-cycles it.
652 bool on_free_list;
653
654 uint16_t start_page;
655 uint16_t stack_count;
656 uint16_t uninitialized_stack_count;
657
658 uint16_t free_stack_pos;
659 uint16_t free_stack[];
660} *nt_stack_chunks = NULL,
661 *nt_free_stack_chunks = NULL;
662
663struct nt_machine_stack_footer {
664 struct nt_stack_chunk_header *ch;
665 size_t index;
666};
667
668static rb_nativethread_lock_t nt_machine_stack_lock = RB_NATIVETHREAD_LOCK_INIT;
669
670// The holder at the fork moment does not exist in the child; start over.
671static void
672nt_machine_stack_atfork(void)
673{
674 rb_native_mutex_initialize(&nt_machine_stack_lock);
675}
676
677#include <sys/mman.h>
678
679// Page-align the configured sizes: the layout puts a guard page and a
680// MAP_FIXED machine stack behind the VM stack, and both must land on page
681// boundaries whatever RUBY_THREAD_VM_STACK_SIZE and
682// RUBY_THREAD_MACHINE_STACK_SIZE hold (those align only to 4KB).
683static inline size_t
684nt_vm_stack_area(const rb_vm_t *vm)
685{
686 return (size_t)roomof(vm->default_params.thread_vm_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
687}
688
689static inline size_t
690nt_machine_stack_area(const rb_vm_t *vm)
691{
692 return (size_t)roomof(vm->default_params.thread_machine_stack_size, MSTACK_PAGE_SIZE) * MSTACK_PAGE_SIZE;
693}
694
695// vm stack area + guard page + machine stack area
696static inline size_t
697nt_thread_stack_size(void)
698{
699 static size_t msz;
700 if (LIKELY(msz > 0)) return msz;
701
702 rb_vm_t *vm = GET_VM();
703 msz = nt_vm_stack_area(vm) + MSTACK_PAGE_SIZE + nt_machine_stack_area(vm);
704 return msz;
705}
706
707static struct nt_stack_chunk_header *
708nt_alloc_thread_stack_chunk(void)
709{
710 const char *m = (void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
711 if (m == MAP_FAILED) {
712 return NULL;
713 }
714
715 ruby_annotate_mmap(m, MSTACK_CHUNK_SIZE, "Ruby:nt_alloc_thread_stack_chunk");
716
717 size_t msz = nt_thread_stack_size();
718 int header_page_cnt = 1;
719 int stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
720 int ch_size = sizeof(struct nt_stack_chunk_header) + sizeof(uint16_t) * stack_count;
721
722 if (ch_size > MSTACK_PAGE_SIZE * header_page_cnt) {
723 header_page_cnt = (ch_size + MSTACK_PAGE_SIZE - 1) / MSTACK_PAGE_SIZE;
724 stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;
725 }
726
727 VM_ASSERT(stack_count <= UINT16_MAX);
728
729 // Enable read/write for the header pages
730 if (mprotect((void *)m, (size_t)header_page_cnt * MSTACK_PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) {
731 munmap((void *)m, MSTACK_CHUNK_SIZE);
732 return NULL;
733 }
734
735 struct nt_stack_chunk_header *ch = (struct nt_stack_chunk_header *)m;
736
737 ch->start_page = header_page_cnt;
738 ch->prev_chunk = nt_stack_chunks;
739 ch->prev_free_chunk = nt_free_stack_chunks;
740 ch->on_free_list = true; // the caller makes it the free-list head
741 ch->uninitialized_stack_count = ch->stack_count = (uint16_t)stack_count;
742 ch->free_stack_pos = 0;
743
744 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);
745
746 return ch;
747}
748
749static void *
750nt_stack_chunk_get_stack_start(struct nt_stack_chunk_header *ch, size_t idx)
751{
752 const char *m = (char *)ch;
753 return (void *)(m + ch->start_page * MSTACK_PAGE_SIZE + idx * nt_thread_stack_size());
754}
755
756static struct nt_machine_stack_footer *
757nt_stack_chunk_get_msf(const rb_vm_t *vm, const char *mstack)
758{
759 // TODO: stack direction
760 const size_t msz = vm->default_params.thread_machine_stack_size;
761 return (struct nt_machine_stack_footer *)&mstack[msz - sizeof(struct nt_machine_stack_footer)];
762}
763
764static void
765nt_stack_chunk_get_stack(const rb_vm_t *vm, struct nt_stack_chunk_header *ch, size_t idx, void **vm_stack, void **machine_stack)
766{
767 // TODO: only support stack going down
768 // [VM ... <GUARD> machine stack ...]
769
770 const char *vstack, *mstack;
771 const char *guard_page;
772 vstack = nt_stack_chunk_get_stack_start(ch, idx);
773 guard_page = vstack + nt_vm_stack_area(vm);
774 mstack = guard_page + MSTACK_PAGE_SIZE;
775
776 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(vm, mstack);
777 msf->ch = ch;
778 msf->index = idx;
779
780#if 0
781 RUBY_DEBUG_LOG("msf:%p vstack:%p-%p guard_page:%p-%p mstack:%p-%p", msf,
782 vstack, (void *)(guard_page-1),
783 guard_page, (void *)(mstack-1),
784 mstack, (void *)(msf));
785#endif
786
787 *vm_stack = (void *)vstack;
788 *machine_stack = (void *)mstack;
789}
790
792static void
793nt_stack_chunk_dump(void)
794{
795 struct nt_stack_chunk_header *ch;
796 int i;
797
798 fprintf(stderr, "** nt_stack_chunks\n");
799 ch = nt_stack_chunks;
800 for (i=0; ch; i++, ch = ch->prev_chunk) {
801 fprintf(stderr, "%d %p free_pos:%d\n", i, (void *)ch, (int)ch->free_stack_pos);
802 }
803
804 fprintf(stderr, "** nt_free_stack_chunks\n");
805 ch = nt_free_stack_chunks;
806 for (i=0; ch; i++, ch = ch->prev_free_chunk) {
807 fprintf(stderr, "%d %p free_pos:%d\n", i, (void *)ch, (int)ch->free_stack_pos);
808 }
809}
810
811static int
812nt_alloc_stack(rb_vm_t *vm, void **vm_stack, void **machine_stack)
813{
814 int err = 0;
815
816 rb_native_mutex_lock(&nt_machine_stack_lock);
817 {
818 retry:
819 if (nt_free_stack_chunks) {
820 struct nt_stack_chunk_header *ch = nt_free_stack_chunks;
821 if (ch->free_stack_pos > 0) {
822 RUBY_DEBUG_LOG("free_stack_pos:%d", ch->free_stack_pos);
823 nt_stack_chunk_get_stack(vm, ch, ch->free_stack[--ch->free_stack_pos], vm_stack, machine_stack);
824 }
825 else if (ch->uninitialized_stack_count > 0) {
826 RUBY_DEBUG_LOG("uninitialized_stack_count:%d", ch->uninitialized_stack_count);
827
828 size_t idx = ch->stack_count - ch->uninitialized_stack_count--;
829
830 // The chunk was mapped PROT_NONE; enable the VM stack and
831 // machine stack pages, leaving the guard page as PROT_NONE.
832 char *stack_start = nt_stack_chunk_get_stack_start(ch, idx);
833 size_t vm_stack_area = nt_vm_stack_area(vm);
834 size_t mstack_size = nt_thread_stack_size() - vm_stack_area - MSTACK_PAGE_SIZE;
835 char *mstack_start = stack_start + vm_stack_area + MSTACK_PAGE_SIZE;
836
837 int mstack_flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE;
838#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
839 mstack_flags |= MAP_STACK;
840#endif
841
842 if (mprotect(stack_start, vm_stack_area, PROT_READ | PROT_WRITE) != 0 ||
843 mmap(mstack_start, mstack_size, PROT_READ | PROT_WRITE, mstack_flags, -1, 0) == MAP_FAILED) {
844 err = errno;
845 ch->uninitialized_stack_count++; // the slot was not consumed
846 }
847 else {
848 nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack);
849 }
850 }
851 else {
852 nt_free_stack_chunks = ch->prev_free_chunk;
853 ch->prev_free_chunk = NULL;
854 ch->on_free_list = false;
855 goto retry;
856 }
857 }
858 else {
859 struct nt_stack_chunk_header *p = nt_alloc_thread_stack_chunk();
860 if (p == NULL) {
861 err = errno;
862 }
863 else {
864 nt_free_stack_chunks = nt_stack_chunks = p;
865 goto retry;
866 }
867 }
868 }
869 rb_native_mutex_unlock(&nt_machine_stack_lock);
870
871 return err;
872}
873
874static void
875nt_madvise_free_or_dontneed(void *addr, size_t len)
876{
877 /* There is no real way to perform error handling here. Both MADV_FREE
878 * and MADV_DONTNEED are both documented to pretty much only return EINVAL
879 * for a huge variety of errors. It's indistinguishable if madvise fails
880 * because the parameters were bad, or because the kernel we're running on
881 * does not support the given advice. This kind of free-but-don't-unmap
882 * is best-effort anyway, so don't sweat it.
883 *
884 * n.b. A very common case of "the kernel doesn't support MADV_FREE and
885 * returns EINVAL" is running under the `rr` debugger; it makes all
886 * MADV_FREE calls return EINVAL. */
887
888#if defined(MADV_FREE)
889 int r = madvise(addr, len, MADV_FREE);
890 // Return on success, or else try MADV_DONTNEED
891 if (r == 0) return;
892#endif
893#if defined(MADV_DONTNEED)
894 madvise(addr, len, MADV_DONTNEED);
895#endif
896}
897
898static void
899nt_free_stack(void *mstack)
900{
901 if (!mstack) return;
902
903 rb_native_mutex_lock(&nt_machine_stack_lock);
904 {
905 struct nt_machine_stack_footer *msf = nt_stack_chunk_get_msf(GET_VM(), mstack);
906 struct nt_stack_chunk_header *ch = msf->ch;
907 int idx = (int)msf->index;
908 void *stack = nt_stack_chunk_get_stack_start(ch, idx);
909
910 RUBY_DEBUG_LOG("stack:%p mstack:%p ch:%p index:%d", stack, mstack, ch, idx);
911
912 if (!ch->on_free_list) {
913 ch->on_free_list = true;
914 ch->prev_free_chunk = nt_free_stack_chunks;
915 nt_free_stack_chunks = ch;
916 }
917 ch->free_stack[ch->free_stack_pos++] = idx;
918
919 // clear the stack pages
920 nt_madvise_free_or_dontneed(stack, nt_thread_stack_size());
921 }
922 rb_native_mutex_unlock(&nt_machine_stack_lock);
923}
924
925static bool
926mn_threads_enabled_p(void)
927{
928 return mn_threads_mode >= 0;
929}
930
931static int
932native_thread_check_and_create_shared(rb_vm_t *vm)
933{
934 bool need_to_make = false;
935
936 if (!mn_threads_enabled_p()) return 0; // no thread is M:N: the pool serves nobody
937
938 ractor_sched_lock(vm, NULL); // NULL: the timer thread also calls this
939 {
940 unsigned int schedulable_ractor_cnt = vm->ractor.cnt;
941 RUBY_ASSERT(schedulable_ractor_cnt >= 1);
942
943 if (!vm->ractor.main_ractor->threads.sched.enable_mn_threads)
944 schedulable_ractor_cnt--; // do not need snt for main ractor
945
946 // CAS keeps a concurrent rejoin from pushing snt_cnt past the cap
947 rb_atomic_t snt_cnt = RUBY_ATOMIC_LOAD(vm->ractor.sched.snt_cnt);
948 while (((int)snt_cnt < MINIMUM_SNT) ||
949 (snt_cnt < schedulable_ractor_cnt &&
950 snt_cnt < vm->ractor.sched.max_cpu)) {
951 rb_atomic_t prev = RUBY_ATOMIC_CAS(vm->ractor.sched.snt_cnt, snt_cnt, snt_cnt + 1);
952 if (prev == snt_cnt) {
953 need_to_make = true;
954 break;
955 }
956 snt_cnt = prev;
957 }
958
959 if (need_to_make) {
960 RUBY_DEBUG_LOG("added snt:%u dnt:%u ractor_cnt:%u grq_cnt:%u",
961 vm->ractor.sched.snt_cnt,
962 vm->ractor.sched.dnt_cnt,
963 vm->ractor.cnt,
964 vm->ractor.sched.grq_cnt);
965 }
966 else {
967 RUBY_DEBUG_LOG("snt:%d ractor_cnt:%d", (int)vm->ractor.sched.snt_cnt, (int)vm->ractor.cnt);
968 }
969 }
970 ractor_sched_unlock(vm, NULL);
971
972 if (need_to_make) {
973 struct rb_native_thread *nt = native_thread_alloc();
974 nt->vm = vm;
975 int err = native_thread_create0(nt);
976 if (err) {
977 // Roll back, or this function would conclude forever that the
978 // pool is wide enough and never try again.
979 ractor_sched_lock(vm, NULL);
980 RUBY_ATOMIC_DEC(vm->ractor.sched.snt_cnt);
981 ractor_sched_unlock(vm, NULL);
982 native_thread_destroy(nt);
983 }
984 return err;
985 }
986 else {
987 return 0;
988 }
989}
990
991// A coroutine thread's epilogue, run from thread_start_func_2 while th is
992// still valid. Everything that touches th happens here; co_start then only
993// makes the final transfer, touching the execution-owned tctx alone.
994static void
995coroutine_thread_terminated(rb_thread_t *th)
996{
997 struct rb_thread_context *tctx = (struct rb_thread_context *)th->sched.context;
998 struct rb_thread_sched *sched = TH_SCHED(th);
999 rb_ractor_t *r = th->ractor;
1000 bool last = (th->invoke_type == thread_invoke_type_ractor_proc);
1001 bool is_dnt = th_has_dedicated_nt(th);
1002
1003 // VM destruct tears down the heap and then unsets ruby_current_vm_ptr;
1004 // this native thread still frees the dead context afterwards (the nt
1005 // loop's reclaim uses ruby_xfree and the stack-pool geometry via
1006 // GET_VM()). Make destruct wait until the reclaim finished. (Observed:
1007 // an assert_separately child exiting right after a Ractor finished
1008 // crashed at GET_VM()->default_params, offset 0x2600, on two arches.)
1009 rb_vm_t *const vm = th->vm; // survives th; the tail below must not read th
1010 RUBY_ATOMIC_INC(vm->ractor.sched.winding_cnt);
1011
1012 rb_thread_t *wake_th;
1013 bool wake_mn = false;
1014
1015 // Leave the living set here, while th is still barrier-registered and no
1016 // successor can run: the GC's root scan walks r->threads.set without the
1017 // Ractor lock, so the unlink must not race with it. Off the set th would
1018 // be unreachable although the handoff below keeps using it (and the GC can
1019 // run: to_dead_common() deregisters th, so no barrier waits for it) --
1020 // dying_th keeps it marked until its last use.
1021 VM_ASSERT(sched->running == th); // th owns the slot through the handoff
1022 if (!last) {
1023 RUBY_ATOMIC_PTR_SET(r->threads.dying_th, th);
1024 rb_ractor_living_threads_remove(r, th);
1025 }
1026
1027 thread_sched_lock(sched, th);
1028 {
1029 // designate the successor (running = next from readyq, or NULL); for
1030 // a dedicated nt (will_switch false) this also enqueues the Ractor.
1031 thread_sched_to_dead_common(sched, th);
1032
1033 // Only the successor WE designated here may be enqueued by this
1034 // epilogue (below). If readyq was empty, running is now NULL and a
1035 // waker (e.g. the timer thread) that later installs a runnable
1036 // thread enqueues the Ractor itself -- enqueuing "whatever is
1037 // running" at that point would duplicate its entry.
1038 wake_th = is_dnt ? NULL : sched->running;
1039 // Read wake_th->nt under the lock: a dedicated successor was already
1040 // woken by to_dead_common and may die (freeing wake_th) as soon as we
1041 // unlock. An M:N successor (nt == NULL) cannot run or be assigned an
1042 // nt before our enqueue below, so the value cannot go stale.
1043 wake_mn = (wake_th != NULL && wake_th->nt == NULL);
1044
1045 tctx->nt = th->nt; // stash the final transfer target for co_start
1046 native_thread_assign(NULL, th);
1047 th->sched.context = NULL; // the wrapper's dfree must not reclaim tctx
1048
1049 if (!last) {
1050 // Still under the sched lock: a successor (even a dedicated one
1051 // woken by to_dead_common) starts by taking it, so it cannot
1052 // observe or overwrite these until we unlock. th was last used
1053 // above and running_ec no longer points into it; now it may be
1054 // collected.
1055 rb_ractor_set_current_ec(r, NULL); // r alive: it has other threads
1056 VM_ASSERT(RUBY_ATOMIC_PTR_LOAD(r->threads.dying_th) == th);
1057 RUBY_ATOMIC_PTR_SET(r->threads.dying_th, NULL);
1058 }
1059 }
1060 if (last) {
1061 thread_sched_unlock(sched, th); // th is still on the living set here
1062
1063 VM_ASSERT(sched->running == NULL);
1064 VM_ASSERT(wake_th == NULL);
1065 // Last access to th/r: the removal may unlink the Ractor, after
1066 // which the GC may collect th and r.
1067 rb_ractor_living_threads_remove(r, th);
1068 rb_current_ec_set(NULL); // TLS only; r may be collectable already
1069 }
1070 else {
1071 // th lost its root at the clear above; the plain unlock's debug log
1072 // would read th->serial.
1073 thread_sched_unlock_no_log(sched, th);
1074
1075 if (wake_mn) {
1076 // enqueue the successor designated above -- exactly once per
1077 // "runnable but unserved" period, by its designator.
1078 thread_sched_lock(sched, NULL);
1079 ractor_sched_enq(vm, r);
1080 thread_sched_unlock(sched, NULL);
1081 }
1082 }
1083}
1084
1085#ifdef __APPLE__
1086# define co_start ruby_coroutine_start
1087#else
1088static
1089#endif
1090COROUTINE
1091co_start(struct coroutine_context *from, struct coroutine_context *self)
1092{
1093#ifdef RUBY_ASAN_ENABLED
1094 __sanitizer_finish_switch_fiber(self->fake_stack,
1095 (const void**)&from->stack_base, &from->stack_size);
1096#endif
1097
1098 rb_thread_t *th = (rb_thread_t *)self->argument;
1099 struct rb_thread_sched *sched = TH_SCHED(th);
1100 VM_ASSERT(th->nt != NULL);
1101 VM_ASSERT(th == sched->running);
1102 VM_ASSERT(sched->lock_owner == NULL);
1103
1104 // RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
1105
1106 thread_sched_set_locked(sched, th);
1107 thread_sched_add_running_thread(TH_SCHED(th), th);
1108 thread_sched_unlock(sched, th);
1109 {
1110 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_RESUMED, th);
1111 call_thread_start_func_2(th);
1112 }
1113 // Thread is terminated. coroutine_thread_terminated (run from
1114 // thread_start_func_2 while th was still valid) already left the living
1115 // set and stashed the transfer target, so th / its Ractor may already be
1116 // collected. Only tctx is touched here: mark it dead and transfer back to
1117 // this native thread's own context, where the nt loop reclaims tctx.
1118 struct rb_thread_context *tctx = (struct rb_thread_context *)self;
1119 tctx->dead = true;
1120 // Hand this context to the nt's loop, which reclaims it right after the
1121 // final transfer returns from switch0.
1122 tctx->nt->dead_co = &tctx->co;
1123 coroutine_transfer0(&tctx->co, tctx->nt->nt_context, true);
1124
1125 rb_bug("unreachable");
1126}
1127
1128static int
1129native_thread_create_shared(rb_thread_t *th)
1130{
1131 // setup coroutine
1132 rb_vm_t *vm = th->vm;
1133 void *vm_stack = NULL, *machine_stack = NULL;
1134 int err = nt_alloc_stack(vm, &vm_stack, &machine_stack);
1135 if (err) return err;
1136
1137 VM_ASSERT(vm_stack < machine_stack);
1138
1139 // setup vm stack
1140 size_t vm_stack_words = th->vm->default_params.thread_vm_stack_size/sizeof(VALUE);
1141 rb_ec_initialize_vm_stack(th->ec, vm_stack, vm_stack_words);
1142
1143 // setup machine stack
1144 size_t machine_stack_size = vm->default_params.thread_machine_stack_size - sizeof(struct nt_machine_stack_footer);
1145 th->ec->machine.stack_start = (void *)((uintptr_t)machine_stack + machine_stack_size);
1146 th->ec->machine.stack_maxsize = machine_stack_size; // TODO
1147 th->sched.context_stack = machine_stack;
1148 th->sched.context_stack_size = machine_stack_size;
1149
1150 struct rb_thread_context *tctx = ruby_xmalloc(sizeof(struct rb_thread_context));
1151 tctx->stack = machine_stack;
1152 tctx->dead = false;
1153 tctx->nt = NULL;
1154 th->sched.context = &tctx->co;
1155 coroutine_initialize(&tctx->co, co_start, machine_stack, machine_stack_size);
1156 tctx->co.argument = th;
1157
1158 RUBY_DEBUG_LOG("th:%u vm_stack:%p machine_stack:%p", rb_th_serial(th), vm_stack, machine_stack);
1159
1160 // Widen the pool before publishing th. Once ready, a Ractor's thread that
1161 // runs to its end frees its own rb_thread_t (rb_ractor_postmortem_free),
1162 // and the caller's create-failure path assumes th never became runnable.
1163 int create_err = native_thread_check_and_create_shared(vm);
1164 if (create_err) return create_err;
1165
1166 thread_sched_to_ready(TH_SCHED(th), th);
1167 return 0;
1168}
1169
1171#if HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1172
1176
1177// (FD_WAIT_IO_MASK and the fd shard helpers are defined near the top.)
1178
1179#define FDMAP_CHUNK_BITS 10
1180#define FDMAP_CHUNK_SIZE (1u << FDMAP_CHUNK_BITS)
1181#define FDMAP_CHUNK_MASK (FDMAP_CHUNK_SIZE - 1)
1182
1183// Callable under any fd shard lock: a chunk spans every shard, so the chunk
1184// table is not guarded by them; slots install by CAS and are never freed.
1185static struct rb_fd_waiters *
1186fd_waiters_lookup(int fd, bool create)
1187{
1188 if (fd < 0) return NULL;
1189
1190 unsigned int ci = (unsigned int)fd >> FDMAP_CHUNK_BITS;
1191 if (ci >= FDMAP_MAX_CHUNKS) return NULL; // the caller falls back to a blocking wait
1192
1193 struct rb_fd_waiters *chunk = RUBY_ATOMIC_PTR_LOAD(timer_th.fdmap_chunks[ci]);
1194
1195 if (chunk == NULL) {
1196 if (!create) return NULL;
1197
1198 chunk = calloc(FDMAP_CHUNK_SIZE, sizeof(*chunk));
1199 if (chunk == NULL) rb_bug("fd_waiters_lookup: calloc failed");
1200
1201 for (unsigned int i = 0; i < FDMAP_CHUNK_SIZE; i++) {
1202 ccan_list_head_init(&chunk[i].waiters);
1203 }
1204
1205 struct rb_fd_waiters *prev = RUBY_ATOMIC_PTR_CAS(timer_th.fdmap_chunks[ci], NULL, chunk);
1206 if (prev != NULL) {
1207 free(chunk); // another shard installed it first
1208 chunk = prev;
1209 }
1210 }
1211
1212 return &chunk[(unsigned int)fd & FDMAP_CHUNK_MASK];
1213}
1214
1215// The fd's shard lock must be held.
1216static uint32_t
1217fd_waiters_union(struct rb_fd_waiters *e)
1218{
1219 uint32_t want = 0;
1220 struct rb_thread_sched_waiting *w;
1221
1222 ccan_list_for_each(&e->waiters, w, fd_node) {
1223 want |= (uint32_t)(w->flags & FD_WAIT_IO_MASK);
1224 }
1225 return want;
1226}
1227
1228// Events name an fd and the generation it was armed with, never a thread: a
1229// stale event then resolves to a table lookup instead of a freed thread.
1230static inline uint64_t
1231fd_event_tag(int fd, uint32_t generation)
1232{
1233 return ((uint64_t)generation << 32) | (uint32_t)fd;
1234}
1235
1236#define FD_EVENT_TAG_FD(tag) ((int)((tag) & 0xffffffffu))
1237#define FD_EVENT_TAG_GEN(tag) ((uint32_t)((tag) >> 32))
1238
1239// Make the backend match `want`. Returns false if the fd cannot be registered
1240// at all (closed, or unsupported by the backend), leaving the entry untouched.
1241// The fd's shard lock must be held. `consumed` says an epoll event for the
1242// current arming was just delivered, so EPOLLONESHOT has already disarmed it.
1243static bool
1244fd_waiters_arm(int fd, struct rb_fd_waiters *e, uint32_t want, bool consumed)
1245{
1246 // After a delivery the kernel side is disarmed even when the flags agree,
1247 // so a consumed call must fall through to re-arm.
1248 if (want == e->armed_flags && !consumed) return true;
1249
1250#if HAVE_SYS_EVENT_H
1251 struct kevent ke[2];
1252 int n = 0;
1253 uint32_t add = want & ~e->armed_flags;
1254 uint32_t del = e->armed_flags & ~want;
1255 void *tag = (void *)(uintptr_t)fd_event_tag(fd, e->generation);
1256
1257 if (del & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); n++; }
1258 if (del & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); n++; }
1259
1260 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1261 // The fd may already be gone; that is not an error for a removal.
1262 if (errno != ENOENT && errno != EBADF) {
1263 perror("kevent");
1264 rb_bug("fd_waiters_arm/kevent delete failed (fd:%d errno:%d)", fd, errno);
1265 }
1266 }
1267
1268 n = 0;
1269 if (add & thread_sched_waiting_io_read) { EV_SET(&ke[n], fd, EVFILT_READ, EV_ADD, 0, 0, tag); n++; }
1270 if (add & thread_sched_waiting_io_write) { EV_SET(&ke[n], fd, EVFILT_WRITE, EV_ADD, 0, 0, tag); n++; }
1271
1272 if (n > 0 && kevent(timer_th.event_fd, ke, n, NULL, 0, NULL) == -1) {
1273 switch (errno) {
1274 case EBADF:
1275 case EINVAL:
1276 return false;
1277 default:
1278 perror("kevent");
1279 rb_bug("fd_waiters_arm/kevent add failed (fd:%d errno:%d)", fd, errno);
1280 }
1281 }
1282#elif HAVE_SYS_EPOLL_H
1283 if (want == 0) {
1284 // A delivered oneshot event has already disarmed the fd; otherwise DEL
1285 // it. MOD to no events would not do: EPOLLHUP and EPOLLERR are
1286 // reported whatever the mask asks for, and without EPOLLONESHOT they
1287 // are reported level-triggered, so a registration left behind by a
1288 // waiter that gave up (kill, interrupt, timeout) spins the timer
1289 // thread once the fd hangs up -- and a spinning timer thread never
1290 // reaches its timeout branch, the only place that mints an snt.
1291 if (!consumed && e->registered) {
1292 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
1293 switch (errno) {
1294 case EBADF:
1295 case ENOENT:
1296 // the fd is already closed or gone from the set
1297 break;
1298 default:
1299 perror("epoll_ctl");
1300 rb_bug("fd_waiters_arm/epoll_ctl disarm failed (fd:%d errno:%d)", fd, errno);
1301 }
1302 }
1303 e->registered = false;
1304 }
1305 // Anything epoll_wait already queued for the old arming is stale now.
1306 e->generation++;
1307 e->armed_flags = 0;
1308 return true;
1309 }
1310
1311 uint32_t epoll_events = EPOLLONESHOT;
1312 if (want & thread_sched_waiting_io_read) epoll_events |= EPOLLIN;
1313 if (want & thread_sched_waiting_io_write) epoll_events |= EPOLLOUT;
1314
1315 struct epoll_event event = {
1316 .events = epoll_events,
1317 .data = { .u64 = fd_event_tag(fd, e->generation) },
1318 };
1319
1320 int op = e->registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
1321
1322 if (epoll_ctl(timer_th.event_fd, op, fd, &event) == -1) {
1323 switch (errno) {
1324 case ENOENT:
1325 // Not registered after all: the fd was closed and reopened. Add it.
1326 if (op == EPOLL_CTL_MOD &&
1327 epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == 0) {
1328 break;
1329 }
1330 e->registered = false;
1331 return false;
1332 case EEXIST:
1333 // Likewise in the other direction.
1334 if (op == EPOLL_CTL_ADD &&
1335 epoll_ctl(timer_th.event_fd, EPOLL_CTL_MOD, fd, &event) == 0) {
1336 break;
1337 }
1338 return false;
1339 case EBADF:
1340 case EPERM:
1341 // closed, or the fd does not support epoll
1342 e->registered = false;
1343 return false;
1344 default:
1345 perror("epoll_ctl");
1346 rb_bug("fd_waiters_arm/epoll_ctl failed (fd:%d op:%d errno:%d)", fd, op, errno);
1347 }
1348 }
1349 e->registered = true;
1350#else
1351# error "neither kqueue nor epoll"
1352#endif
1353
1354 e->armed_flags = want;
1355 return true;
1356}
1357
1358static bool
1359fd_ready_nonblock(int fd, short events)
1360{
1361 struct pollfd pfd = {
1362 .fd = fd,
1363 .events = events,
1364 };
1365
1366 // A "ready" answer makes the caller report the requested event, so a
1367 // closed fd (POLLNVAL) must not count: it owes the caller EBADF.
1368 return poll(&pfd, 1, 0) > 0 && !(pfd.revents & POLLNVAL);
1369}
1370
1371static void
1372verify_waiting_list(void)
1373{
1374#if VM_CHECK_MODE > 0
1375 struct rb_thread_sched_waiting *w;
1376
1377 for (int lvl = 0; lvl < TIMER_WHEEL_LEVELS; lvl++) {
1378 const struct timer_wheel_level *lv = &timer_th.wheel[lvl];
1379
1380 for (int slot = 0; slot < TIMER_WHEEL_SLOTS; slot++) {
1381 bool occupied = (lv->occupied >> slot) & 1;
1382 VM_ASSERT(occupied == !ccan_list_empty(&lv->slots[slot]));
1383
1384 ccan_list_for_each(&lv->slots[slot], w, node) {
1385 // an io entry's flags belong to its fd shard: do not read them here
1386 if (!(w->flags & FD_WAIT_IO_MASK)) {
1387 VM_ASSERT(w->flags & thread_sched_waiting_timeout);
1388 VM_ASSERT(w->data.timeout != 0);
1389 }
1390 VM_ASSERT(w->wheel_lvl == lvl);
1391 VM_ASSERT(w->wheel_slot == slot);
1392 }
1393 }
1394 }
1395#endif
1396}
1397
1398/* ------------------------------------------------------------------------
1399 * backend: the readiness notification mechanism (epoll / kqueue).
1400 *
1401 * Everything below that names epoll or kqueue is this backend; the rest of
1402 * the M:N scheduler only asks it to arm an fd (fd_waiters_arm) and to wait
1403 * for what fired (event_wait / timer_thread_polling).
1404 * ------------------------------------------------------------------------ */
1405
1406#if HAVE_SYS_EVENT_H // kqueue helpers
1407
1408static enum thread_sched_waiting_flag
1409kqueue_translate_filter_to_flags(int16_t filter)
1410{
1411 switch (filter) {
1412 case EVFILT_READ:
1413 return thread_sched_waiting_io_read;
1414 case EVFILT_WRITE:
1415 return thread_sched_waiting_io_write;
1416 case EVFILT_TIMER:
1417 return thread_sched_waiting_timeout;
1418 default:
1419 rb_bug("kevent filter:%d not supported", filter);
1420 }
1421}
1422
1423static int
1424kqueue_wait(rb_vm_t *vm)
1425{
1426 struct timespec calculated_timeout;
1427 struct timespec *timeout = NULL;
1428 int timeout_ms = timer_thread_set_timeout(vm);
1429
1430 if (timeout_ms > 0) {
1431 calculated_timeout.tv_sec = timeout_ms / 1000;
1432 calculated_timeout.tv_nsec = (timeout_ms % 1000) * 1000000;
1433 timeout = &calculated_timeout;
1434 }
1435 else if (timeout_ms == 0) {
1436 // Relying on the absence of other members of struct timespec is not strictly portable,
1437 // and kevent needs a 0-valued timespec to mean immediate timeout.
1438 memset(&calculated_timeout, 0, sizeof(struct timespec));
1439 timeout = &calculated_timeout;
1440 }
1441
1442 return kevent(timer_th.event_fd, NULL, 0, timer_th.finished_events, KQUEUE_EVENTS_MAX, timeout);
1443}
1444
1445static void
1446kqueue_create(void)
1447{
1448 if ((timer_th.event_fd = kqueue()) == -1) rb_bug("kqueue creation failed (errno:%d)", errno);
1449 int flags = fcntl(timer_th.event_fd, F_GETFD);
1450 if (flags == -1) {
1451 rb_bug("kqueue GETFD failed (errno:%d)", errno);
1452 }
1453
1454 flags |= FD_CLOEXEC;
1455 if (fcntl(timer_th.event_fd, F_SETFD, flags) == -1) {
1456 rb_bug("kqueue SETFD failed (errno:%d)", errno);
1457 }
1458}
1459
1460#endif // HAVE_SYS_EVENT_H
1461
1462// return false if the fd is not waitable or not need to wait.
1463static enum timer_thread_register_result
1464timer_thread_register_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags, rb_hrtime_t *rel, uint32_t event_serial)
1465{
1466 RUBY_DEBUG_LOG("th:%u fd:%d flag:%d rel:%lu", rb_th_serial(th), fd, flags, rel ? (unsigned long)*rel : 0);
1467
1468 VM_ASSERT(th == NULL || TH_SCHED(th)->running == th);
1469 VM_ASSERT(flags != 0);
1470
1471 rb_hrtime_t abs = 0; // 0 means no timeout
1472
1473 if (rel) {
1474 if (*rel > 0) {
1475 flags |= thread_sched_waiting_timeout;
1476 }
1477 else {
1478 return timer_thread_already_ready; // zero timeout: nothing to wait for
1479 }
1480 }
1481
1482 if (flags & thread_sched_waiting_timeout) {
1483 VM_ASSERT(rel != NULL);
1484 abs = rb_hrtime_add(rb_hrtime_now(), *rel);
1485 }
1486
1487 if (flags & thread_sched_waiting_io_read) {
1488 if (!(flags & thread_sched_waiting_io_force) && fd_ready_nonblock(fd, POLLIN)) {
1489 RUBY_DEBUG_LOG("fd readable");
1490 return timer_thread_already_ready;
1491 }
1492 VM_ASSERT(fd >= 0);
1493 }
1494
1495 if (flags & thread_sched_waiting_io_write) {
1496 if (!(flags & thread_sched_waiting_io_force) && fd_ready_nonblock(fd, POLLOUT)) {
1497 RUBY_DEBUG_LOG("fd writable");
1498 return timer_thread_already_ready;
1499 }
1500 VM_ASSERT(fd >= 0);
1501 }
1502
1503 if (flags & FD_WAIT_IO_MASK) {
1504 fd_shard_lock(fd);
1505 {
1506 struct rb_fd_waiters *e = fd_waiters_lookup(fd, true);
1507
1508 if (e == NULL) { // fd beyond the map: fall back to a blocking wait
1509 fd_shard_unlock(fd);
1510 return timer_thread_unavailable;
1511 }
1512
1513 // Arm the union of what this fd's waiters want, so a second waiter
1514 // on the same fd extends the arming instead of colliding with it.
1515 if (!fd_waiters_arm(fd, e, fd_waiters_union(e) | (uint32_t)(flags & FD_WAIT_IO_MASK), false)) {
1516 fd_shard_unlock(fd);
1517 return timer_thread_unavailable;
1518 }
1519
1520 if (th) {
1521 ccan_list_add_tail(&e->waiters, &th->sched.waiting_reason.fd_node);
1522
1523 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1524 th->sched.waiting_reason.flags = flags;
1525 th->sched.waiting_reason.data.timeout = abs;
1526 th->sched.waiting_reason.data.fd = fd;
1527 th->sched.waiting_reason.data.result = 0;
1528 th->sched.waiting_reason.data.event_serial = event_serial;
1529
1530 if (abs != 0) {
1531 RUBY_DEBUG_LOG("abs:%lu", (unsigned long)abs);
1532 VM_ASSERT(flags & thread_sched_waiting_timeout);
1533
1534 rb_native_mutex_lock(&timer_th.waiting_lock);
1535 {
1536 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1537 timer_wheel_insert(&th->sched.waiting_reason);
1538 verify_waiting_list();
1539 if (timer_th.next_expiry < prev_expiry) {
1540 // an earlier deadline than the timer thread is armed for
1541 timer_thread_wakeup_force();
1542 }
1543 }
1544 rb_native_mutex_unlock(&timer_th.waiting_lock);
1545 }
1546 }
1547 RUBY_DEBUG_LOG("armed fd:%d want:%u", fd, e->armed_flags);
1548 }
1549 fd_shard_unlock(fd);
1550 }
1551 else if (th) {
1552 // fd-less timed wait: the wheel lock owns it end to end
1553 VM_ASSERT(abs != 0 && (flags & thread_sched_waiting_timeout));
1554
1555 rb_native_mutex_lock(&timer_th.waiting_lock);
1556 {
1557 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
1558 th->sched.waiting_reason.flags = flags;
1559 th->sched.waiting_reason.data.timeout = abs;
1560 th->sched.waiting_reason.data.fd = fd;
1561 th->sched.waiting_reason.data.result = 0;
1562 th->sched.waiting_reason.data.event_serial = event_serial;
1563
1564 rb_hrtime_t prev_expiry = timer_th.next_expiry;
1565 timer_wheel_insert(&th->sched.waiting_reason);
1566 verify_waiting_list();
1567 if (timer_th.next_expiry < prev_expiry) {
1568 timer_thread_wakeup_force();
1569 }
1570 }
1571 rb_native_mutex_unlock(&timer_th.waiting_lock);
1572 }
1573 else {
1574 VM_ASSERT(abs == 0);
1575 }
1576
1577 return timer_thread_registered;
1578}
1579
1580// Drop `th` from its fd's waiter list and re-arm the backend for whoever is
1581// left. The fd's shard lock must be held.
1582static void
1583timer_thread_unregister_waiting(rb_thread_t *th, int fd, enum thread_sched_waiting_flag flags)
1584{
1585 if (!(th->sched.waiting_reason.flags & FD_WAIT_IO_MASK)) {
1586 return;
1587 }
1588
1589 RUBY_DEBUG_LOG("th:%u fd:%d", rb_th_serial(th), fd);
1590
1591 ccan_list_del_init(&th->sched.waiting_reason.fd_node);
1592
1593 struct rb_fd_waiters *e = fd_waiters_lookup(fd, false);
1594 if (e) {
1595 fd_waiters_arm(fd, e, fd_waiters_union(e), false);
1596 }
1597}
1598
1599// The timer thread's own wakeup pipe has no waiter: it stays armed forever and
1600// is recognised by its fd when an event arrives.
1601static void
1602timer_thread_arm_comm_pipe(void)
1603{
1604 int fd = timer_th.comm_fds[0];
1605
1606#if HAVE_SYS_EVENT_H
1607 struct kevent ke;
1608 EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, (void *)(uintptr_t)fd_event_tag(fd, 0));
1609 if (kevent(timer_th.event_fd, &ke, 1, NULL, 0, NULL) == -1) {
1610 rb_bug("timer_thread_arm_comm_pipe/kevent failed (errno:%d)", errno);
1611 }
1612#elif HAVE_SYS_EPOLL_H
1613 struct epoll_event event = {
1614 .events = EPOLLIN,
1615 .data = { .u64 = fd_event_tag(fd, 0) },
1616 };
1617 if (epoll_ctl(timer_th.event_fd, EPOLL_CTL_ADD, fd, &event) == -1) {
1618 rb_bug("timer_thread_arm_comm_pipe/epoll_ctl failed (errno:%d)", errno);
1619 }
1620#else
1621# error "neither kqueue nor epoll"
1622#endif
1623}
1624
1625static void
1626timer_thread_setup_mn(void)
1627{
1628#if HAVE_SYS_EVENT_H
1629 kqueue_create();
1630 RUBY_DEBUG_LOG("kqueue_fd:%d", timer_th.event_fd);
1631#else
1632 if ((timer_th.event_fd = epoll_create1(EPOLL_CLOEXEC)) == -1) rb_bug("epoll_create (errno:%d)", errno);
1633 RUBY_DEBUG_LOG("epoll_fd:%d", timer_th.event_fd);
1634#endif
1635 RUBY_DEBUG_LOG("comm_fds:%d/%d", timer_th.comm_fds[0], timer_th.comm_fds[1]);
1636
1637 timer_thread_arm_comm_pipe();
1638}
1639
1640static int
1641event_wait(rb_vm_t *vm)
1642{
1643#if HAVE_SYS_EVENT_H
1644 int r = kqueue_wait(vm);
1645#else
1646 int r = epoll_wait(timer_th.event_fd, timer_th.finished_events, EPOLL_EVENTS_MAX, timer_thread_set_timeout(vm));
1647#endif
1648 return r;
1649}
1650
1651// How many waiters one pass may unlink before it stops holding waiting_lock.
1652#define FD_WAKE_BATCH 16
1653
1654
1655// Deliver an fd event to every thread waiting for it. Waiters are unlinked
1656// under waiting_lock but woken after releasing it (the scheduler lock is taken
1657// before it, never after); event_serial, captured under the lock, guards them.
1658static void
1659timer_thread_wake_fd_waiters(int fd, uint32_t generation, uint32_t wake_flags, int result)
1660{
1661 struct timer_wake batch[FD_WAKE_BATCH];
1662
1663 if (wake_flags == 0) return;
1664
1665 while (1) {
1666 int n = 0;
1667 bool more = false;
1668
1669 fd_shard_lock(fd);
1670 {
1671 struct rb_fd_waiters *e = fd_waiters_lookup(fd, false);
1672
1673 // A newer generation means the fd was disarmed after this event was
1674 // queued, and the number may name a different file by now.
1675 if (e != NULL && e->generation == generation) {
1676 struct rb_thread_sched_waiting *w, *nxt;
1677
1678 ccan_list_for_each_safe(&e->waiters, w, nxt, fd_node) {
1679 if (!(w->flags & wake_flags)) continue;
1680
1681 if (n == FD_WAKE_BATCH) {
1682 more = true;
1683 break;
1684 }
1685
1686 ccan_list_del_init(&w->fd_node);
1687
1688 if (w->flags & thread_sched_waiting_timeout) {
1689 // also leaves the timer wheel (or the expiry pass's
1690 // batch, whose claim will then find the flags gone)
1691 rb_native_mutex_lock(&timer_th.waiting_lock);
1692 timer_wheel_del(w);
1693 rb_native_mutex_unlock(&timer_th.waiting_lock);
1694 }
1695
1696 // The pin must be visible before the flags clear: a waiter
1697 // that sees the clear may skip parking, finish and die,
1698 // and the fence only waits on the pending count.
1699 batch[n].th = thread_sched_waiting_thread(w);
1700 batch[n].serial = w->data.event_serial;
1701 timer_wake_pending_inc(batch[n].th);
1702 n++;
1703
1704 w->flags = thread_sched_waiting_none;
1705 w->data.fd = -1;
1706 w->data.result = result;
1707 }
1708
1709 // Re-arm for whoever is still waiting on this fd (nothing, if
1710 // they all just woke up).
1711 fd_waiters_arm(fd, e, fd_waiters_union(e), true);
1712 }
1713 }
1714 fd_shard_unlock(fd);
1715
1716 for (int i = 0; i < n; i++) {
1717 timer_thread_wakeup_thread(batch[i].th, batch[i].serial);
1718 }
1719 timer_wake_pending_clear(batch, n);
1720
1721 if (!more) break;
1722 }
1723}
1724
1725/*
1726 * The purpose of the timer thread:
1727 *
1728 * (1) Periodic checking
1729 * (1-1) Provide time slice for active NTs
1730 * (1-2) Check NT shortage
1731 * (1-3) Periodic UBF (global)
1732 * (1-4) Lazy GRQ deq start
1733 * (2) Receive notification
1734 * (2-1) async I/O termination
1735 * (2-2) timeout
1736 * (2-2-1) sleep(n)
1737 * (2-2-2) timeout(n), I/O, ...
1738 */
1739static void
1740timer_thread_polling(rb_vm_t *vm)
1741{
1742 int r = event_wait(vm);
1743
1744 RUBY_DEBUG_LOG("r:%d errno:%d", r, errno);
1745
1746 switch (r) {
1747 case 0: // timeout
1748 RUBY_DEBUG_LOG("timeout%s", "");
1749
1750 ractor_sched_lock(vm, NULL);
1751 {
1752 // (1-1) timeslice
1753 timer_thread_check_timeslice(vm);
1754
1755 // (1-4) lazy grq deq
1756 if (vm->ractor.sched.grq_cnt > 0) {
1757 RUBY_DEBUG_LOG("GRQ cnt: %u", vm->ractor.sched.grq_cnt);
1758 rb_native_cond_signal(&vm->ractor.sched.cond);
1759 }
1760 }
1761 ractor_sched_unlock(vm, NULL);
1762
1763 // (1-2)
1764 native_thread_check_and_create_shared(vm);
1765
1766 break;
1767
1768 case -1:
1769 switch (errno) {
1770 case EINTR:
1771 // simply retry
1772 break;
1773 default:
1774 perror("event_wait");
1775 rb_bug("event_wait errno:%d", errno);
1776 }
1777 break;
1778
1779 default:
1780 RUBY_DEBUG_LOG("%d event(s)", r);
1781
1782#if HAVE_SYS_EVENT_H
1783 for (int i=0; i<r; i++) {
1784 uint64_t tag = (uint64_t)(uintptr_t)timer_th.finished_events[i].udata;
1785 int fd = (int)timer_th.finished_events[i].ident;
1786 int16_t filter = timer_th.finished_events[i].filter;
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 uint32_t wake_flags = kqueue_translate_filter_to_flags(filter) & FD_WAIT_IO_MASK;
1795 if (timer_th.finished_events[i].flags & (EV_EOF | EV_ERROR)) {
1796 wake_flags = FD_WAIT_IO_MASK; // end of file or error concerns everyone
1797 }
1798
1799 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, filter);
1800 }
1801#elif HAVE_SYS_EPOLL_H
1802 for (int i=0; i<r; i++) {
1803 uint64_t tag = timer_th.finished_events[i].data.u64;
1804 int fd = FD_EVENT_TAG_FD(tag);
1805 uint32_t events = timer_th.finished_events[i].events;
1806
1807 if (fd == timer_th.comm_fds[0]) {
1808 RUBY_DEBUG_LOG("comm from fd:%d", timer_th.comm_fds[1]);
1809 consume_communication_pipe(timer_th.comm_fds[0]);
1810 continue;
1811 }
1812
1813 RUBY_DEBUG_LOG("io event. fd:%d event:%s%s%s%s%s%s", fd,
1814 (events & EPOLLIN) ? "in/" : "",
1815 (events & EPOLLOUT) ? "out/" : "",
1816 (events & EPOLLRDHUP) ? "RDHUP/" : "",
1817 (events & EPOLLPRI) ? "pri/" : "",
1818 (events & EPOLLERR) ? "err/" : "",
1819 (events & EPOLLHUP) ? "hup/" : "");
1820
1821 uint32_t wake_flags = 0;
1822 if (events & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) wake_flags |= thread_sched_waiting_io_read;
1823 if (events & EPOLLOUT) wake_flags |= thread_sched_waiting_io_write;
1824 // An error or hangup ends every wait on this fd, in either direction.
1825 if (events & (EPOLLERR | EPOLLHUP)) wake_flags |= FD_WAIT_IO_MASK;
1826
1827 timer_thread_wake_fd_waiters(fd, FD_EVENT_TAG_GEN(tag), wake_flags, (int)events);
1828 }
1829#else
1830# error "neither kqueue nor epoll"
1831#endif
1832 }
1833}
1834
1835#endif // HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H
1836
1837#else // USE_MN_THREADS
1838
1839static void
1840timer_thread_setup_mn(void)
1841{
1842 // do nothing
1843}
1844
1845static void
1846timer_thread_polling(rb_vm_t *vm)
1847{
1848 int timeout = timer_thread_set_timeout(vm);
1849
1850 struct pollfd pfd = {
1851 .fd = timer_th.comm_fds[0],
1852 .events = POLLIN,
1853 };
1854
1855 int r = poll(&pfd, 1, timeout);
1856
1857 switch (r) {
1858 case 0: // timeout
1859 ractor_sched_lock(vm, NULL);
1860 {
1861 // (1-1) timeslice
1862 timer_thread_check_timeslice(vm);
1863 }
1864 ractor_sched_unlock(vm, NULL);
1865 break;
1866
1867 case -1: // error
1868 switch (errno) {
1869 case EINTR:
1870 // simply retry
1871 break;
1872 default:
1873 perror("poll");
1874 rb_bug("poll errno:%d", errno);
1875 break;
1876 }
1877
1878 case 1:
1879 consume_communication_pipe(timer_th.comm_fds[0]);
1880 break;
1881
1882 default:
1883 rb_bug("unreachbale");
1884 }
1885}
1886
1887static int
1888native_thread_create_shared(rb_thread_t *th)
1889{
1890 rb_bug("unreachable");
1891}
1892
1893static enum thread_sched_wait_result
1894thread_sched_wait_events(struct rb_thread_sched *sched, rb_thread_t *th, int fd, enum thread_sched_waiting_flag events, rb_hrtime_t *rel)
1895{
1896 rb_bug("unreachable");
1897}
1898
1899// Without the wheel every thread is dedicated, so a Ractor wait takes its
1900// deadline on its own condvar and never reaches these.
1901static bool
1902ractor_sched_timeout_arm(rb_thread_t *th, const rb_hrtime_t *rel)
1903{
1904 rb_bug("unreachable");
1905}
1906
1907static bool
1908ractor_sched_timeout_disarm(rb_thread_t *th)
1909{
1910 rb_bug("unreachable");
1911}
1912
1913static int
1914timer_wheel_timeout(int timeout)
1915{
1916 return timeout; // no M:N threads, no timed waiters
1917}
1918
1919static void
1920timer_thread_wake_fence(rb_thread_t *th)
1921{
1922 // no timer wheel, no wake batches
1923}
1924
1925static void
1926timer_thread_check_timeout(rb_vm_t *vm)
1927{
1928 // no M:N threads, no timed waiters
1929}
1930
1931#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