Ruby 4.1.0dev (2026-08-15 revision d1c079751b80352d347452c8d134ffb177838adb)
thread_pthread.c (d1c079751b80352d347452c8d134ffb177838adb)
1/* -*-c-*- */
2/**********************************************************************
3
4 thread_pthread.c -
5
6 $Author$
7
8 Copyright (C) 2004-2007 Koichi Sasada
9
10**********************************************************************/
11
12#ifdef THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION
13
14#include "internal/gc.h"
15#include "internal/sanitizers.h"
16
17#ifdef HAVE_SYS_RESOURCE_H
18#include <sys/resource.h>
19#endif
20#ifdef HAVE_THR_STKSEGMENT
21#include <thread.h>
22#endif
23#if defined(HAVE_FCNTL_H)
24#include <fcntl.h>
25#elif defined(HAVE_SYS_FCNTL_H)
26#include <sys/fcntl.h>
27#endif
28#ifdef HAVE_SYS_PRCTL_H
29#include <sys/prctl.h>
30#endif
31#if defined(HAVE_SYS_TIME_H)
32#include <sys/time.h>
33#endif
34#if defined(__HAIKU__)
35#include <kernel/OS.h>
36#endif
37#ifdef __linux__
38#include <sys/syscall.h> /* for SYS_gettid */
39#endif
40#include <time.h>
41#include <signal.h>
42
43#include "probes.h"
44
45#if defined __APPLE__
46# include <AvailabilityMacros.h>
47#endif
48
49#if defined(HAVE_SYS_EVENTFD_H) && defined(HAVE_EVENTFD)
50# define USE_EVENTFD (1)
51# include <sys/eventfd.h>
52#else
53# define USE_EVENTFD (0)
54#endif
55
56#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && \
57 defined(CLOCK_REALTIME) && defined(CLOCK_MONOTONIC) && \
58 defined(HAVE_CLOCK_GETTIME)
59static pthread_condattr_t condattr_mono;
60static pthread_condattr_t *condattr_monotonic = &condattr_mono;
61#else
62static const void *const condattr_monotonic = NULL;
63#endif
64
65#include COROUTINE_H
66
67#ifndef HAVE_SYS_EVENT_H
68#define HAVE_SYS_EVENT_H 0
69#endif
70
71#ifndef HAVE_SYS_EPOLL_H
72#define HAVE_SYS_EPOLL_H 0
73#else
74// force setting for debug
75// #undef HAVE_SYS_EPOLL_H
76// #define HAVE_SYS_EPOLL_H 0
77#endif
78
79#ifndef USE_MN_THREADS
80 #if defined(__EMSCRIPTEN__) || defined(COROUTINE_PTHREAD_CONTEXT)
81 // on __EMSCRIPTEN__ provides epoll* declarations, but no implementations.
82 // on COROUTINE_PTHREAD_CONTEXT, it doesn't worth to use it.
83 #define USE_MN_THREADS 0
84 #elif HAVE_SYS_EPOLL_H
85 #include <sys/epoll.h>
86 #define USE_MN_THREADS 1
87 #elif HAVE_SYS_EVENT_H
88 #include <sys/event.h>
89 #define USE_MN_THREADS 1
90 #else
91 #define USE_MN_THREADS 0
92 #endif
93#endif
94
95#ifdef HAVE_SCHED_YIELD
96#define native_thread_yield() (void)sched_yield()
97#else
98#define native_thread_yield() ((void)0)
99#endif
100
101// native thread wrappers
102
103#define NATIVE_MUTEX_LOCK_DEBUG 0
104#define NATIVE_MUTEX_LOCK_DEBUG_YIELD 0
105
106static void
107mutex_debug(const char *msg, void *lock)
108{
109 if (NATIVE_MUTEX_LOCK_DEBUG) {
110 int r;
111 static pthread_mutex_t dbglock = PTHREAD_MUTEX_INITIALIZER;
112
113 if ((r = pthread_mutex_lock(&dbglock)) != 0) {exit(EXIT_FAILURE);}
114 fprintf(stdout, "%s: %p\n", msg, lock);
115 if ((r = pthread_mutex_unlock(&dbglock)) != 0) {exit(EXIT_FAILURE);}
116 }
117}
118
119void
120rb_native_mutex_lock(pthread_mutex_t *lock)
121{
122 int r;
123#if NATIVE_MUTEX_LOCK_DEBUG_YIELD
124 native_thread_yield();
125#endif
126 mutex_debug("lock", lock);
127 if ((r = pthread_mutex_lock(lock)) != 0) {
128 rb_bug_errno("pthread_mutex_lock", r);
129 }
130}
131
132void
133rb_native_mutex_unlock(pthread_mutex_t *lock)
134{
135 int r;
136 mutex_debug("unlock", lock);
137 if ((r = pthread_mutex_unlock(lock)) != 0) {
138 rb_bug_errno("pthread_mutex_unlock", r);
139 }
140}
141
142int
143rb_native_mutex_trylock(pthread_mutex_t *lock)
144{
145 int r;
146 mutex_debug("trylock", lock);
147 if ((r = pthread_mutex_trylock(lock)) != 0) {
148 if (r == EBUSY) {
149 return EBUSY;
150 }
151 else {
152 rb_bug_errno("pthread_mutex_trylock", r);
153 }
154 }
155 return 0;
156}
157
158void
159rb_native_mutex_initialize(pthread_mutex_t *lock)
160{
161 int r = pthread_mutex_init(lock, 0);
162 mutex_debug("init", lock);
163 if (r != 0) {
164 rb_bug_errno("pthread_mutex_init", r);
165 }
166}
167
168void
169rb_native_mutex_destroy(pthread_mutex_t *lock)
170{
171 int r = pthread_mutex_destroy(lock);
172 mutex_debug("destroy", lock);
173 if (r != 0) {
174 rb_bug_errno("pthread_mutex_destroy", r);
175 }
176}
177
178void
179rb_native_cond_initialize(rb_nativethread_cond_t *cond)
180{
181 int r = pthread_cond_init(cond, condattr_monotonic);
182 if (r != 0) {
183 rb_bug_errno("pthread_cond_init", r);
184 }
185}
186
187void
188rb_native_cond_destroy(rb_nativethread_cond_t *cond)
189{
190 int r = pthread_cond_destroy(cond);
191 if (r != 0) {
192 rb_bug_errno("pthread_cond_destroy", r);
193 }
194}
195
196/*
197 * In OS X 10.7 (Lion), pthread_cond_signal and pthread_cond_broadcast return
198 * EAGAIN after retrying 8192 times. You can see them in the following page:
199 *
200 * http://www.opensource.apple.com/source/Libc/Libc-763.11/pthreads/pthread_cond.c
201 *
202 * The following rb_native_cond_signal and rb_native_cond_broadcast functions
203 * need to retrying until pthread functions don't return EAGAIN.
204 */
205
206void
207rb_native_cond_signal(rb_nativethread_cond_t *cond)
208{
209 int r;
210 do {
211 r = pthread_cond_signal(cond);
212 } while (r == EAGAIN);
213 if (r != 0) {
214 rb_bug_errno("pthread_cond_signal", r);
215 }
216}
217
218void
219rb_native_cond_broadcast(rb_nativethread_cond_t *cond)
220{
221 int r;
222 do {
223 r = pthread_cond_broadcast(cond);
224 } while (r == EAGAIN);
225 if (r != 0) {
226 rb_bug_errno("rb_native_cond_broadcast", r);
227 }
228}
229
230void
231rb_native_cond_wait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex)
232{
233 int r = pthread_cond_wait(cond, mutex);
234 if (r != 0) {
235 rb_bug_errno("pthread_cond_wait", r);
236 }
237}
238
239static int
240native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, const rb_hrtime_t *abs)
241{
242 int r;
243 struct timespec ts;
244
245 /*
246 * An old Linux may return EINTR. Even though POSIX says
247 * "These functions shall not return an error code of [EINTR]".
248 * http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_cond_timedwait.html
249 * Let's hide it from arch generic code.
250 */
251 do {
252 rb_hrtime2timespec(&ts, abs);
253 r = pthread_cond_timedwait(cond, mutex, &ts);
254 } while (r == EINTR);
255
256 if (r != 0 && r != ETIMEDOUT) {
257 rb_bug_errno("pthread_cond_timedwait", r);
258 }
259
260 return r;
261}
262
263static rb_hrtime_t
264native_cond_timeout(rb_nativethread_cond_t *cond, const rb_hrtime_t rel)
265{
266 if (condattr_monotonic) {
267 return rb_hrtime_add(rb_hrtime_now(), rel);
268 }
269 else {
270 struct timespec ts;
271
272 rb_timespec_now(&ts);
273 return rb_hrtime_add(rb_timespec2hrtime(&ts), rel);
274 }
275}
276
277void
278rb_native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, unsigned long msec)
279{
280 rb_hrtime_t hrmsec = native_cond_timeout(cond, RB_HRTIME_PER_MSEC * msec);
281 native_cond_timedwait(cond, mutex, &hrmsec);
282}
283
284// thread scheduling
285
286static rb_internal_thread_event_hook_t *rb_internal_thread_event_hooks = NULL;
287static void rb_thread_execute_hooks(rb_event_flag_t event, rb_thread_t *th);
288
289#if 0
290static const char *
291event_name(rb_event_flag_t event)
292{
293 switch (event) {
295 return "STARTED";
297 return "READY";
299 return "RESUMED";
301 return "SUSPENDED";
303 return "EXITED";
304 }
305 return "no-event";
306}
307
308#define RB_INTERNAL_THREAD_HOOK(event, th) \
309 if (UNLIKELY(rb_internal_thread_event_hooks)) { \
310 fprintf(stderr, "[thread=%"PRIxVALUE"] %s in %s (%s:%d)\n", th->self, event_name(event), __func__, __FILE__, __LINE__); \
311 rb_thread_execute_hooks(event, th); \
312 }
313#else
314#define RB_INTERNAL_THREAD_HOOK(event, th) if (UNLIKELY(rb_internal_thread_event_hooks)) { rb_thread_execute_hooks(event, th); }
315#endif
316
317static rb_serial_t current_fork_gen = 1; /* We can't use GET_VM()->fork_gen */
318
319#if defined(SIGVTALRM) && !defined(__EMSCRIPTEN__)
320# define USE_UBF_LIST 1
321#endif
322
323static void threadptr_trap_interrupt(rb_thread_t *);
324
325static void native_thread_dedicated_inc(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt);
326static void native_thread_dedicated_dec(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt);
327static void native_thread_assign(struct rb_native_thread *nt, rb_thread_t *th);
328
329static void ractor_sched_enq(rb_vm_t *vm, rb_ractor_t *r);
330static void timer_thread_wakeup(void);
331static void timer_thread_wakeup_locked(rb_vm_t *vm);
332static void timer_thread_wakeup_force(void);
333static void thread_sched_switch(rb_thread_t *cth, rb_thread_t *next_th);
334static void ractor_sched_cancel_enq(rb_vm_t *vm, struct rb_thread_sched *sched);
335#if USE_MN_THREADS
336static void nt_machine_stack_atfork(void);
337
338// A coroutine thread's execution context: the coroutine_context (first, so
339// th->sched.context points at the whole block) plus what is needed to free
340// it without the rb_thread_t. Owned by the execution: the dying thread marks
341// it dead before its final transfer, and whichever context RESUMES from that
342// transfer frees it -- the resume itself proves the transfer's register save
343// into this block has completed, so no further synchronization is needed.
344struct rb_thread_context {
345 struct coroutine_context co; // must be first
346 void *stack; // the coroutine machine stack (pool stack)
347 struct rb_native_thread *nt; // final transfer target, stashed at termination
348 bool dead;
349};
350
351static bool thread_sched_reclaim(struct coroutine_context *dead_co);
352#endif
353static void coroutine_transfer0(struct coroutine_context *transfer_from,
354 struct coroutine_context *transfer_to, bool to_dead);
355
356#define thread_sched_dump(s) thread_sched_dump_(__FILE__, __LINE__, s)
357
358static bool
359th_has_dedicated_nt(const rb_thread_t *th)
360{
361 // TODO: th->has_dedicated_nt
362 return th->nt->dedicated > 0;
363}
364
366static void
367thread_sched_dump_(const char *file, int line, struct rb_thread_sched *sched)
368{
369 fprintf(stderr, "@%s:%d running:%d\n", file, line, sched->running ? (int)sched->running->serial : -1);
370 rb_thread_t *th;
371 int i = 0;
372 ccan_list_for_each(&sched->readyq, th, sched.node.readyq) {
373 i++; if (i>10) rb_bug("too many");
374 fprintf(stderr, " ready:%d (%sNT:%d)\n", th->serial,
375 th->nt ? (th->nt->dedicated ? "D" : "S") : "x",
376 th->nt ? (int)th->nt->serial : -1);
377 }
378}
379
380#define ractor_sched_dump(s) ractor_sched_dump_(__FILE__, __LINE__, s)
381
383static void
384ractor_sched_dump_(const char *file, int line, rb_vm_t *vm)
385{
386 rb_ractor_t *r;
387
388 fprintf(stderr, "ractor_sched_dump %s:%d\n", file, line);
389
390 int i = 0;
391 ccan_list_for_each(&vm->ractor.sched.grq, r, threads.sched.grq_node) {
392 i++;
393 if (i>10) rb_bug("!!");
394 fprintf(stderr, " %d ready:%d\n", i, rb_ractor_id(r));
395 }
396}
397
398#define thread_sched_lock(a, b) thread_sched_lock_(a, b, __FILE__, __LINE__)
399#define thread_sched_unlock(a, b) thread_sched_unlock_(a, b, __FILE__, __LINE__)
400
401static void
402thread_sched_set_locked(struct rb_thread_sched *sched, rb_thread_t *th)
403{
404#if VM_CHECK_MODE > 0
405 VM_ASSERT(sched->lock_owner == NULL);
406
407 sched->lock_owner = th;
408#endif
409}
410
411static void
412thread_sched_set_unlocked(struct rb_thread_sched *sched, rb_thread_t *th)
413{
414#if VM_CHECK_MODE > 0
415 VM_ASSERT(sched->lock_owner == th);
416
417 sched->lock_owner = NULL;
418#endif
419}
420
421static void
422thread_sched_lock_(struct rb_thread_sched *sched, rb_thread_t *th, const char *file, int line)
423{
424 rb_native_mutex_lock(&sched->lock_);
425
426#if VM_CHECK_MODE
427 RUBY_DEBUG_LOG2(file, line, "r:%d th:%u", th ? (int)rb_ractor_id(th->ractor) : -1, rb_th_serial(th));
428#else
429 RUBY_DEBUG_LOG2(file, line, "th:%u", rb_th_serial(th));
430#endif
431
432 thread_sched_set_locked(sched, th);
433}
434
435static void
436thread_sched_unlock_(struct rb_thread_sched *sched, rb_thread_t *th, const char *file, int line)
437{
438 RUBY_DEBUG_LOG2(file, line, "th:%u", rb_th_serial(th));
439
440 thread_sched_set_unlocked(sched, th);
441
442 rb_native_mutex_unlock(&sched->lock_);
443}
444
445static void
446ASSERT_thread_sched_locked(struct rb_thread_sched *sched, rb_thread_t *th)
447{
448 VM_ASSERT(rb_native_mutex_trylock(&sched->lock_) == EBUSY);
449
450#if VM_CHECK_MODE
451 if (th) {
452 VM_ASSERT(sched->lock_owner == th);
453 }
454 else {
455 VM_ASSERT(sched->lock_owner != NULL);
456 }
457#endif
458}
459
460#define ractor_sched_lock(a, b) ractor_sched_lock_(a, b, __FILE__, __LINE__)
461#define ractor_sched_unlock(a, b) ractor_sched_unlock_(a, b, __FILE__, __LINE__)
462
464static unsigned int
465rb_ractor_serial(const rb_ractor_t *r)
466{
467 if (r) {
468 return rb_ractor_id(r);
469 }
470 else {
471 return 0;
472 }
473}
474
475static void
476ractor_sched_set_locked(rb_vm_t *vm, rb_ractor_t *cr)
477{
478#if VM_CHECK_MODE > 0
479 VM_ASSERT(vm->ractor.sched.lock_owner == NULL);
480 VM_ASSERT(vm->ractor.sched.locked == false);
481
482 vm->ractor.sched.lock_owner = cr;
483 vm->ractor.sched.locked = true;
484#endif
485}
486
487static void
488ractor_sched_set_unlocked(rb_vm_t *vm, rb_ractor_t *cr)
489{
490#if VM_CHECK_MODE > 0
491 VM_ASSERT(vm->ractor.sched.locked);
492 VM_ASSERT(vm->ractor.sched.lock_owner == cr);
493
494 vm->ractor.sched.locked = false;
495 vm->ractor.sched.lock_owner = NULL;
496#endif
497}
498
499
500static void
501ractor_sched_lock_(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
502{
503 rb_native_mutex_lock(&vm->ractor.sched.lock);
504
505#if VM_CHECK_MODE
506 RUBY_DEBUG_LOG2(file, line, "cr:%u prev_owner:%u", rb_ractor_serial(cr), rb_ractor_serial(vm->ractor.sched.lock_owner));
507#else
508 RUBY_DEBUG_LOG2(file, line, "cr:%u", rb_ractor_serial(cr));
509#endif
510
511 ractor_sched_set_locked(vm, cr);
512}
513
514static void
515ractor_sched_unlock_(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
516{
517 RUBY_DEBUG_LOG2(file, line, "cr:%u", rb_ractor_serial(cr));
518
519 ractor_sched_set_unlocked(vm, cr);
520 rb_native_mutex_unlock(&vm->ractor.sched.lock);
521}
522
523static void
524ASSERT_ractor_sched_locked(rb_vm_t *vm, rb_ractor_t *cr)
525{
526 VM_ASSERT(rb_native_mutex_trylock(&vm->ractor.sched.lock) == EBUSY);
527 VM_ASSERT(vm->ractor.sched.locked);
528 VM_ASSERT(cr == NULL || vm->ractor.sched.lock_owner == cr);
529}
530
532static bool
533ractor_sched_running_threads_contain_p(rb_vm_t *vm, rb_thread_t *th)
534{
535 rb_thread_t *rth;
536 ccan_list_for_each(&vm->ractor.sched.running_threads, rth, sched.node.running_threads) {
537 if (rth == th) return true;
538 }
539 return false;
540}
541
543static unsigned int
544ractor_sched_running_threads_size(rb_vm_t *vm)
545{
546 rb_thread_t *th;
547 unsigned int i = 0;
548 ccan_list_for_each(&vm->ractor.sched.running_threads, th, sched.node.running_threads) {
549 i++;
550 }
551 return i;
552}
553
555static unsigned int
556ractor_sched_timeslice_threads_size(rb_vm_t *vm)
557{
558 rb_thread_t *th;
559 unsigned int i = 0;
560 ccan_list_for_each(&vm->ractor.sched.timeslice_threads, th, sched.node.timeslice_threads) {
561 i++;
562 }
563 return i;
564}
565
567static bool
568ractor_sched_timeslice_threads_contain_p(rb_vm_t *vm, rb_thread_t *th)
569{
570 rb_thread_t *rth;
571 ccan_list_for_each(&vm->ractor.sched.timeslice_threads, rth, sched.node.timeslice_threads) {
572 if (rth == th) return true;
573 }
574 return false;
575}
576
577static void ractor_sched_barrier_join_signal_locked(rb_vm_t *vm);
578
579// setup timeslice signals by the timer thread.
580static void
581thread_sched_setup_running_threads(struct rb_thread_sched *sched, rb_ractor_t *cr, rb_vm_t *vm,
582 rb_thread_t *add_th, rb_thread_t *del_th, rb_thread_t *add_timeslice_th)
583{
584#if USE_RUBY_DEBUG_LOG
585 unsigned int prev_running_cnt = vm->ractor.sched.running_cnt;
586#endif
587
588 rb_thread_t *del_timeslice_th;
589
590 if (del_th && sched->is_running_timeslice) {
591 del_timeslice_th = del_th;
592 sched->is_running_timeslice = false;
593 }
594 else {
595 del_timeslice_th = NULL;
596 }
597
598 RUBY_DEBUG_LOG("+:%u -:%u +ts:%u -ts:%u",
599 rb_th_serial(add_th), rb_th_serial(del_th),
600 rb_th_serial(add_timeslice_th), rb_th_serial(del_timeslice_th));
601
602 ractor_sched_lock(vm, cr);
603 {
604 // update running_threads
605 if (del_th) {
606 VM_ASSERT(ractor_sched_running_threads_contain_p(vm, del_th));
607 VM_ASSERT(del_timeslice_th != NULL ||
608 !ractor_sched_timeslice_threads_contain_p(vm, del_th));
609
610 ccan_list_del_init(&del_th->sched.node.running_threads);
611 vm->ractor.sched.running_cnt--;
612
613 if (UNLIKELY(vm->ractor.sched.barrier_waiting)) {
614 ractor_sched_barrier_join_signal_locked(vm);
615 }
616 sched->is_running = false;
617 }
618
619 if (add_th) {
620 if (vm->ractor.sched.barrier_waiting) {
621 // TODO: GC barrier check?
622 RUBY_DEBUG_LOG("barrier_waiting");
623 RUBY_VM_SET_VM_BARRIER_INTERRUPT(add_th->ec);
624 }
625
626 VM_ASSERT(!ractor_sched_running_threads_contain_p(vm, add_th));
627 VM_ASSERT(!ractor_sched_timeslice_threads_contain_p(vm, add_th));
628
629 ccan_list_add(&vm->ractor.sched.running_threads, &add_th->sched.node.running_threads);
630 vm->ractor.sched.running_cnt++;
631 sched->is_running = true;
632 }
633
634 if (add_timeslice_th) {
635 // update timeslice threads
636 int was_empty = ccan_list_empty(&vm->ractor.sched.timeslice_threads);
637 VM_ASSERT(!ractor_sched_timeslice_threads_contain_p(vm, add_timeslice_th));
638 ccan_list_add(&vm->ractor.sched.timeslice_threads, &add_timeslice_th->sched.node.timeslice_threads);
639 sched->is_running_timeslice = true;
640 if (was_empty) {
641 timer_thread_wakeup_locked(vm);
642 }
643 }
644
645 if (del_timeslice_th) {
646 VM_ASSERT(ractor_sched_timeslice_threads_contain_p(vm, del_timeslice_th));
647 ccan_list_del_init(&del_timeslice_th->sched.node.timeslice_threads);
648 }
649
650 VM_ASSERT(ractor_sched_running_threads_size(vm) == vm->ractor.sched.running_cnt);
651 VM_ASSERT(ractor_sched_timeslice_threads_size(vm) <= vm->ractor.sched.running_cnt);
652 }
653 ractor_sched_unlock(vm, cr);
654
655 //RUBY_DEBUG_LOG("+:%u -:%u +ts:%u -ts:%u run:%u->%u",
656 // rb_th_serial(add_th), rb_th_serial(del_th),
657 // rb_th_serial(add_timeslice_th), rb_th_serial(del_timeslice_th),
658 RUBY_DEBUG_LOG("run:%u->%u", prev_running_cnt, vm->ractor.sched.running_cnt);
659}
660
661static void
662thread_sched_add_running_thread(struct rb_thread_sched *sched, rb_thread_t *th)
663{
664 ASSERT_thread_sched_locked(sched, th);
665 VM_ASSERT(sched->running == th);
666
667 rb_vm_t *vm = th->vm;
668 thread_sched_setup_running_threads(sched, th->ractor, vm, th, NULL, ccan_list_empty(&sched->readyq) ? NULL : th);
669}
670
671static void
672thread_sched_del_running_thread(struct rb_thread_sched *sched, rb_thread_t *th)
673{
674 ASSERT_thread_sched_locked(sched, th);
675
676 rb_vm_t *vm = th->vm;
677 thread_sched_setup_running_threads(sched, th->ractor, vm, NULL, th, NULL);
678}
679
680void
681rb_add_running_thread(rb_thread_t *th)
682{
683 struct rb_thread_sched *sched = TH_SCHED(th);
684
685 thread_sched_lock(sched, th);
686 {
687 thread_sched_add_running_thread(sched, th);
688 }
689 thread_sched_unlock(sched, th);
690}
691
692void
693rb_del_running_thread(rb_thread_t *th)
694{
695 struct rb_thread_sched *sched = TH_SCHED(th);
696
697 thread_sched_lock(sched, th);
698 {
699 thread_sched_del_running_thread(sched, th);
700 }
701 thread_sched_unlock(sched, th);
702}
703
704// setup current or next running thread
705// sched->running should be set only on this function.
706//
707// if th is NULL, there is no running threads.
708static void
709thread_sched_set_running(struct rb_thread_sched *sched, rb_thread_t *th)
710{
711 RUBY_DEBUG_LOG("th:%u->th:%u", rb_th_serial(sched->running), rb_th_serial(th));
712 VM_ASSERT(sched->running != th);
713
714 if (RUBY_DTRACE_RTS_SET_RUNNING_ENABLED()) {
715 RUBY_DTRACE_RTS_SET_RUNNING(sched, sched->running, th);
716 }
717
718 sched->running = th;
719}
720
722static bool
723thread_sched_readyq_contain_p(struct rb_thread_sched *sched, rb_thread_t *th)
724{
725 rb_thread_t *rth;
726 ccan_list_for_each(&sched->readyq, rth, sched.node.readyq) {
727 if (rth == th) {
728 VM_ASSERT(th->sched.node.is_ready);
729 return true;
730 }
731 }
732 VM_ASSERT(!th->sched.node.is_ready);
733 return false;
734}
735
736// deque thread from the ready queue.
737// if the ready queue is empty, return NULL.
738//
739// return deque'ed running thread (or NULL).
740static rb_thread_t *
741thread_sched_deq(struct rb_thread_sched *sched)
742{
743 ASSERT_thread_sched_locked(sched, NULL);
744 rb_thread_t *next_th;
745
746 VM_ASSERT(sched->running != NULL);
747
748 if (ccan_list_empty(&sched->readyq)) {
749 next_th = NULL;
750 }
751 else {
752 next_th = ccan_list_pop(&sched->readyq, rb_thread_t, sched.node.readyq);
753 VM_ASSERT(next_th->sched.node.is_ready);
754 next_th->sched.node.is_ready = false;
755
756 VM_ASSERT(sched->readyq_cnt > 0);
757 sched->readyq_cnt--;
758 ccan_list_node_init(&next_th->sched.node.readyq);
759 }
760
761 RUBY_DEBUG_LOG("next_th:%u readyq_cnt:%d", rb_th_serial(next_th), sched->readyq_cnt);
762
763 return next_th;
764}
765
766// enqueue ready thread to the ready queue.
767static void
768thread_sched_enq(struct rb_thread_sched *sched, rb_thread_t *ready_th)
769{
770 ASSERT_thread_sched_locked(sched, NULL);
771 RUBY_DEBUG_LOG("ready_th:%u readyq_cnt:%d", rb_th_serial(ready_th), sched->readyq_cnt);
772
773 VM_ASSERT(sched->running != NULL);
774 VM_ASSERT(!thread_sched_readyq_contain_p(sched, ready_th));
775
776 if (sched->is_running) {
777 if (ccan_list_empty(&sched->readyq)) {
778 // add sched->running to timeslice
779 thread_sched_setup_running_threads(sched, ready_th->ractor, ready_th->vm, NULL, NULL, sched->running);
780 }
781 }
782 else {
783 // ractor_sched lock is needed
784 // VM_ASSERT(!ractor_sched_timeslice_threads_contain_p(ready_th->vm, sched->running));
785 }
786
787 ccan_list_add_tail(&sched->readyq, &ready_th->sched.node.readyq);
788 ready_th->sched.node.is_ready = true;
789 sched->readyq_cnt++;
790}
791
792// DNT: kick condvar
793// SNT: TODO
794static void
795thread_sched_wakeup_running_thread(struct rb_thread_sched *sched, rb_thread_t *next_th, bool will_switch)
796{
797 ASSERT_thread_sched_locked(sched, NULL);
798 VM_ASSERT(sched->running == next_th);
799
800 if (next_th) {
801 if (next_th->nt) {
802 if (th_has_dedicated_nt(next_th)) {
803 RUBY_DEBUG_LOG("pinning th:%u", next_th->serial);
804 rb_native_cond_signal(&next_th->nt->cond.readyq);
805 }
806 else {
807 // TODO
808 RUBY_DEBUG_LOG("th:%u is already running.", next_th->serial);
809 }
810 }
811 else {
812 if (will_switch) {
813 RUBY_DEBUG_LOG("th:%u (do nothing)", rb_th_serial(next_th));
814 }
815 else {
816 RUBY_DEBUG_LOG("th:%u (enq)", rb_th_serial(next_th));
817 ractor_sched_enq(next_th->vm, next_th->ractor);
818 }
819 }
820 }
821 else {
822 RUBY_DEBUG_LOG("no waiting threads%s", "");
823 }
824}
825
826// waiting -> ready (locked)
827static void
828thread_sched_to_ready_common(struct rb_thread_sched *sched, rb_thread_t *th, bool wakeup, bool will_switch)
829{
830 RUBY_DEBUG_LOG("th:%u running:%u redyq_cnt:%d", rb_th_serial(th), rb_th_serial(sched->running), sched->readyq_cnt);
831
832 VM_ASSERT(sched->running != th);
833 VM_ASSERT(!thread_sched_readyq_contain_p(sched, th));
834 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_READY, th);
835
836 if (sched->running == NULL) {
837 thread_sched_set_running(sched, th);
838 if (wakeup) thread_sched_wakeup_running_thread(sched, th, will_switch);
839 }
840 else {
841 thread_sched_enq(sched, th);
842 }
843}
844
845// waiting -> ready
846//
847// `th` had became "waiting" state by `thread_sched_to_waiting`
848// and `thread_sched_to_ready` enqueue `th` to the thread ready queue.
850static void
851thread_sched_to_ready(struct rb_thread_sched *sched, rb_thread_t *th)
852{
853 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
854
855 thread_sched_lock(sched, th);
856 {
857 thread_sched_to_ready_common(sched, th, true, false);
858 }
859 thread_sched_unlock(sched, th);
860}
861
862// wait until sched->running is `th`.
863static void
864thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, bool can_direct_transfer)
865{
866 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
867
868 ASSERT_thread_sched_locked(sched, th);
869 VM_ASSERT(th == rb_ec_thread_ptr(rb_current_ec_noinline()));
870
871 if (th != sched->running) {
872 // TODO: This optimization should also be made to work for MN_THREADS
873 if (th->has_dedicated_nt && th == sched->runnable_hot_th && (sched->running == NULL || sched->running->has_dedicated_nt)) {
874 RUBY_DEBUG_LOG("(nt) stealing: hot-th:%u. running:%u", rb_th_serial(th), rb_th_serial(sched->running));
875
876 // th serves itself on its own nt, displacing the enqueued
877 // running thread back to the readyq: cancel the entry that was
878 // posted for it (a later dequeue would find this Ractor served
879 // and its next enqueue would double-list the node)
880 ractor_sched_cancel_enq(th->vm, sched);
881
882 // If there is a thread set to run, move it back to the front of the readyq
883 if (sched->running != NULL) {
884 rb_thread_t *running = sched->running;
885 VM_ASSERT(!thread_sched_readyq_contain_p(sched, running));
886 running->sched.node.is_ready = true;
887 ccan_list_add(&sched->readyq, &running->sched.node.readyq);
888 sched->readyq_cnt++;
889 }
890
891 // Pull off the ready queue and start running.
892 if (th->sched.node.is_ready) {
893 VM_ASSERT(thread_sched_readyq_contain_p(sched, th));
894 ccan_list_del_init(&th->sched.node.readyq);
895 th->sched.node.is_ready = false;
896 sched->readyq_cnt--;
897 }
898 thread_sched_set_running(sched, th);
899 rb_ractor_thread_switch(th->ractor, th, false);
900 }
901 else if (th == sched->runnable_hot_th) {
902 // The hot thread cannot steal the control (e.g. the running thread
903 // is an MN thread). It is going to sleep, so it is no longer spinning;
904 // drop the hint so that other threads don't yield the lock to it.
905 sched->runnable_hot_th = NULL;
906 sched->runnable_hot_th_waiting = 0;
907 }
908
909 // already deleted from running threads
910 // VM_ASSERT(!ractor_sched_running_threads_contain_p(th->vm, th)); // need locking
911
912 // wait for execution right
913 rb_thread_t *next_th;
914 while((next_th = sched->running) != th) {
915 if (th_has_dedicated_nt(th)) {
916 RUBY_DEBUG_LOG("(nt) sleep th:%u running:%u", rb_th_serial(th), rb_th_serial(sched->running));
917
918 thread_sched_set_unlocked(sched, th);
919 {
920 RUBY_DEBUG_LOG("nt:%d cond:%p", th->nt->serial, &th->nt->cond.readyq);
921 rb_native_cond_wait(&th->nt->cond.readyq, &sched->lock_);
922 }
923 thread_sched_set_locked(sched, th);
924
925 if (sched->runnable_hot_th != NULL && sched->runnable_hot_th_waiting) {
926 VM_ASSERT(sched->runnable_hot_th != th);
927 // Give the hot thread a chance to preempt, if it's actively spinning.
928 // On multicore, this reduces the rate of core-switching. On single-core it
929 // should mostly be a nop, since the other thread can't be concurrently spinning.
930 thread_sched_unlock(sched, th);
931 thread_sched_lock(sched, th);
932 }
933
934 RUBY_DEBUG_LOG("(nt) wakeup %s", sched->running == th ? "success" : "failed");
935 if (th == sched->running) {
936 rb_ractor_thread_switch(th->ractor, th, false);
937 }
938 }
939 else {
940 // search another ready thread
941 if (can_direct_transfer &&
942 (next_th = sched->running) != NULL &&
943 !next_th->nt // next_th is running or has dedicated nt
944 ) {
945
946 RUBY_DEBUG_LOG("th:%u->%u (direct)", rb_th_serial(th), rb_th_serial(next_th));
947
948 thread_sched_set_unlocked(sched, th);
949 {
950 rb_ractor_set_current_ec(th->ractor, NULL);
951 thread_sched_switch(th, next_th);
952 }
953 thread_sched_set_locked(sched, th);
954 }
955 else {
956 // search another ready ractor
957 struct rb_native_thread *nt = th->nt;
958 native_thread_assign(NULL, th);
959
960 RUBY_DEBUG_LOG("th:%u->%u (ractor scheduling)", rb_th_serial(th), rb_th_serial(next_th));
961
962 thread_sched_set_unlocked(sched, th);
963 {
964 rb_ractor_set_current_ec(th->ractor, NULL);
965 coroutine_transfer0(th->sched.context, nt->nt_context, false);
966 }
967 thread_sched_set_locked(sched, th);
968 }
969
970 VM_ASSERT(rb_current_ec_noinline() == th->ec);
971 }
972 }
973
974 VM_ASSERT(th->nt != NULL);
975 VM_ASSERT(rb_current_ec_noinline() == th->ec);
976 VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
977
978 // add th to running threads
979 thread_sched_add_running_thread(sched, th);
980 }
981
982 // Control transfer to the current thread is now complete. The original thread
983 // cannot steal control at this point.
984 sched->runnable_hot_th = NULL;
985 sched->runnable_hot_th_waiting = 0;
986
987 // VM_ASSERT(ractor_sched_running_threads_contain_p(th->vm, th)); need locking
988 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_RESUMED, th);
989}
990
991// waiting -> ready -> running (locked)
992static void
993thread_sched_to_running_common(struct rb_thread_sched *sched, rb_thread_t *th)
994{
995 RUBY_DEBUG_LOG("th:%u dedicated:%d", rb_th_serial(th), th_has_dedicated_nt(th));
996
997 VM_ASSERT(sched->running != th);
998 VM_ASSERT(th_has_dedicated_nt(th));
999 VM_ASSERT(GET_THREAD() == th);
1000
1001 native_thread_dedicated_dec(th->vm, th->ractor, th->nt);
1002
1003 // waiting -> ready
1004 thread_sched_to_ready_common(sched, th, false, false);
1005
1006 if (sched->running == th) {
1007 thread_sched_add_running_thread(sched, th);
1008 }
1009
1010 // TODO: check SNT number
1011 thread_sched_wait_running_turn(sched, th, false);
1012}
1013
1014// waiting -> ready -> running
1015//
1016// `th` had been waiting by `thread_sched_to_waiting()`
1017// and run a dedicated task (like waitpid and so on).
1018// After the dedicated task, this function is called
1019// to join a normal thread-scheduling.
1020static void
1021thread_sched_to_running(struct rb_thread_sched *sched, rb_thread_t *th)
1022{
1023 // We are reading and writing these sched fields without lock cover, but
1024 // there are no correctness issues resulting from stale cache or delayed writeback.
1025 // When it works, this causes the next-scheduled thread to yield the sched lock
1026 // briefly so that we can grab it if we're still spinning (not descheduled yet).
1027 if (sched->runnable_hot_th == th) {
1028 sched->runnable_hot_th_waiting = 1;
1029 }
1030 thread_sched_lock(sched, th);
1031 {
1032 thread_sched_to_running_common(sched, th);
1033 }
1034 thread_sched_unlock(sched, th);
1035}
1036
1037// resume a next thread in the thread ready queue.
1038//
1039// deque next running thread from the ready thread queue and
1040// resume this thread if available.
1041//
1042// If the next therad has a dedicated native thraed, simply signal to resume.
1043// Otherwise, make the ractor ready and other nt will run the ractor and the thread.
1044static void
1045thread_sched_wakeup_next_thread(struct rb_thread_sched *sched, rb_thread_t *th, bool will_switch)
1046{
1047 ASSERT_thread_sched_locked(sched, th);
1048
1049 VM_ASSERT(sched->running == th);
1050 VM_ASSERT(sched->running->nt != NULL);
1051
1052 rb_thread_t *next_th = thread_sched_deq(sched);
1053
1054 RUBY_DEBUG_LOG("next_th:%u", rb_th_serial(next_th));
1055 VM_ASSERT(th != next_th);
1056
1057 thread_sched_set_running(sched, next_th);
1058 VM_ASSERT(next_th == sched->running);
1059 thread_sched_wakeup_running_thread(sched, next_th, will_switch);
1060
1061 if (th != next_th) {
1062 thread_sched_del_running_thread(sched, th);
1063 }
1064}
1065
1066// running -> dead (locked)
1067static void
1068thread_sched_to_dead_common(struct rb_thread_sched *sched, rb_thread_t *th)
1069{
1070 RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated);
1071
1072 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
1073
1074 // A dying coroutine thread (will_switch=true here) does NOT wake the
1075 // next thread now: it is still winding down (co_start's epilogue), and
1076 // the same Ractor must not have two threads executing at once. The
1077 // epilogue enqueues the Ractor after its last rb_ractor_t access.
1078 thread_sched_wakeup_next_thread(sched, th, !th_has_dedicated_nt(th));
1079
1080 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_EXITED, th);
1081}
1082
1083// running -> dead
1084static void
1085thread_sched_to_dead(struct rb_thread_sched *sched, rb_thread_t *th)
1086{
1087 thread_sched_lock(sched, th);
1088 {
1089 thread_sched_to_dead_common(sched, th);
1090 }
1091 thread_sched_unlock(sched, th);
1092}
1093
1094// running -> waiting (locked)
1095//
1096// This thread will run dedicated task (th->nt->dedicated++).
1097static void
1098thread_sched_to_waiting_common(struct rb_thread_sched *sched, rb_thread_t *th, bool yield_immediately)
1099{
1100 RUBY_DEBUG_LOG("th:%u DNT:%d", rb_th_serial(th), th->nt->dedicated);
1101
1102 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
1103
1104 native_thread_dedicated_inc(th->vm, th->ractor, th->nt);
1105 if (!yield_immediately) {
1106 sched->runnable_hot_th = th;
1107 sched->runnable_hot_th_waiting = 0;
1108 }
1109 thread_sched_wakeup_next_thread(sched, th, false);
1110}
1111
1112// running -> waiting
1113//
1114// This thread will run a dedicated task.
1115static void
1116thread_sched_to_waiting(struct rb_thread_sched *sched, rb_thread_t *th, bool yield_immediately)
1117{
1118 thread_sched_lock(sched, th);
1119 {
1120 thread_sched_to_waiting_common(sched, th, yield_immediately);
1121 }
1122 thread_sched_unlock(sched, th);
1123}
1124
1125// mini utility func
1126// return true if any there are any interrupts
1127static bool
1128ubf_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, rb_atomic_t *event_serial)
1129{
1130 VM_ASSERT(func != NULL);
1131
1132 retry:
1133 if (RUBY_VM_INTERRUPTED(th->ec)) {
1134 RUBY_DEBUG_LOG("interrupted:0x%x", th->ec->interrupt_flag);
1135 return true;
1136 }
1137
1138 rb_native_mutex_lock(&th->interrupt_lock);
1139 {
1140 if (!th->ec->raised_flag && RUBY_VM_INTERRUPTED(th->ec)) {
1141 rb_native_mutex_unlock(&th->interrupt_lock);
1142 goto retry;
1143 }
1144
1145 VM_ASSERT(th->unblock.func == NULL);
1146 th->unblock.func = func;
1147 th->unblock.arg = arg;
1148 if (event_serial) {
1149 rb_atomic_t prev_serial = RUBY_ATOMIC_FETCH_ADD(th->unblock.event_serial, 1);
1150 *event_serial = prev_serial+1;
1151 }
1152 }
1153 rb_native_mutex_unlock(&th->interrupt_lock);
1154
1155 return false;
1156}
1157
1158static void
1159ubf_clear(rb_thread_t *th, bool clear_serial)
1160{
1161 rb_native_mutex_lock(&th->interrupt_lock);
1162 {
1163 th->unblock.func = NULL;
1164 th->unblock.arg = NULL;
1165 if (clear_serial) {
1166 RUBY_ATOMIC_ADD(th->unblock.event_serial, 1);
1167 }
1168 }
1169 rb_native_mutex_unlock(&th->interrupt_lock);
1170}
1171
1172static void
1173ubf_waiting(void *ptr)
1174{
1175 rb_thread_t *th = (rb_thread_t *)ptr;
1176 struct rb_thread_sched *sched = TH_SCHED(th);
1177
1178 // only once. it is safe because th->interrupt_lock is already acquired.
1179 th->unblock.func = NULL;
1180 th->unblock.arg = NULL;
1181
1182 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
1183
1184 thread_sched_lock(sched, th);
1185 {
1186 if (sched->running == th) {
1187 // not sleeping yet.
1188 }
1189 else {
1190 thread_sched_to_ready_common(sched, th, true, false);
1191 }
1192 }
1193 thread_sched_unlock(sched, th);
1194}
1195
1196// running -> waiting
1197//
1198// This thread will sleep until other thread wakeup the thread.
1199static void
1200thread_sched_to_waiting_until_wakeup(struct rb_thread_sched *sched, rb_thread_t *th)
1201{
1202 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
1203
1204 RB_VM_SAVE_MACHINE_CONTEXT(th);
1205
1206
1207 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
1208
1209 thread_sched_lock(sched, th);
1210 {
1211 // NOTE: there's a lock ordering inversion here with the ubf call, but it's benign.
1212 if (ubf_set(th, ubf_waiting, (void *)th, NULL)) {
1213 RUBY_DEBUG_LOG("th:%u interrupted", rb_th_serial(th));
1214 }
1215 else {
1216 bool can_direct_transfer = !th_has_dedicated_nt(th);
1217 // NOTE: th->status is set before and after this sleep outside of this function in `sleep_forever`
1218 thread_sched_wakeup_next_thread(sched, th, can_direct_transfer);
1219 thread_sched_wait_running_turn(sched, th, can_direct_transfer);
1220 }
1221 }
1222 thread_sched_unlock(sched, th);
1223
1224 ubf_clear(th, false);
1225}
1226
1227// run another thread in the ready queue.
1228// continue to run if there are no ready threads.
1229static void
1230thread_sched_yield(struct rb_thread_sched *sched, rb_thread_t *th)
1231{
1232 RUBY_DEBUG_LOG("th:%d sched->readyq_cnt:%d", (int)th->serial, sched->readyq_cnt);
1233
1234 thread_sched_lock(sched, th);
1235 {
1236 if (!ccan_list_empty(&sched->readyq)) {
1237 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
1238 thread_sched_wakeup_next_thread(sched, th, !th_has_dedicated_nt(th));
1239 bool can_direct_transfer = !th_has_dedicated_nt(th);
1240 thread_sched_to_ready_common(sched, th, false, can_direct_transfer);
1241 thread_sched_wait_running_turn(sched, th, can_direct_transfer);
1242 th->status = THREAD_RUNNABLE;
1243 }
1244 else {
1245 VM_ASSERT(sched->readyq_cnt == 0);
1246 }
1247 }
1248 thread_sched_unlock(sched, th);
1249}
1250
1251void
1252rb_thread_sched_init(struct rb_thread_sched *sched, bool atfork)
1253{
1254 rb_native_mutex_initialize(&sched->lock_);
1255
1256#if VM_CHECK_MODE
1257 sched->lock_owner = NULL;
1258#endif
1259
1260 ccan_list_head_init(&sched->readyq);
1261 sched->readyq_cnt = 0;
1262 ccan_list_node_init(&sched->grq_node); // self-linked = not enqueued
1263
1264#if USE_MN_THREADS
1265 if (!atfork) sched->enable_mn_threads = true; // MN is enabled on Ractors
1266#endif
1267}
1268
1269static void
1270coroutine_transfer0(struct coroutine_context *transfer_from, struct coroutine_context *transfer_to, bool to_dead)
1271{
1272#ifdef RUBY_ASAN_ENABLED
1273 void **fake_stack = to_dead ? NULL : &transfer_from->fake_stack;
1274 __sanitizer_start_switch_fiber(fake_stack, transfer_to->stack_base, transfer_to->stack_size);
1275#endif
1276
1277#if defined(COROUTINE_SANITIZE_THREAD)
1278 /* Tell TSan we are switching to transfer_to's fiber before the stack
1279 * switch, so its per-thread shadow stack stays bound to the right
1280 * coroutine. */
1281 __tsan_switch_to_fiber(transfer_to->tsan_fiber, 0);
1282#endif
1283
1285 struct coroutine_context *returning_from = coroutine_transfer(transfer_from, transfer_to);
1286
1287 /* if to_dead was passed, the caller is promising that this coroutine is finished and it should
1288 * never be resumed! */
1289 VM_ASSERT(!to_dead);
1290#ifdef RUBY_ASAN_ENABLED
1291 __sanitizer_finish_switch_fiber(transfer_from->fake_stack,
1292 (const void**)&returning_from->stack_base, &returning_from->stack_size);
1293#endif
1294}
1295
1296static void
1297thread_sched_switch0(struct coroutine_context *current_cont, rb_thread_t *next_th, struct rb_native_thread *nt, bool to_dead)
1298{
1299 VM_ASSERT(!nt->dedicated);
1300 VM_ASSERT(next_th->nt == NULL);
1301
1302 RUBY_DEBUG_LOG("next_th:%u", rb_th_serial(next_th));
1303
1304 // this direct transfer serves next_th without a dequeue; cancel its
1305 // Ractor's outstanding grq entry (no-op when nothing is enqueued)
1306 ractor_sched_cancel_enq(next_th->vm, TH_SCHED(next_th));
1307
1308 ruby_thread_set_native(next_th);
1309 native_thread_assign(nt, next_th);
1310
1311 coroutine_transfer0(current_cont, next_th->sched.context, to_dead);
1312}
1313
1314static void
1315thread_sched_switch(rb_thread_t *cth, rb_thread_t *next_th)
1316{
1317 struct rb_native_thread *nt = cth->nt;
1318 native_thread_assign(NULL, cth);
1319 RUBY_DEBUG_LOG("th:%u->%u on nt:%d", rb_th_serial(cth), rb_th_serial(next_th), nt->serial);
1320 thread_sched_switch0(cth->sched.context, next_th, nt, cth->status == THREAD_KILLED);
1321}
1322
1323#if VM_CHECK_MODE > 0
1325static unsigned int
1326grq_size(rb_vm_t *vm, rb_ractor_t *cr)
1327{
1328 ASSERT_ractor_sched_locked(vm, cr);
1329
1330 rb_ractor_t *r, *prev_r = NULL;
1331 unsigned int i = 0;
1332
1333 ccan_list_for_each(&vm->ractor.sched.grq, r, threads.sched.grq_node) {
1334 i++;
1335
1336 VM_ASSERT(r != prev_r);
1337 prev_r = r;
1338 }
1339 return i;
1340}
1341#endif
1342
1343// ruby_vm_destruct: wait until no native thread is between a coroutine
1344// epilogue and its reclaim -- past that point the reclaim frees through the
1345// (about to be destroyed) objspace and reads the (about to be unset) VM.
1346// Runs without the VM lock, which the epilogue needs to progress.
1347void
1348rb_thread_sched_wait_winding(rb_vm_t *vm)
1349{
1350 while (RUBY_ATOMIC_LOAD(vm->ractor.sched.winding_cnt) > 0) {
1351 native_thread_yield();
1352 }
1353}
1354
1355// A direct service of a runnable thread (direct transfer or the hot-thread
1356// steal) bypasses the grq; cancel the Ractor's outstanding entry so that
1357// "enqueued <=> runnable and unserved" keeps holding. The caller holds the
1358// per-Ractor sched lock, so no concurrent enqueue can relink the node: a
1359// self-linked read needs no lock (the common case -- direct switches whose
1360// transition never enqueued). A linked read can race only with a dequeue,
1361// hence the recheck under the grq lock.
1362static void
1363ractor_sched_cancel_enq(rb_vm_t *vm, struct rb_thread_sched *sched)
1364{
1365 if (sched->grq_node.next != &sched->grq_node) {
1366 ractor_sched_lock(vm, NULL);
1367 {
1368 if (sched->grq_node.next != &sched->grq_node) {
1369 ccan_list_del_init(&sched->grq_node);
1370 VM_ASSERT(vm->ractor.sched.grq_cnt > 0);
1371 vm->ractor.sched.grq_cnt--;
1372 }
1373 }
1374 ractor_sched_unlock(vm, NULL);
1375 }
1376}
1377
1378static void
1379ractor_sched_enq(rb_vm_t *vm, rb_ractor_t *r)
1380{
1381 struct rb_thread_sched *sched = &r->threads.sched;
1382 rb_ractor_t *cr = NULL; // timer thread can call this function
1383
1384 VM_ASSERT(sched->running != NULL);
1385 VM_ASSERT(sched->running->nt == NULL);
1386
1387 ractor_sched_lock(vm, cr);
1388 {
1389 // Precondition: not already enqueued (the grq_node is self-linked).
1390 // This holds because every service of a runnable-but-unserved thread
1391 // either dequeues the entry (the nt scheduling loop) or cancels it
1392 // (direct transfers / the hot-thread steal; see
1393 // ractor_sched_cancel_enq) -- re-adding a linked node would corrupt
1394 // the queue, so check unconditionally (a CHECK-mode-only assert
1395 // would miss it: the race needs timing that CHECK builds perturb).
1396 if (sched->grq_node.next != &sched->grq_node) {
1397 rb_bug("ractor_sched_enq: already enqueued");
1398 }
1399 ccan_list_add_tail(&vm->ractor.sched.grq, &sched->grq_node);
1400 vm->ractor.sched.grq_cnt++;
1401 VM_ASSERT(grq_size(vm, cr) == vm->ractor.sched.grq_cnt);
1402
1403 RUBY_DEBUG_LOG("r:%u th:%u grq_cnt:%u", rb_ractor_id(r), rb_th_serial(sched->running), vm->ractor.sched.grq_cnt);
1404
1405 rb_native_cond_signal(&vm->ractor.sched.cond);
1406
1407 // The signal reaches a parked snt, and a running one revisits the
1408 // queue in ractor_sched_deq before it can wait (same lock as here).
1409 // With every snt dedicated or retired, only the timer thread's
1410 // timeout branch can serve the entry or widen the pool: wake it
1411 // (a no-op unless it sleeps untimed).
1412 if (vm->ractor.sched.snt_cnt == 0) {
1413 timer_thread_wakeup_locked(vm);
1414 }
1415
1416 // ractor_sched_dump(vm);
1417 }
1418 ractor_sched_unlock(vm, cr);
1419}
1420
1421
1422#ifndef MINIMUM_SNT
1423// make at least MINIMUM_SNT snts for debug.
1424#define MINIMUM_SNT 0
1425#endif
1426
1427/* A shared thread woken with nothing to run is one whose turn another thread
1428 * took first. After this many in a row it gives itself back: the queue keeps
1429 * running dry, so the pool is wider than the work. 0 retires on the first one
1430 * and is too eager to be useful; a negative value keeps every thread. */
1431#ifndef SNT_IDLE_RETIRE
1432#define SNT_IDLE_RETIRE 3
1433#endif
1434
1435/* Never give the last shared thread back. With none left an enqueue has nobody
1436 * to signal, and the only code that makes one runs on the timer thread's
1437 * timeout branch, which is reached only once it has seen a backlog. */
1438#define SNT_KEEP_MINIMUM (MINIMUM_SNT > 1 ? MINIMUM_SNT : 1)
1439
1440static rb_ractor_t *
1441ractor_sched_deq(rb_vm_t *vm, rb_ractor_t *cr)
1442{
1443 rb_ractor_t *r;
1444 int idle_streak = 0; // consecutive pops that found the queue empty
1445
1446 ractor_sched_lock(vm, cr);
1447 {
1448 RUBY_DEBUG_LOG("empty? %d", ccan_list_empty(&vm->ractor.sched.grq));
1449 // ractor_sched_dump(vm);
1450
1451 VM_ASSERT(rb_current_execution_context(false) == NULL);
1452 VM_ASSERT(grq_size(vm, cr) == vm->ractor.sched.grq_cnt);
1453
1454 while ((r = ccan_list_pop(&vm->ractor.sched.grq, rb_ractor_t, threads.sched.grq_node)) == NULL) {
1455 RUBY_DEBUG_LOG("wait grq_cnt:%d", (int)vm->ractor.sched.grq_cnt);
1456
1457 if (SNT_IDLE_RETIRE >= 0 && ++idle_streak > SNT_IDLE_RETIRE &&
1458 (int)vm->ractor.sched.snt_cnt > SNT_KEEP_MINIMUM) {
1459 vm->ractor.sched.snt_cnt--;
1460 RUBY_DEBUG_LOG("retire, snt_cnt:%d", (int)vm->ractor.sched.snt_cnt);
1461 break; // returning NULL ends this nt; see the caller
1462 }
1463
1464 ractor_sched_set_unlocked(vm, cr);
1465 rb_native_cond_wait(&vm->ractor.sched.cond, &vm->ractor.sched.lock);
1466 ractor_sched_set_locked(vm, cr);
1467
1468 RUBY_DEBUG_LOG("wakeup grq_cnt:%d", (int)vm->ractor.sched.grq_cnt);
1469 }
1470
1471 VM_ASSERT(rb_current_execution_context(false) == NULL);
1472
1473 if (r) {
1474 ccan_list_node_init(&r->threads.sched.grq_node); // back to self-linked
1475 VM_ASSERT(vm->ractor.sched.grq_cnt > 0);
1476 vm->ractor.sched.grq_cnt--;
1477 RUBY_DEBUG_LOG("r:%d grq_cnt:%u", (int)rb_ractor_id(r), vm->ractor.sched.grq_cnt);
1478 }
1479 else {
1480 // the retire branch is the only way out of the loop without a ractor
1481 VM_ASSERT(idle_streak > SNT_IDLE_RETIRE);
1482 }
1483 }
1484 ractor_sched_unlock(vm, cr);
1485
1486 return r;
1487}
1488
1489void rb_ractor_lock_self(rb_ractor_t *r);
1490void rb_ractor_unlock_self(rb_ractor_t *r);
1491
1492// The current thread for a ractor is put to "sleep" (descheduled in the STOPPED_FOREVER state) waiting for
1493// a ractor action to wake it up.
1494void
1495rb_ractor_sched_wait(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf, void *ubf_arg)
1496{
1497 // ractor lock of cr is acquired
1498
1499 RUBY_DEBUG_LOG("start%s", "");
1500
1501 rb_thread_t * volatile th = rb_ec_thread_ptr(ec);
1502 struct rb_thread_sched *sched = TH_SCHED(th);
1503 struct ractor_waiter *waiter = (struct ractor_waiter*)ubf_arg;
1504
1505 if (ubf_set(th, ubf, ubf_arg, &waiter->event_serial)) {
1506 // interrupted
1507 return;
1508 }
1509
1510 thread_sched_lock(sched, th);
1511 rb_ractor_unlock_self(cr);
1512 {
1513 // setup sleep
1514 bool can_direct_transfer = !th_has_dedicated_nt(th);
1515 RB_VM_SAVE_MACHINE_CONTEXT(th);
1516 th->status = THREAD_STOPPED_FOREVER;
1517 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_SUSPENDED, th);
1518 thread_sched_wakeup_next_thread(sched, th, can_direct_transfer);
1519 // sleep
1520 thread_sched_wait_running_turn(sched, th, can_direct_transfer);
1521 th->status = THREAD_RUNNABLE;
1522 }
1523 thread_sched_unlock(sched, th);
1524 rb_ractor_lock_self(cr);
1525
1526 ubf_clear(th, true);
1527
1528 RUBY_DEBUG_LOG("end%s", "");
1529}
1530
1531void
1532rb_ractor_sched_wakeup(rb_ractor_t *r, rb_thread_t *r_th)
1533{
1534 // ractor lock of r acquired
1535 struct rb_thread_sched *sched = TH_SCHED(r_th);
1536
1537 RUBY_DEBUG_LOG("r:%u th:%d", (unsigned int)rb_ractor_id(r), r_th->serial);
1538
1539 thread_sched_lock(sched, r_th);
1540 {
1541 if (r_th->status == THREAD_STOPPED_FOREVER) {
1542 RUBY_ATOMIC_ADD(r_th->unblock.event_serial, 1);
1543 thread_sched_to_ready_common(sched, r_th, true, false);
1544 }
1545 }
1546 thread_sched_unlock(sched, r_th);
1547}
1548
1549static bool
1550ractor_sched_barrier_completed_p(rb_vm_t *vm)
1551{
1552 RUBY_DEBUG_LOG("run:%u wait:%u", vm->ractor.sched.running_cnt, vm->ractor.sched.barrier_waiting_cnt);
1553 VM_ASSERT(vm->ractor.sched.running_cnt - 1 >= vm->ractor.sched.barrier_waiting_cnt);
1554
1555 return (vm->ractor.sched.running_cnt - vm->ractor.sched.barrier_waiting_cnt) == 1;
1556}
1557
1558void
1559rb_ractor_sched_barrier_start(rb_vm_t *vm, rb_ractor_t *cr)
1560{
1561 VM_ASSERT(cr == GET_RACTOR());
1562 VM_ASSERT(vm->ractor.sync.lock_owner == cr); // VM is locked
1563 VM_ASSERT(!vm->ractor.sched.barrier_waiting);
1564 VM_ASSERT(vm->ractor.sched.barrier_waiting_cnt == 0);
1565 VM_ASSERT(vm->ractor.sched.barrier_ractor == NULL);
1566 VM_ASSERT(vm->ractor.sched.barrier_lock_rec == 0);
1567
1568 RUBY_DEBUG_LOG("start serial:%u", vm->ractor.sched.barrier_serial);
1569
1570 unsigned int lock_rec;
1571
1572 ractor_sched_lock(vm, cr);
1573 {
1574 vm->ractor.sched.barrier_waiting = true;
1575 vm->ractor.sched.barrier_ractor = cr;
1576 vm->ractor.sched.barrier_lock_rec = vm->ractor.sync.lock_rec;
1577
1578 // release VM lock
1579 lock_rec = vm->ractor.sync.lock_rec;
1580 vm->ractor.sync.lock_rec = 0;
1581 vm->ractor.sync.lock_owner = NULL;
1582 rb_native_mutex_unlock(&vm->ractor.sync.lock);
1583
1584 // interrupts all running threads
1585 rb_thread_t *ith;
1586 ccan_list_for_each(&vm->ractor.sched.running_threads, ith, sched.node.running_threads) {
1587 if (ith->ractor != cr) {
1588 RUBY_DEBUG_LOG("barrier request to th:%u", rb_th_serial(ith));
1589 RUBY_VM_SET_VM_BARRIER_INTERRUPT(ith->ec);
1590 }
1591 }
1592
1593 // wait for other ractors
1594 while (!ractor_sched_barrier_completed_p(vm)) {
1595 ractor_sched_set_unlocked(vm, cr);
1596 rb_native_cond_wait(&vm->ractor.sched.barrier_complete_cond, &vm->ractor.sched.lock);
1597 ractor_sched_set_locked(vm, cr);
1598 }
1599
1600 RUBY_DEBUG_LOG("completed seirial:%u", vm->ractor.sched.barrier_serial);
1601
1602 // no other ractors are there
1603 vm->ractor.sched.barrier_serial++;
1604 vm->ractor.sched.barrier_waiting_cnt = 0;
1605 rb_native_cond_broadcast(&vm->ractor.sched.barrier_release_cond);
1606
1607 // acquire VM lock
1608 rb_native_mutex_lock(&vm->ractor.sync.lock);
1609 vm->ractor.sync.lock_rec = lock_rec;
1610 vm->ractor.sync.lock_owner = cr;
1611 }
1612
1613 // do not release ractor_sched_lock and there is no newly added (resumed) thread
1614 // thread_sched_setup_running_threads
1615}
1616
1617// called from vm_lock_leave if the vm_lock used for barrierred
1618void
1619rb_ractor_sched_barrier_end(rb_vm_t *vm, rb_ractor_t *cr)
1620{
1621 RUBY_DEBUG_LOG("serial:%u", (unsigned int)vm->ractor.sched.barrier_serial - 1);
1622 VM_ASSERT(vm->ractor.sched.barrier_waiting);
1623 VM_ASSERT(vm->ractor.sched.barrier_ractor);
1624 VM_ASSERT(vm->ractor.sched.barrier_lock_rec > 0);
1625
1626 vm->ractor.sched.barrier_waiting = false;
1627 vm->ractor.sched.barrier_ractor = NULL;
1628 vm->ractor.sched.barrier_lock_rec = 0;
1629 ractor_sched_unlock(vm, cr);
1630}
1631
1632static void
1633ractor_sched_barrier_join_signal_locked(rb_vm_t *vm)
1634{
1635 if (ractor_sched_barrier_completed_p(vm)) {
1636 rb_native_cond_signal(&vm->ractor.sched.barrier_complete_cond);
1637 }
1638}
1639
1640static void
1641ractor_sched_barrier_join_wait_locked(rb_vm_t *vm, rb_thread_t *th)
1642{
1643 VM_ASSERT(vm->ractor.sched.barrier_waiting);
1644
1645 unsigned int barrier_serial = vm->ractor.sched.barrier_serial;
1646
1647 while (vm->ractor.sched.barrier_serial == barrier_serial) {
1648 RUBY_DEBUG_LOG("sleep serial:%u", barrier_serial);
1649 RB_VM_SAVE_MACHINE_CONTEXT(th);
1650
1651 rb_ractor_t *cr = th->ractor;
1652 ractor_sched_set_unlocked(vm, cr);
1653 rb_native_cond_wait(&vm->ractor.sched.barrier_release_cond, &vm->ractor.sched.lock);
1654 ractor_sched_set_locked(vm, cr);
1655
1656 RUBY_DEBUG_LOG("wakeup serial:%u", barrier_serial);
1657 }
1658}
1659
1660void
1661rb_ractor_sched_barrier_join(rb_vm_t *vm, rb_ractor_t *cr)
1662{
1663 VM_ASSERT(cr->threads.sched.running != NULL); // running ractor
1664 VM_ASSERT(cr == GET_RACTOR());
1665 VM_ASSERT(vm->ractor.sync.lock_owner == NULL); // VM is locked, but owner == NULL
1666 VM_ASSERT(vm->ractor.sched.barrier_waiting); // VM needs barrier sync
1667
1668#if USE_RUBY_DEBUG_LOG || VM_CHECK_MODE > 0
1669 unsigned int barrier_serial = vm->ractor.sched.barrier_serial;
1670#endif
1671
1672 RUBY_DEBUG_LOG("join");
1673
1674 rb_native_mutex_unlock(&vm->ractor.sync.lock);
1675 {
1676 VM_ASSERT(vm->ractor.sched.barrier_waiting); // VM needs barrier sync
1677 VM_ASSERT(vm->ractor.sched.barrier_serial == barrier_serial);
1678
1679 ractor_sched_lock(vm, cr);
1680 {
1681 // running_cnt
1682 /* Every joiner is a member of the running set: a dying thread now
1683 * leaves the living set before handing over its scheduler slot. */
1684 VM_ASSERT(ractor_sched_running_threads_contain_p(vm, GET_THREAD()));
1685 vm->ractor.sched.barrier_waiting_cnt++;
1686 RUBY_DEBUG_LOG("waiting_cnt:%u serial:%u", vm->ractor.sched.barrier_waiting_cnt, barrier_serial);
1687
1688 ractor_sched_barrier_join_signal_locked(vm);
1689 ractor_sched_barrier_join_wait_locked(vm, cr->threads.sched.running);
1690 }
1691 ractor_sched_unlock(vm, cr);
1692 }
1693
1694 rb_native_mutex_lock(&vm->ractor.sync.lock);
1695 // VM locked here
1696}
1697
1698#if 0
1699// TODO
1700
1701static void clear_thread_cache_altstack(void);
1702
1703static void
1704rb_thread_sched_destroy(struct rb_thread_sched *sched)
1705{
1706 /*
1707 * only called once at VM shutdown (not atfork), another thread
1708 * may still grab vm->gvl.lock when calling gvl_release at
1709 * the end of thread_start_func_2
1710 */
1711 if (0) {
1712 rb_native_mutex_destroy(&sched->lock);
1713 }
1714 clear_thread_cache_altstack();
1715}
1716#endif
1717
1718#ifdef RB_THREAD_T_HAS_NATIVE_ID
1719static int
1720get_native_thread_id(void)
1721{
1722#ifdef __linux__
1723 return (int)syscall(SYS_gettid);
1724#elif defined(__FreeBSD__)
1725 return pthread_getthreadid_np();
1726#endif
1727}
1728#endif
1729
1730#if defined(HAVE_WORKING_FORK)
1731static void rb_internal_thread_event_hooks_rw_lock_atfork(void);
1732
1733static void
1734thread_sched_atfork(struct rb_thread_sched *sched)
1735{
1736 current_fork_gen++;
1737 rb_thread_sched_init(sched, true);
1738 rb_thread_t *th = GET_THREAD();
1739 rb_vm_t *vm = GET_VM();
1740
1741 if (th_has_dedicated_nt(th)) {
1742 vm->ractor.sched.snt_cnt = 0;
1743 vm->ractor.sched.dnt_cnt = 1;
1744 }
1745 else {
1746 vm->ractor.sched.snt_cnt = 1;
1747 vm->ractor.sched.dnt_cnt = 0;
1748 }
1749 vm->ractor.sched.running_cnt = 0;
1750
1751 rb_native_mutex_initialize(&vm->ractor.sched.lock);
1752#if VM_CHECK_MODE > 0
1753 vm->ractor.sched.lock_owner = NULL;
1754 vm->ractor.sched.locked = false;
1755#endif
1756
1757 // rb_native_cond_destroy(&vm->ractor.sched.cond);
1758 rb_native_cond_initialize(&vm->ractor.sched.cond);
1759 rb_native_cond_initialize(&vm->ractor.sched.barrier_complete_cond);
1760 rb_native_cond_initialize(&vm->ractor.sched.barrier_release_cond);
1761
1762 ccan_list_head_init(&vm->ractor.sched.grq);
1763 vm->ractor.sched.grq_cnt = 0; // the list was just emptied; reset the count with it
1764 // A fork during a VM barrier leaves the child with barrier state that can
1765 // never complete (the other ractors are gone); reset it like the rest.
1766 vm->ractor.sched.barrier_waiting = false;
1767 vm->ractor.sched.barrier_waiting_cnt = 0;
1768 vm->ractor.sched.barrier_ractor = NULL;
1769 vm->ractor.sched.barrier_lock_rec = 0;
1770 // Threads that were winding down in the parent do not exist in the child;
1771 // without this reset the child's ruby_vm_destruct would wait for their
1772 // reclaim (which never comes) forever.
1773 vm->ractor.sched.winding_cnt = 0;
1774 ccan_list_head_init(&vm->ractor.sched.timeslice_threads);
1775 ccan_list_head_init(&vm->ractor.sched.running_threads);
1776
1777#if USE_MN_THREADS
1778 nt_machine_stack_atfork();
1779#endif
1780 rb_internal_thread_event_hooks_rw_lock_atfork();
1781
1782 VM_ASSERT(sched->is_running);
1783 sched->is_running_timeslice = false;
1784
1785 if (sched->running != th) {
1786 thread_sched_to_running(sched, th);
1787 }
1788 else {
1789 thread_sched_setup_running_threads(sched, th->ractor, vm, th, NULL, NULL);
1790 }
1791
1792#ifdef RB_THREAD_T_HAS_NATIVE_ID
1793 if (th->nt) {
1794 th->nt->tid = get_native_thread_id();
1795 }
1796#endif
1797}
1798
1799#endif
1800
1801#ifdef RB_THREAD_LOCAL_SPECIFIER
1802static RB_THREAD_LOCAL_SPECIFIER rb_thread_t *ruby_native_thread;
1803#else
1804static pthread_key_t ruby_native_thread_key;
1805#endif
1806
1807static void
1808null_func(int i)
1809{
1810 /* null */
1811 // This function can be called from signal handler
1812 // RUBY_DEBUG_LOG("i:%d", i);
1813}
1814
1816ruby_thread_from_native(void)
1817{
1818#ifdef RB_THREAD_LOCAL_SPECIFIER
1819 return ruby_native_thread;
1820#else
1821 return pthread_getspecific(ruby_native_thread_key);
1822#endif
1823}
1824
1825int
1826ruby_thread_set_native(rb_thread_t *th)
1827{
1828 if (th) {
1829#ifdef USE_UBF_LIST
1830 ccan_list_node_init(&th->sched.node.ubf);
1831#endif
1832 }
1833
1834 // setup TLS
1835
1836 if (th && th->ec) {
1837 rb_ractor_set_current_ec(th->ractor, th->ec);
1838 }
1839#ifdef RB_THREAD_LOCAL_SPECIFIER
1840 ruby_native_thread = th;
1841 return 1;
1842#else
1843 return pthread_setspecific(ruby_native_thread_key, th) == 0;
1844#endif
1845}
1846
1847static void native_thread_setup(struct rb_native_thread *nt);
1848static void native_thread_setup_on_thread(struct rb_native_thread *nt);
1849
1850// Internal cache of page size:
1851static size_t RB_THREAD_PAGE_SIZE;
1852
1853void
1854Init_native_thread(rb_thread_t *main_th)
1855{
1856 // Get the system page size for later use in stack allocation and stack overflow checks:
1857 RB_THREAD_PAGE_SIZE = sysconf(_SC_PAGESIZE);
1858
1859#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK)
1860 if (condattr_monotonic) {
1861 int r = pthread_condattr_init(condattr_monotonic);
1862 if (r == 0) {
1863 r = pthread_condattr_setclock(condattr_monotonic, CLOCK_MONOTONIC);
1864 }
1865 if (r) condattr_monotonic = NULL;
1866 }
1867#endif
1868
1869#ifndef RB_THREAD_LOCAL_SPECIFIER
1870 if (pthread_key_create(&ruby_native_thread_key, 0) == EAGAIN) {
1871 rb_bug("pthread_key_create failed (ruby_native_thread_key)");
1872 }
1873 if (pthread_key_create(&ruby_current_ec_key, 0) == EAGAIN) {
1874 rb_bug("pthread_key_create failed (ruby_current_ec_key)");
1875 }
1876#endif
1877 ruby_posix_signal(SIGVTALRM, null_func);
1878
1879 // setup vm
1880 rb_vm_t *vm = main_th->vm;
1881 rb_native_mutex_initialize(&vm->ractor.sched.lock);
1882 rb_native_cond_initialize(&vm->ractor.sched.cond);
1883 rb_native_cond_initialize(&vm->ractor.sched.barrier_complete_cond);
1884 rb_native_cond_initialize(&vm->ractor.sched.barrier_release_cond);
1885
1886 ccan_list_head_init(&vm->ractor.sched.grq);
1887 ccan_list_head_init(&vm->ractor.sched.timeslice_threads);
1888 ccan_list_head_init(&vm->ractor.sched.running_threads);
1889
1890 // setup main thread
1891 main_th->nt->thread_id = pthread_self();
1892 main_th->nt->serial = 1;
1893#ifdef RUBY_NT_SERIAL
1894 ruby_nt_serial = 1;
1895#endif
1896 ruby_thread_set_native(main_th);
1897 native_thread_setup(main_th->nt);
1898 native_thread_setup_on_thread(main_th->nt);
1899
1900 TH_SCHED(main_th)->running = main_th;
1901 main_th->has_dedicated_nt = 1;
1902
1903 thread_sched_setup_running_threads(TH_SCHED(main_th), main_th->ractor, vm, main_th, NULL, NULL);
1904
1905 // setup main NT
1906 main_th->nt->dedicated = 1;
1907 main_th->nt->vm = vm;
1908
1909 // setup mn
1910 vm->ractor.sched.dnt_cnt = 1;
1911}
1912
1913extern int ruby_mn_threads_enabled;
1914
1915void
1916ruby_mn_threads_params(void)
1917{
1918 rb_vm_t *vm = GET_VM();
1919 rb_ractor_t *main_ractor = GET_RACTOR();
1920
1921 const char *mn_threads_cstr = getenv("RUBY_MN_THREADS");
1922 bool enable_mn_threads = false;
1923
1924 if (USE_MN_THREADS && mn_threads_cstr && (enable_mn_threads = atoi(mn_threads_cstr) > 0)) {
1925 // enabled
1926 ruby_mn_threads_enabled = 1;
1927 }
1928 main_ractor->threads.sched.enable_mn_threads = enable_mn_threads;
1929
1930 const char *max_cpu_cstr = getenv("RUBY_MAX_CPU");
1931#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_ONLN)
1932 long nprocessors = sysconf(_SC_NPROCESSORS_ONLN);
1933 const int default_max_cpu = (nprocessors > 0) ? (int)nprocessors : 8;
1934#else
1935 const int default_max_cpu = 8;
1936#endif
1937 int max_cpu = default_max_cpu;
1938
1939 if (USE_MN_THREADS && max_cpu_cstr) {
1940 int given_max_cpu = atoi(max_cpu_cstr);
1941 if (given_max_cpu > 0) {
1942 max_cpu = given_max_cpu;
1943 }
1944 }
1945
1946 vm->ractor.sched.max_cpu = max_cpu;
1947}
1948
1949static void
1950native_thread_dedicated_inc(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt)
1951{
1952 RUBY_DEBUG_LOG("nt:%d %d->%d", nt->serial, nt->dedicated, nt->dedicated + 1);
1953
1954 if (nt->dedicated == 0) {
1955 ractor_sched_lock(vm, cr);
1956 {
1957 vm->ractor.sched.snt_cnt--;
1958 vm->ractor.sched.dnt_cnt++;
1959
1960 // This may have dedicated the last snt away from a pending
1961 // entry whose enqueue saw snt_cnt > 0 (see ractor_sched_enq).
1962 if (vm->ractor.sched.snt_cnt == 0 && vm->ractor.sched.grq_cnt > 0) {
1963 timer_thread_wakeup_locked(vm);
1964 }
1965 }
1966 ractor_sched_unlock(vm, cr);
1967 }
1968
1969 nt->dedicated++;
1970}
1971
1972static void
1973native_thread_dedicated_dec(rb_vm_t *vm, rb_ractor_t *cr, struct rb_native_thread *nt)
1974{
1975 RUBY_DEBUG_LOG("nt:%d %d->%d", nt->serial, nt->dedicated, nt->dedicated - 1);
1976 VM_ASSERT(nt->dedicated > 0);
1977 nt->dedicated--;
1978
1979 if (nt->dedicated == 0) {
1980 ractor_sched_lock(vm, cr);
1981 {
1982 /* max_cpu bounds the shared threads and this is where one rejoins
1983 * them, so this is where the cap has to hold. A thread with no room
1984 * to come back to belongs to neither count until it ends. */
1985 if (vm->ractor.sched.snt_cnt < vm->ractor.sched.max_cpu ||
1986 (int)vm->ractor.sched.snt_cnt <= MINIMUM_SNT) {
1987 vm->ractor.sched.snt_cnt++;
1988 }
1989 else {
1990 nt->retiring = true;
1991 }
1992 vm->ractor.sched.dnt_cnt--;
1993 }
1994 ractor_sched_unlock(vm, cr);
1995 }
1996}
1997
1998static void
1999native_thread_assign(struct rb_native_thread *nt, rb_thread_t *th)
2000{
2001#if USE_RUBY_DEBUG_LOG
2002 if (nt) {
2003 if (th->nt) {
2004 RUBY_DEBUG_LOG("th:%d nt:%d->%d", (int)th->serial, (int)th->nt->serial, (int)nt->serial);
2005 }
2006 else {
2007 RUBY_DEBUG_LOG("th:%d nt:NULL->%d", (int)th->serial, (int)nt->serial);
2008 }
2009 }
2010 else {
2011 if (th->nt) {
2012 RUBY_DEBUG_LOG("th:%d nt:%d->NULL", (int)th->serial, (int)th->nt->serial);
2013 }
2014 else {
2015 RUBY_DEBUG_LOG("th:%d nt:NULL->NULL", (int)th->serial);
2016 }
2017 }
2018#endif
2019
2020 th->nt = nt;
2021}
2022
2023static void
2024native_thread_destroy_atfork(struct rb_native_thread *nt)
2025{
2026 if (nt) {
2027 /* We can't call rb_native_cond_destroy here because according to the
2028 * specs of pthread_cond_destroy:
2029 *
2030 * Attempting to destroy a condition variable upon which other threads
2031 * are currently blocked results in undefined behavior.
2032 *
2033 * Specifically, glibc's pthread_cond_destroy waits on all the other
2034 * listeners. Since after forking all the threads are dead, the condition
2035 * variable's listeners will never wake up, so it will hang forever.
2036 */
2037
2038 RB_ALTSTACK_FREE(nt->altstack);
2039 SIZED_FREE(nt->nt_context);
2040 SIZED_FREE(nt);
2041 }
2042}
2043
2044static void
2045native_thread_destroy(struct rb_native_thread *nt)
2046{
2047 if (nt) {
2048 rb_native_cond_destroy(&nt->cond.readyq);
2049
2050 if (&nt->cond.readyq != &nt->cond.intr) {
2051 rb_native_cond_destroy(&nt->cond.intr);
2052 }
2053
2054 native_thread_destroy_atfork(nt);
2055 }
2056}
2057
2058// A retiring snt frees its own rb_native_thread as it exits. Disarm the
2059// altstack registration first: a signal between the free and the thread's
2060// end must not run on the freed block.
2061static void
2062native_thread_destroy_self(struct rb_native_thread *nt)
2063{
2064#ifdef USE_SIGALTSTACK
2065 stack_t disable = {0};
2066 disable.ss_flags = SS_DISABLE;
2067 sigaltstack(&disable, NULL);
2068#endif
2069 native_thread_destroy(nt);
2070}
2071
2072#if defined HAVE_PTHREAD_GETATTR_NP || defined HAVE_PTHREAD_ATTR_GET_NP
2073#define STACKADDR_AVAILABLE 1
2074#elif defined HAVE_PTHREAD_GET_STACKADDR_NP && defined HAVE_PTHREAD_GET_STACKSIZE_NP
2075#define STACKADDR_AVAILABLE 1
2076#undef MAINSTACKADDR_AVAILABLE
2077#define MAINSTACKADDR_AVAILABLE 1
2078void *pthread_get_stackaddr_np(pthread_t);
2079size_t pthread_get_stacksize_np(pthread_t);
2080#elif defined HAVE_THR_STKSEGMENT || defined HAVE_PTHREAD_STACKSEG_NP
2081#define STACKADDR_AVAILABLE 1
2082#elif defined HAVE_PTHREAD_GETTHRDS_NP
2083#define STACKADDR_AVAILABLE 1
2084#elif defined __HAIKU__
2085#define STACKADDR_AVAILABLE 1
2086#endif
2087
2088#ifndef MAINSTACKADDR_AVAILABLE
2089# ifdef STACKADDR_AVAILABLE
2090# define MAINSTACKADDR_AVAILABLE 1
2091# else
2092# define MAINSTACKADDR_AVAILABLE 0
2093# endif
2094#endif
2095#if MAINSTACKADDR_AVAILABLE && !defined(get_main_stack)
2096# define get_main_stack(addr, size) get_stack(addr, size)
2097#endif
2098
2099#ifdef STACKADDR_AVAILABLE
2100/*
2101 * Get the initial address and size of current thread's stack
2102 */
2103static int
2104get_stack(void **addr, size_t *size)
2105{
2106#define CHECK_ERR(expr) \
2107 {int err = (expr); if (err) return err;}
2108#ifdef HAVE_PTHREAD_GETATTR_NP /* Linux */
2109 pthread_attr_t attr;
2110 size_t guard = 0;
2111 STACK_GROW_DIR_DETECTION;
2112 CHECK_ERR(pthread_getattr_np(pthread_self(), &attr));
2113# ifdef HAVE_PTHREAD_ATTR_GETSTACK
2114 CHECK_ERR(pthread_attr_getstack(&attr, addr, size));
2115 STACK_DIR_UPPER((void)0, (void)(*addr = (char *)*addr + *size));
2116# else
2117 CHECK_ERR(pthread_attr_getstackaddr(&attr, addr));
2118 CHECK_ERR(pthread_attr_getstacksize(&attr, size));
2119# endif
2120# ifdef HAVE_PTHREAD_ATTR_GETGUARDSIZE
2121 CHECK_ERR(pthread_attr_getguardsize(&attr, &guard));
2122# else
2123 guard = RB_THREAD_PAGE_SIZE;
2124# endif
2125 *size -= guard;
2126 pthread_attr_destroy(&attr);
2127#elif defined HAVE_PTHREAD_ATTR_GET_NP /* FreeBSD, DragonFly BSD, NetBSD */
2128 pthread_attr_t attr;
2129 CHECK_ERR(pthread_attr_init(&attr));
2130 CHECK_ERR(pthread_attr_get_np(pthread_self(), &attr));
2131# ifdef HAVE_PTHREAD_ATTR_GETSTACK
2132 CHECK_ERR(pthread_attr_getstack(&attr, addr, size));
2133# else
2134 CHECK_ERR(pthread_attr_getstackaddr(&attr, addr));
2135 CHECK_ERR(pthread_attr_getstacksize(&attr, size));
2136# endif
2137 STACK_DIR_UPPER((void)0, (void)(*addr = (char *)*addr + *size));
2138 pthread_attr_destroy(&attr);
2139#elif (defined HAVE_PTHREAD_GET_STACKADDR_NP && defined HAVE_PTHREAD_GET_STACKSIZE_NP) /* MacOS X */
2140 pthread_t th = pthread_self();
2141 *addr = pthread_get_stackaddr_np(th);
2142 *size = pthread_get_stacksize_np(th);
2143#elif defined HAVE_THR_STKSEGMENT || defined HAVE_PTHREAD_STACKSEG_NP
2144 stack_t stk;
2145# if defined HAVE_THR_STKSEGMENT /* Solaris */
2146 CHECK_ERR(thr_stksegment(&stk));
2147# else /* OpenBSD */
2148 CHECK_ERR(pthread_stackseg_np(pthread_self(), &stk));
2149# endif
2150 *addr = stk.ss_sp;
2151 *size = stk.ss_size;
2152#elif defined HAVE_PTHREAD_GETTHRDS_NP /* AIX */
2153 pthread_t th = pthread_self();
2154 struct __pthrdsinfo thinfo;
2155 char reg[256];
2156 int regsiz=sizeof(reg);
2157 CHECK_ERR(pthread_getthrds_np(&th, PTHRDSINFO_QUERY_ALL,
2158 &thinfo, sizeof(thinfo),
2159 &reg, &regsiz));
2160 *addr = thinfo.__pi_stackaddr;
2161 /* Must not use thinfo.__pi_stacksize for size.
2162 It is around 3KB smaller than the correct size
2163 calculated by thinfo.__pi_stackend - thinfo.__pi_stackaddr. */
2164 *size = thinfo.__pi_stackend - thinfo.__pi_stackaddr;
2165 STACK_DIR_UPPER((void)0, (void)(*addr = (char *)*addr + *size));
2166#elif defined __HAIKU__
2167 thread_info info;
2168 STACK_GROW_DIR_DETECTION;
2169 CHECK_ERR(get_thread_info(find_thread(NULL), &info));
2170 *addr = info.stack_base;
2171 *size = (uintptr_t)info.stack_end - (uintptr_t)info.stack_base;
2172 STACK_DIR_UPPER((void)0, (void)(*addr = (char *)*addr + *size));
2173#else
2174#error STACKADDR_AVAILABLE is defined but not implemented.
2175#endif
2176 return 0;
2177#undef CHECK_ERR
2178}
2179#endif
2180
2181static struct {
2182 rb_nativethread_id_t id;
2183 size_t stack_maxsize;
2184 VALUE *stack_start;
2185} native_main_thread;
2186
2187#ifdef STACK_END_ADDRESS
2188extern void *STACK_END_ADDRESS;
2189#endif
2190
2191static void
2192native_thread_init_main_thread_stack(void *addr)
2193{
2194 native_main_thread.id = pthread_self();
2195#ifdef RUBY_ASAN_ENABLED
2196 addr = asan_get_real_stack_addr((void *)addr);
2197#endif
2198
2199#if MAINSTACKADDR_AVAILABLE
2200 if (native_main_thread.stack_maxsize) return;
2201 {
2202 void* stackaddr;
2203 size_t size;
2204 if (get_main_stack(&stackaddr, &size) == 0) {
2205 native_main_thread.stack_maxsize = size;
2206 native_main_thread.stack_start = stackaddr;
2207 goto bound_check;
2208 }
2209 }
2210#endif
2211#ifdef STACK_END_ADDRESS
2212 native_main_thread.stack_start = STACK_END_ADDRESS;
2213#else
2214 if (!native_main_thread.stack_start ||
2215 STACK_UPPER((VALUE *)(void *)&addr,
2216 native_main_thread.stack_start > (VALUE *)addr,
2217 native_main_thread.stack_start < (VALUE *)addr)) {
2218 native_main_thread.stack_start = (VALUE *)addr;
2219 }
2220#endif
2221 {
2222#if defined(HAVE_GETRLIMIT)
2223#if defined(PTHREAD_STACK_DEFAULT)
2224 size_t size = PTHREAD_STACK_DEFAULT;
2225#else
2226 size_t size = RUBY_VM_THREAD_VM_STACK_SIZE;
2227#endif
2228 size_t space;
2229 struct rlimit rlim;
2230 STACK_GROW_DIR_DETECTION;
2231 if (getrlimit(RLIMIT_STACK, &rlim) == 0) {
2232 size = (size_t)rlim.rlim_cur;
2233 }
2234 addr = native_main_thread.stack_start;
2235 if (IS_STACK_DIR_UPPER()) {
2236 space = ((size_t)((char *)addr + size) / RB_THREAD_PAGE_SIZE) * RB_THREAD_PAGE_SIZE - (size_t)addr;
2237 }
2238 else {
2239 space = (size_t)addr - ((size_t)((char *)addr - size) / RB_THREAD_PAGE_SIZE + 1) * RB_THREAD_PAGE_SIZE;
2240 }
2241 native_main_thread.stack_maxsize = space;
2242#endif
2243 }
2244
2245#if MAINSTACKADDR_AVAILABLE
2246 bound_check:
2247#endif
2248 /* If addr is out of range of main-thread stack range estimation, */
2249 /* it should be on co-routine (alternative stack). [Feature #2294] */
2250 {
2251 void *start, *end;
2252 STACK_GROW_DIR_DETECTION;
2253
2254 if (IS_STACK_DIR_UPPER()) {
2255 start = native_main_thread.stack_start;
2256 end = (char *)native_main_thread.stack_start + native_main_thread.stack_maxsize;
2257 }
2258 else {
2259 start = (char *)native_main_thread.stack_start - native_main_thread.stack_maxsize;
2260 end = native_main_thread.stack_start;
2261 }
2262
2263 if ((void *)addr < start || (void *)addr > end) {
2264 /* out of range */
2265 native_main_thread.stack_start = (VALUE *)addr;
2266 native_main_thread.stack_maxsize = 0; /* unknown */
2267 }
2268 }
2269}
2270
2271#define CHECK_ERR(expr) \
2272 {int err = (expr); if (err) {rb_bug_errno(#expr, err);}}
2273
2274static int
2275native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame)
2276{
2277 rb_nativethread_id_t curr = pthread_self();
2278#ifdef RUBY_ASAN_ENABLED
2279 local_in_parent_frame = asan_get_real_stack_addr(local_in_parent_frame);
2280 th->ec->machine.asan_fake_stack_handle = asan_get_thread_fake_stack_handle();
2281#endif
2282
2283 if (!native_main_thread.id) {
2284 /* This thread is the first thread, must be the main thread -
2285 * configure the native_main_thread object */
2286 native_thread_init_main_thread_stack(local_in_parent_frame);
2287 }
2288
2289 if (pthread_equal(curr, native_main_thread.id)) {
2290 th->ec->machine.stack_start = native_main_thread.stack_start;
2291 th->ec->machine.stack_maxsize = native_main_thread.stack_maxsize;
2292 }
2293 else {
2294#ifdef STACKADDR_AVAILABLE
2295 if (th_has_dedicated_nt(th)) {
2296 void *start;
2297 size_t size;
2298
2299 if (get_stack(&start, &size) == 0) {
2300 uintptr_t diff = (uintptr_t)start - (uintptr_t)local_in_parent_frame;
2301 th->ec->machine.stack_start = local_in_parent_frame;
2302 th->ec->machine.stack_maxsize = size - diff;
2303 }
2304 }
2305#else
2306 rb_raise(rb_eNotImpError, "ruby engine can initialize only in the main thread");
2307#endif
2308 }
2309
2310 return 0;
2311}
2312
2313struct nt_param {
2314 rb_vm_t *vm;
2315 struct rb_native_thread *nt;
2316};
2317
2318static void *
2319nt_start(void *ptr);
2320
2321static int
2322native_thread_create0(struct rb_native_thread *nt)
2323{
2324 int err = 0;
2325 pthread_attr_t attr;
2326
2327 const size_t stack_size = nt->vm->default_params.thread_machine_stack_size;
2328
2329#ifdef USE_SIGALTSTACK
2330 nt->altstack = rb_allocate_sigaltstack();
2331#endif
2332
2333 CHECK_ERR(pthread_attr_init(&attr));
2334
2335# ifdef PTHREAD_STACK_MIN
2336 RUBY_DEBUG_LOG("stack size: %lu", (unsigned long)stack_size);
2337 CHECK_ERR(pthread_attr_setstacksize(&attr, stack_size));
2338# endif
2339
2340# ifdef HAVE_PTHREAD_ATTR_SETINHERITSCHED
2341 CHECK_ERR(pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED));
2342# endif
2343 CHECK_ERR(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
2344
2345 err = pthread_create(&nt->thread_id, &attr, nt_start, nt);
2346
2347 RUBY_DEBUG_LOG("nt:%d err:%d", (int)nt->serial, err);
2348
2349 CHECK_ERR(pthread_attr_destroy(&attr));
2350
2351 return err;
2352}
2353
2354static void
2355native_thread_setup(struct rb_native_thread *nt)
2356{
2357 // init cond
2358 rb_native_cond_initialize(&nt->cond.readyq);
2359
2360 if (&nt->cond.readyq != &nt->cond.intr) {
2361 rb_native_cond_initialize(&nt->cond.intr);
2362 }
2363}
2364
2365static void
2366native_thread_setup_on_thread(struct rb_native_thread *nt)
2367{
2368 // init tid
2369#ifdef RB_THREAD_T_HAS_NATIVE_ID
2370 nt->tid = get_native_thread_id();
2371#endif
2372
2373 // init signal handler
2374 RB_ALTSTACK_INIT(nt->altstack, nt->altstack);
2375}
2376
2377static struct rb_native_thread *
2378native_thread_alloc(void)
2379{
2380 struct rb_native_thread *nt = ZALLOC(struct rb_native_thread);
2381 native_thread_setup(nt);
2382
2383#if USE_MN_THREADS
2384 nt->nt_context = ruby_xmalloc(sizeof(struct coroutine_context));
2385#endif
2386
2387#if USE_RUBY_DEBUG_LOG
2388 static rb_atomic_t nt_serial = 2;
2389 nt->serial = RUBY_ATOMIC_FETCH_ADD(nt_serial, 1);
2390#endif
2391 return nt;
2392}
2393
2394static int
2395native_thread_create_dedicated(rb_thread_t *th)
2396{
2397 th->nt = native_thread_alloc();
2398 th->nt->vm = th->vm;
2399 th->nt->running_thread = th;
2400 th->nt->dedicated = 1;
2401
2402 // vm stack
2403 size_t vm_stack_word_size = th->vm->default_params.thread_vm_stack_size / sizeof(VALUE);
2404 void *vm_stack = ruby_xmalloc(vm_stack_word_size * sizeof(VALUE));
2405 th->sched.malloc_stack = true;
2406 rb_ec_initialize_vm_stack(th->ec, vm_stack, vm_stack_word_size);
2407 th->sched.context_stack = vm_stack;
2408 th->sched.context_stack_size = vm_stack_word_size;
2409
2410 int err = native_thread_create0(th->nt);
2411 if (!err) {
2412 // setup
2413 thread_sched_to_ready(TH_SCHED(th), th);
2414 }
2415 return err;
2416}
2417
2418static void
2419call_thread_start_func_2(rb_thread_t *th)
2420{
2421 /* Capture the address of a local in this stack frame to mark the beginning of the
2422 machine stack for this thread. This is required even if we can tell the real
2423 stack beginning from the pthread API in native_thread_init_stack, because
2424 glibc stores some of its own data on the stack before calling into user code
2425 on a new thread, and replacing that data on fiber-switch would break it (see
2426 bug #13887) */
2427 VALUE stack_start = 0;
2428 VALUE *stack_start_addr = asan_get_real_stack_addr(&stack_start);
2429
2430 native_thread_init_stack(th, stack_start_addr);
2431 thread_start_func_2(th, th->ec->machine.stack_start);
2432}
2433
2434static void *
2435nt_start(void *ptr)
2436{
2437 struct rb_native_thread *nt = (struct rb_native_thread *)ptr;
2438 rb_vm_t *vm = nt->vm;
2439
2440 native_thread_setup_on_thread(nt);
2441
2442 // init tid
2443#ifdef RB_THREAD_T_HAS_NATIVE_ID
2444 nt->tid = get_native_thread_id();
2445#endif
2446
2447#if USE_RUBY_DEBUG_LOG && defined(RUBY_NT_SERIAL)
2448 ruby_nt_serial = nt->serial;
2449#endif
2450
2451 RUBY_DEBUG_LOG("nt:%u", nt->serial);
2452
2453 if (!nt->dedicated) {
2454 coroutine_initialize_main(nt->nt_context);
2455 }
2456
2457 bool retired = false;
2458
2459 while (1) {
2460 if (nt->dedicated) {
2461 // wait running turn
2462 rb_thread_t *th = nt->running_thread;
2463 struct rb_thread_sched *sched = TH_SCHED(th);
2464
2465 RUBY_DEBUG_LOG("on dedicated th:%u", rb_th_serial(th));
2466 ruby_thread_set_native(th);
2467
2468 thread_sched_lock(sched, th);
2469 {
2470 if (sched->running == th) {
2471 thread_sched_add_running_thread(sched, th);
2472 }
2473 thread_sched_wait_running_turn(sched, th, false);
2474 }
2475 thread_sched_unlock(sched, th);
2476
2477 // start threads
2478 call_thread_start_func_2(th);
2479 break; // TODO: allow to change to the SNT
2480 }
2481 else {
2482 RUBY_DEBUG_LOG("check next");
2483 if (nt->retiring) { // came back with no room in the shared pool
2484 retired = true;
2485 break;
2486 }
2487
2488 rb_ractor_t *r = ractor_sched_deq(vm, NULL);
2489
2490 if (r) {
2491 struct rb_thread_sched *sched = &r->threads.sched;
2492
2493 bool locked = true;
2494
2495 thread_sched_lock(sched, NULL);
2496 {
2497 rb_thread_t *next_th = sched->running;
2498
2499 if (next_th && next_th->nt == NULL) {
2500 RUBY_DEBUG_LOG("nt:%d next_th:%d", (int)nt->serial, (int)next_th->serial);
2501#if USE_MN_THREADS
2502 thread_sched_switch0(nt->nt_context, next_th, nt, false);
2503
2504 // If a coroutine terminated during the transfer, co_start
2505 // recorded it in nt->dead_co (switch0's return value is
2506 // backend-dependent, unusable; see thread_pthread.h).
2507 struct coroutine_context *dead_co = nt->dead_co;
2508 nt->dead_co = NULL;
2509 if (thread_sched_reclaim(dead_co)) {
2510 // it already released the sched lock before its
2511 // transfer (its Ractor may be gone): leave sched be.
2512 locked = false;
2513 }
2514#else
2515 thread_sched_switch0(nt->nt_context, next_th, nt, false);
2516#endif
2517 }
2518 else {
2519 RUBY_DEBUG_LOG("no schedulable threads -- next_th:%p", next_th);
2520 }
2521 }
2522 if (locked) {
2523 thread_sched_unlock(sched, NULL);
2524 }
2525 }
2526 else {
2527 // ractor_sched_deq retired this nt.
2528 retired = true;
2529 break;
2530 }
2531
2532 if (nt->dedicated) {
2533 // SNT becomes DNT while running
2534 break;
2535 }
2536 }
2537 }
2538
2539 if (retired) {
2540 // The counts dropped this nt already; nothing can reference it now.
2541 RUBY_DEBUG_LOG("retired nt:%u", nt->serial);
2542 native_thread_destroy_self(nt);
2543 }
2544
2545 return NULL;
2546}
2547
2548static int native_thread_create_shared(rb_thread_t *th);
2549
2550#if USE_MN_THREADS
2551static void nt_free_stack(void *mstack);
2552
2553
2554// Reclaim the context a coroutine thread recorded in nt->dead_co before its
2555// final transfer (co_start's epilogue). Our running here proves that transfer's
2556// register save into the block completed. Returns true when a thread did
2557// terminate -- it RELEASED the sched lock before transferring; NULL/false means
2558// a live yield, where the loop still owns the lock.
2559static bool
2560thread_sched_reclaim(struct coroutine_context *dead_co)
2561{
2562 struct rb_thread_context *tctx = (struct rb_thread_context *)dead_co;
2563
2564 if (tctx != NULL && tctx->dead) {
2565 nt_free_stack(tctx->stack);
2566 SIZED_FREE(tctx);
2567 // pairs with the increment at the top of coroutine_thread_terminated:
2568 // a waiting VM destruct may proceed once this reclaim is done
2569 VM_ASSERT(RUBY_ATOMIC_LOAD(GET_VM()->ractor.sched.winding_cnt) > 0);
2570 RUBY_ATOMIC_DEC(GET_VM()->ractor.sched.winding_cnt);
2571 return true;
2572 }
2573 return false;
2574}
2575#endif
2576
2577void
2578rb_threadptr_sched_free(rb_thread_t *th)
2579{
2580#if USE_MN_THREADS
2581 if (th->sched.malloc_stack) {
2582 // has dedicated
2583 SIZED_FREE_N((VALUE *)th->sched.context_stack, th->sched.context_stack_size);
2584 native_thread_destroy(th->nt);
2585 }
2586 else if (th->sched.context != NULL) {
2587 // a coroutine thread that never reached its epilogue (never started);
2588 // a terminated one is reclaimed by whoever resumed from its final
2589 // transfer (thread_sched_reclaim), and cleared this pointer.
2590 struct rb_thread_context *tctx = (struct rb_thread_context *)th->sched.context;
2591 nt_free_stack(tctx->stack);
2592 SIZED_FREE(tctx);
2593 th->sched.context = NULL;
2594 // TODO: how to free nt and nt->altstack?
2595 }
2596#else
2597 SIZED_FREE_N((VALUE *)th->sched.context_stack, th->sched.context_stack_size);
2598 native_thread_destroy(th->nt);
2599#endif
2600
2601 th->nt = NULL;
2602}
2603
2604
2605static int
2606native_thread_create(rb_thread_t *th)
2607{
2608 VM_ASSERT(th->nt == 0);
2609 RUBY_DEBUG_LOG("th:%d has_dnt:%d", th->serial, th->has_dedicated_nt);
2610 RB_INTERNAL_THREAD_HOOK(RUBY_INTERNAL_THREAD_EVENT_STARTED, th);
2611
2612 if (!th->ractor->threads.sched.enable_mn_threads) {
2613 th->has_dedicated_nt = 1;
2614 }
2615
2616 if (th->has_dedicated_nt) {
2617 return native_thread_create_dedicated(th);
2618 }
2619 else {
2620 return native_thread_create_shared(th);
2621 }
2622}
2623
2624#if USE_NATIVE_THREAD_PRIORITY
2625
2626static void
2627native_thread_apply_priority(rb_thread_t *th)
2628{
2629#if defined(_POSIX_PRIORITY_SCHEDULING) && (_POSIX_PRIORITY_SCHEDULING > 0)
2630 struct sched_param sp;
2631 int policy;
2632 int priority = 0 - th->priority;
2633 int max, min;
2634 pthread_getschedparam(th->nt->thread_id, &policy, &sp);
2635 max = sched_get_priority_max(policy);
2636 min = sched_get_priority_min(policy);
2637
2638 if (min > priority) {
2639 priority = min;
2640 }
2641 else if (max < priority) {
2642 priority = max;
2643 }
2644
2645 sp.sched_priority = priority;
2646 pthread_setschedparam(th->nt->thread_id, policy, &sp);
2647#else
2648 /* not touched */
2649#endif
2650}
2651
2652#endif /* USE_NATIVE_THREAD_PRIORITY */
2653
2654static int
2655native_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout, rb_thread_t *th)
2656{
2657 return rb_fd_select(n, readfds, writefds, exceptfds, timeout);
2658}
2659
2660static void
2661ubf_pthread_cond_signal(void *ptr)
2662{
2663 rb_thread_t *th = (rb_thread_t *)ptr;
2664 RUBY_DEBUG_LOG("th:%u on nt:%d", rb_th_serial(th), (int)th->nt->serial);
2665 rb_native_cond_signal(&th->nt->cond.intr);
2666}
2667
2668static void
2669native_cond_sleep(rb_thread_t *th, rb_hrtime_t *rel)
2670{
2671 rb_nativethread_lock_t *lock = &th->interrupt_lock;
2672 rb_nativethread_cond_t *cond = &th->nt->cond.intr;
2673
2674 /* Solaris cond_timedwait() return EINVAL if an argument is greater than
2675 * current_time + 100,000,000. So cut up to 100,000,000. This is
2676 * considered as a kind of spurious wakeup. The caller to native_sleep
2677 * should care about spurious wakeup.
2678 *
2679 * See also [Bug #1341] [ruby-core:29702]
2680 * http://download.oracle.com/docs/cd/E19683-01/816-0216/6m6ngupgv/index.html
2681 */
2682 const rb_hrtime_t max = (rb_hrtime_t)100000000 * RB_HRTIME_PER_SEC;
2683
2684 THREAD_BLOCKING_BEGIN(th);
2685 {
2687 th->unblock.func = ubf_pthread_cond_signal;
2688 th->unblock.arg = th;
2689
2690 if (RUBY_VM_INTERRUPTED(th->ec)) {
2691 /* interrupted. return immediate */
2692 RUBY_DEBUG_LOG("interrupted before sleep th:%u", rb_th_serial(th));
2693 }
2694 else {
2695 if (!rel) {
2696 rb_native_cond_wait(cond, lock);
2697 }
2698 else {
2699 rb_hrtime_t end;
2700
2701 if (*rel > max) {
2702 *rel = max;
2703 }
2704
2705 end = native_cond_timeout(cond, *rel);
2706 native_cond_timedwait(cond, lock, &end);
2707 }
2708 }
2709 th->unblock.func = 0;
2710
2712 }
2713 THREAD_BLOCKING_END(th);
2714
2715 RUBY_DEBUG_LOG("done th:%u", rb_th_serial(th));
2716}
2717
2718#ifdef USE_UBF_LIST
2719static CCAN_LIST_HEAD(ubf_list_head);
2720static rb_nativethread_lock_t ubf_list_lock = RB_NATIVETHREAD_LOCK_INIT;
2721
2722static void
2723ubf_list_atfork(void)
2724{
2725 ccan_list_head_init(&ubf_list_head);
2726 rb_native_mutex_initialize(&ubf_list_lock);
2727}
2728
2730static bool
2731ubf_list_contain_p(rb_thread_t *th)
2732{
2733 rb_thread_t *list_th;
2734 ccan_list_for_each(&ubf_list_head, list_th, sched.node.ubf) {
2735 if (list_th == th) return true;
2736 }
2737 return false;
2738}
2739
2740/* The thread 'th' is registered to be trying unblock. */
2741static void
2742register_ubf_list(rb_thread_t *th)
2743{
2744 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
2745 struct ccan_list_node *node = &th->sched.node.ubf;
2746
2747 VM_ASSERT(th->unblock.func != NULL);
2748
2749 rb_native_mutex_lock(&ubf_list_lock);
2750 {
2751 // check not connected yet
2752 if (ccan_list_empty((struct ccan_list_head*)node)) {
2753 VM_ASSERT(!ubf_list_contain_p(th));
2754 ccan_list_add(&ubf_list_head, node);
2755 }
2756 }
2757 rb_native_mutex_unlock(&ubf_list_lock);
2758
2759 timer_thread_wakeup();
2760}
2761
2762/* The thread 'th' is unblocked. It no longer need to be registered. */
2763static void
2764unregister_ubf_list(rb_thread_t *th)
2765{
2766 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
2767 struct ccan_list_node *node = &th->sched.node.ubf;
2768
2769 /* we can't allow re-entry into ubf_list_head */
2770 VM_ASSERT(th->unblock.func == NULL);
2771
2772 if (!ccan_list_empty((struct ccan_list_head*)node)) {
2773 rb_native_mutex_lock(&ubf_list_lock);
2774 {
2775 VM_ASSERT(ubf_list_contain_p(th));
2776 ccan_list_del_init(node);
2777 }
2778 rb_native_mutex_unlock(&ubf_list_lock);
2779 }
2780}
2781
2782/*
2783 * send a signal to intent that a target thread return from blocking syscall.
2784 * Maybe any signal is ok, but we chose SIGVTALRM.
2785 */
2786static void
2787ubf_wakeup_thread(rb_thread_t *th)
2788{
2789 RUBY_DEBUG_LOG("th:%u thread_id:%p", rb_th_serial(th), (void *)th->nt->thread_id);
2790
2791 pthread_kill(th->nt->thread_id, SIGVTALRM);
2792}
2793
2794static void
2795ubf_select(void *ptr)
2796{
2797 rb_thread_t *th = (rb_thread_t *)ptr;
2798 RUBY_DEBUG_LOG("wakeup th:%u", rb_th_serial(th));
2799 ubf_wakeup_thread(th);
2800 register_ubf_list(th);
2801}
2802
2803static bool
2804ubf_threads_empty(void)
2805{
2806 return ccan_list_empty(&ubf_list_head) != 0;
2807}
2808
2809static void
2810ubf_wakeup_all_threads(void)
2811{
2812 rb_thread_t *th;
2813 rb_native_mutex_lock(&ubf_list_lock);
2814 {
2815 ccan_list_for_each(&ubf_list_head, th, sched.node.ubf) {
2816 ubf_wakeup_thread(th);
2817 }
2818 }
2819 rb_native_mutex_unlock(&ubf_list_lock);
2820}
2821
2822#else /* USE_UBF_LIST */
2823#define register_ubf_list(th) (void)(th)
2824#define unregister_ubf_list(th) (void)(th)
2825#define ubf_select 0
2826static void ubf_wakeup_all_threads(void) { return; }
2827static bool ubf_threads_empty(void) { return true; }
2828#define ubf_list_atfork() do {} while (0)
2829#endif /* USE_UBF_LIST */
2830
2831#define TT_DEBUG 0
2832#define WRITE_CONST(fd, str) (void)(write((fd),(str),sizeof(str)-1)<0)
2833
2834void
2835rb_thread_wakeup_timer_thread(int sig)
2836{
2837 // This function can be called from signal handlers so that
2838 // pthread_mutex_lock() should not be used.
2839
2840 // wakeup timer thread
2841 timer_thread_wakeup_force();
2842
2843 // interrupt main thread if main thread is available
2844 if (RUBY_ATOMIC_LOAD(system_working)) {
2845 rb_vm_t *vm = GET_VM();
2846 rb_thread_t *main_th = vm->ractor.main_thread;
2847
2848 if (main_th) {
2849 volatile rb_execution_context_t *main_th_ec = ACCESS_ONCE(rb_execution_context_t *, main_th->ec);
2850
2851 if (main_th_ec) {
2852 RUBY_VM_SET_TRAP_INTERRUPT(main_th_ec);
2853
2854 if (vm->ubf_async_safe && main_th->unblock.func) {
2855 (main_th->unblock.func)(main_th->unblock.arg);
2856 }
2857 }
2858 }
2859 }
2860}
2861
2862#define CLOSE_INVALIDATE_PAIR(expr) \
2863 close_invalidate_pair(expr,"close_invalidate: "#expr)
2864static void
2865close_invalidate(int *fdp, const char *msg)
2866{
2867 int fd = *fdp;
2868
2869 *fdp = -1;
2870 if (close(fd) < 0) {
2871 async_bug_fd(msg, errno, fd);
2872 }
2873}
2874
2875static void
2876close_invalidate_pair(int fds[2], const char *msg)
2877{
2878 if (USE_EVENTFD && fds[0] == fds[1]) {
2879 fds[1] = -1; // disable write port first
2880 close_invalidate(&fds[0], msg);
2881 }
2882 else {
2883 close_invalidate(&fds[1], msg);
2884 close_invalidate(&fds[0], msg);
2885 }
2886}
2887
2888static void
2889set_nonblock(int fd)
2890{
2891 int oflags;
2892 int err;
2893
2894 oflags = fcntl(fd, F_GETFL);
2895 if (oflags == -1)
2896 rb_sys_fail(0);
2897 oflags |= O_NONBLOCK;
2898 err = fcntl(fd, F_SETFL, oflags);
2899 if (err == -1)
2900 rb_sys_fail(0);
2901}
2902
2903/* communication pipe with timer thread and signal handler */
2904static void
2905setup_communication_pipe_internal(int pipes[2])
2906{
2907 int err;
2908
2909 if (pipes[0] > 0 || pipes[1] > 0) {
2910 VM_ASSERT(pipes[0] > 0);
2911 VM_ASSERT(pipes[1] > 0);
2912 return;
2913 }
2914
2915 /*
2916 * Don't bother with eventfd on ancient Linux 2.6.22..2.6.26 which were
2917 * missing EFD_* flags, they can fall back to pipe
2918 */
2919#if USE_EVENTFD && defined(EFD_NONBLOCK) && defined(EFD_CLOEXEC)
2920 pipes[0] = pipes[1] = eventfd(0, EFD_NONBLOCK|EFD_CLOEXEC);
2921
2922 if (pipes[0] >= 0) {
2923 rb_update_max_fd(pipes[0]);
2924 return;
2925 }
2926#endif
2927
2928 err = rb_cloexec_pipe(pipes);
2929 if (err != 0) {
2930 rb_bug("can not create communication pipe");
2931 }
2932 rb_update_max_fd(pipes[0]);
2933 rb_update_max_fd(pipes[1]);
2934 set_nonblock(pipes[0]);
2935 set_nonblock(pipes[1]);
2936}
2937
2938#if !defined(SET_CURRENT_THREAD_NAME) && defined(__linux__) && defined(PR_SET_NAME)
2939# define SET_CURRENT_THREAD_NAME(name) prctl(PR_SET_NAME, name)
2940#endif
2941
2942enum {
2943 THREAD_NAME_MAX =
2944#if defined(__linux__)
2945 16
2946#elif defined(__APPLE__)
2947/* Undocumented, and main thread seems unlimited */
2948 64
2949#else
2950 16
2951#endif
2952};
2953
2954static VALUE threadptr_invoke_proc_location(rb_thread_t *th);
2955
2956static void
2957native_set_thread_name(rb_thread_t *th)
2958{
2959#ifdef SET_CURRENT_THREAD_NAME
2960 VALUE loc;
2961 if (!NIL_P(loc = th->name)) {
2962 SET_CURRENT_THREAD_NAME(RSTRING_PTR(loc));
2963 }
2964 else if ((loc = threadptr_invoke_proc_location(th)) != Qnil) {
2965 char *name, *p;
2966 char buf[THREAD_NAME_MAX];
2967 size_t len;
2968 int n;
2969
2970 name = RSTRING_PTR(RARRAY_AREF(loc, 0));
2971 p = strrchr(name, '/'); /* show only the basename of the path. */
2972 if (p && p[1])
2973 name = p + 1;
2974
2975 n = snprintf(buf, sizeof(buf), "%s:%d", name, NUM2INT(RARRAY_AREF(loc, 1)));
2976 RB_GC_GUARD(loc);
2977
2978 len = (size_t)n;
2979 if (len >= sizeof(buf)) {
2980 buf[sizeof(buf)-2] = '*';
2981 buf[sizeof(buf)-1] = '\0';
2982 }
2983 SET_CURRENT_THREAD_NAME(buf);
2984 }
2985#endif
2986}
2987
2988static void
2989native_set_another_thread_name(rb_nativethread_id_t thread_id, VALUE name)
2990{
2991#if defined SET_ANOTHER_THREAD_NAME || defined SET_CURRENT_THREAD_NAME
2992 char buf[THREAD_NAME_MAX];
2993 const char *s = "";
2994# if !defined SET_ANOTHER_THREAD_NAME
2995 if (!pthread_equal(pthread_self(), thread_id)) return;
2996# endif
2997 if (!NIL_P(name)) {
2998 long n;
2999 RSTRING_GETMEM(name, s, n);
3000 if (n >= (int)sizeof(buf)) {
3001 memcpy(buf, s, sizeof(buf)-1);
3002 buf[sizeof(buf)-1] = '\0';
3003 s = buf;
3004 }
3005 }
3006# if defined SET_ANOTHER_THREAD_NAME
3007 SET_ANOTHER_THREAD_NAME(thread_id, s);
3008# elif defined SET_CURRENT_THREAD_NAME
3009 SET_CURRENT_THREAD_NAME(s);
3010# endif
3011#endif
3012}
3013
3014#if defined(RB_THREAD_T_HAS_NATIVE_ID) || defined(__APPLE__)
3015static VALUE
3016native_thread_native_thread_id(rb_thread_t *target_th)
3017{
3018 if (!target_th->nt) return Qnil;
3019
3020#ifdef RB_THREAD_T_HAS_NATIVE_ID
3021 int tid = target_th->nt->tid;
3022 if (tid == 0) return Qnil;
3023 return INT2FIX(tid);
3024#elif defined(__APPLE__)
3025 uint64_t tid;
3026/* The first condition is needed because MAC_OS_X_VERSION_10_6
3027 is not defined on 10.5, and while __POWERPC__ takes care of ppc/ppc64,
3028 i386 will be broken without this. Note, 10.5 is supported with GCC upstream,
3029 so it has C++17 and everything needed to build modern Ruby. */
3030# if (!defined(MAC_OS_X_VERSION_10_6) || \
3031 (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6) || \
3032 defined(__POWERPC__) /* never defined for PowerPC platforms */)
3033 const bool no_pthread_threadid_np = true;
3034# define NO_PTHREAD_MACH_THREAD_NP 1
3035# elif MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6
3036 const bool no_pthread_threadid_np = false;
3037# else
3038# if !(defined(__has_attribute) && __has_attribute(availability))
3039 /* __API_AVAILABLE macro does nothing on gcc */
3040 __attribute__((weak)) int pthread_threadid_np(pthread_t, uint64_t*);
3041# endif
3042 /* Check weakly linked symbol */
3043 const bool no_pthread_threadid_np = !&pthread_threadid_np;
3044# endif
3045 if (no_pthread_threadid_np) {
3046 return ULL2NUM(pthread_mach_thread_np(pthread_self()));
3047 }
3048# ifndef NO_PTHREAD_MACH_THREAD_NP
3049 int e = pthread_threadid_np(target_th->nt->thread_id, &tid);
3050 if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
3051 return ULL2NUM((unsigned long long)tid);
3052# endif
3053#endif
3054}
3055# define USE_NATIVE_THREAD_NATIVE_THREAD_ID 1
3056#else
3057# define USE_NATIVE_THREAD_NATIVE_THREAD_ID 0
3058#endif
3059
3060static struct {
3061 rb_serial_t created_fork_gen;
3062 pthread_t pthread_id;
3063
3064 int comm_fds[2]; // r, w
3065
3066#if (HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H) && USE_MN_THREADS
3067 int event_fd; // kernel event queue fd (epoll/kqueue)
3068#endif
3069#if HAVE_SYS_EPOLL_H && USE_MN_THREADS
3070#define EPOLL_EVENTS_MAX 0x10
3071 struct epoll_event finished_events[EPOLL_EVENTS_MAX];
3072#elif HAVE_SYS_EVENT_H && USE_MN_THREADS
3073#define KQUEUE_EVENTS_MAX 0x10
3074 struct kevent finished_events[KQUEUE_EVENTS_MAX];
3075#endif
3076
3077 // waiting threads list
3078 struct ccan_list_head waiting; // waiting threads in ractors
3079 pthread_mutex_t waiting_lock;
3080
3081#if (HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H) && USE_MN_THREADS
3082 // fd -> struct rb_fd_waiters, in chunks so entries never move.
3083 // Protected by waiting_lock.
3084 struct rb_fd_waiters **fdmap_chunks;
3085 unsigned int fdmap_nchunks;
3086#endif
3087} timer_th = {
3088 .created_fork_gen = 0,
3089};
3090
3091#define TIMER_THREAD_CREATED_P() (timer_th.created_fork_gen == current_fork_gen)
3092
3093static void timer_thread_check_timeslice(rb_vm_t *vm);
3094static int timer_thread_set_timeout(rb_vm_t *vm);
3095static void timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial);
3096static rb_thread_t *thread_sched_waiting_thread(struct rb_thread_sched_waiting *w);
3097
3098#include "thread_pthread_mn.c"
3099
3100static rb_thread_t *
3101thread_sched_waiting_thread(struct rb_thread_sched_waiting *w)
3102{
3103 if (w) {
3104 return (rb_thread_t *)((size_t)w - offsetof(rb_thread_t, sched.waiting_reason));
3105 }
3106 else {
3107 return NULL;
3108 }
3109}
3110
3111static int
3112timer_thread_set_timeout(rb_vm_t *vm)
3113{
3114#if 0
3115 return 10; // ms
3116#else
3117 int timeout = -1;
3118
3119 ractor_sched_lock(vm, NULL);
3120 {
3121 if ( !ccan_list_empty(&vm->ractor.sched.timeslice_threads) // (1-1) Provide time slice for active NTs
3122 || !ubf_threads_empty() // (1-3) Periodic UBF
3123 || vm->ractor.sched.grq_cnt > 0 // (1-4) Lazy GRQ deq start
3124 ) {
3125
3126 RUBY_DEBUG_LOG("timeslice:%d ubf:%d grq:%d",
3127 !ccan_list_empty(&vm->ractor.sched.timeslice_threads),
3128 !ubf_threads_empty(),
3129 (vm->ractor.sched.grq_cnt > 0));
3130
3131 timeout = 10; // ms
3132 vm->ractor.sched.timeslice_wait_inf = false;
3133 }
3134 else {
3135 vm->ractor.sched.timeslice_wait_inf = true;
3136 }
3137 }
3138 ractor_sched_unlock(vm, NULL);
3139
3140 // Always check waiting threads to find minimum timeout
3141 // even when scheduler has work (grq_cnt > 0)
3142 rb_native_mutex_lock(&timer_th.waiting_lock);
3143 {
3144 struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node);
3145 rb_thread_t *th = thread_sched_waiting_thread(w);
3146
3147 if (th && (th->sched.waiting_reason.flags & thread_sched_waiting_timeout)) {
3148 rb_hrtime_t now = rb_hrtime_now();
3149 rb_hrtime_t hrrel = rb_hrtime_sub(th->sched.waiting_reason.data.timeout, now);
3150
3151 RUBY_DEBUG_LOG("th:%u now:%lu rel:%lu", rb_th_serial(th), (unsigned long)now, (unsigned long)hrrel);
3152
3153 rb_hrtime_t msec = (hrrel + RB_HRTIME_PER_MSEC - 1) / RB_HRTIME_PER_MSEC;
3154 // A deadline further away than INT_MAX ms must clamp, not truncate:
3155 // a negative timeout would be an untimed epoll_wait.
3156 int thread_timeout = msec > INT_MAX ? INT_MAX : (int)msec; // ms
3157
3158 // Use minimum of scheduler timeout and thread sleep timeout
3159 if (timeout < 0 || thread_timeout < timeout) {
3160 timeout = thread_timeout;
3161 }
3162 }
3163 }
3164 rb_native_mutex_unlock(&timer_th.waiting_lock);
3165
3166 RUBY_DEBUG_LOG("timeout:%d inf:%d", timeout, (int)vm->ractor.sched.timeslice_wait_inf);
3167
3168 // fprintf(stderr, "timeout:%d\n", timeout);
3169 return timeout;
3170#endif
3171}
3172
3173static void
3174timer_thread_check_signal(rb_vm_t *vm)
3175{
3176 // ruby_sigchld_handler(vm); TODO
3177
3178 int signum = rb_signal_buff_size();
3179 if (UNLIKELY(signum > 0) && vm->ractor.main_thread) {
3180 RUBY_DEBUG_LOG("signum:%d", signum);
3181 threadptr_trap_interrupt(vm->ractor.main_thread);
3182 }
3183}
3184
3185static bool
3186timer_thread_check_exceed(rb_hrtime_t abs, rb_hrtime_t now)
3187{
3188 return abs <= now;
3189}
3190
3191static rb_thread_t *
3192timer_thread_deq_wakeup(rb_vm_t *vm, rb_hrtime_t now, uint32_t *event_serial)
3193{
3194 struct rb_thread_sched_waiting *w = ccan_list_top(&timer_th.waiting, struct rb_thread_sched_waiting, node);
3195
3196 if (w != NULL &&
3197 (w->flags & thread_sched_waiting_timeout) &&
3198 timer_thread_check_exceed(w->data.timeout, now)) {
3199
3200 RUBY_DEBUG_LOG("wakeup th:%u", rb_th_serial(thread_sched_waiting_thread(w)));
3201
3202 // delete from waiting list
3203 ccan_list_del_init(&w->node);
3204
3205 rb_thread_t *th = thread_sched_waiting_thread(w);
3206
3207#if (HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H) && USE_MN_THREADS
3208 // An fd+timeout waiter is also on its fd's waiter list; leave it there too.
3209 timer_thread_unregister_waiting(th, w->data.fd, w->flags);
3210#endif
3211
3212 // setup result
3213 w->flags = thread_sched_waiting_none;
3214 w->data.result = 0;
3215
3216 *event_serial = w->data.event_serial;
3217 return th;
3218 }
3219
3220 return NULL;
3221}
3222
3223static void
3224timer_thread_wakeup_thread_locked(struct rb_thread_sched *sched, rb_thread_t *th, uint32_t event_serial)
3225{
3226 if (sched->running != th && th->sched.event_serial == event_serial) {
3227 thread_sched_to_ready_common(sched, th, true, false);
3228 }
3229}
3230
3231static void
3232timer_thread_wakeup_thread(rb_thread_t *th, uint32_t event_serial)
3233{
3234 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
3235 struct rb_thread_sched *sched = TH_SCHED(th);
3236
3237 thread_sched_lock(sched, th);
3238 {
3239 timer_thread_wakeup_thread_locked(sched, th, event_serial);
3240 }
3241 thread_sched_unlock(sched, th);
3242}
3243
3244static void
3245timer_thread_check_timeout(rb_vm_t *vm)
3246{
3247 rb_hrtime_t now = rb_hrtime_now();
3248 rb_thread_t *th;
3249 uint32_t event_serial;
3250
3251 rb_native_mutex_lock(&timer_th.waiting_lock);
3252 {
3253 while ((th = timer_thread_deq_wakeup(vm, now, &event_serial)) != NULL) {
3254 rb_native_mutex_unlock(&timer_th.waiting_lock);
3255 timer_thread_wakeup_thread(th, event_serial);
3256 rb_native_mutex_lock(&timer_th.waiting_lock);
3257 }
3258 }
3259 rb_native_mutex_unlock(&timer_th.waiting_lock);
3260}
3261
3262static void
3263timer_thread_check_timeslice(rb_vm_t *vm)
3264{
3265 // TODO: check time
3266 rb_thread_t *th;
3267 ccan_list_for_each(&vm->ractor.sched.timeslice_threads, th, sched.node.timeslice_threads) {
3268 RUBY_DEBUG_LOG("timeslice th:%u", rb_th_serial(th));
3269 RUBY_VM_SET_TIMER_INTERRUPT(th->ec);
3270 }
3271}
3272
3273void
3274rb_assert_sig(void)
3275{
3276 sigset_t oldmask;
3277 pthread_sigmask(0, NULL, &oldmask);
3278 if (sigismember(&oldmask, SIGVTALRM)) {
3279 rb_bug("!!!");
3280 }
3281 else {
3282 RUBY_DEBUG_LOG("ok");
3283 }
3284}
3285
3286static void *
3287timer_thread_func(void *ptr)
3288{
3289 rb_vm_t *vm = (rb_vm_t *)ptr;
3290#if defined(RUBY_NT_SERIAL)
3291 ruby_nt_serial = (rb_atomic_t)-1;
3292#endif
3293
3294 RUBY_DEBUG_LOG("started%s", "");
3295
3296 while (RUBY_ATOMIC_LOAD(system_working)) {
3297 timer_thread_check_signal(vm);
3298 timer_thread_check_timeout(vm);
3299 ubf_wakeup_all_threads();
3300
3301 RUBY_DEBUG_LOG("system_working:%d", RUBY_ATOMIC_LOAD(system_working));
3302 timer_thread_polling(vm);
3303 }
3304
3305 RUBY_DEBUG_LOG("terminated");
3306 return NULL;
3307}
3308
3309/* only use signal-safe system calls here */
3310static void
3311signal_communication_pipe(int fd)
3312{
3313#if USE_EVENTFD
3314 const uint64_t buff = 1;
3315#else
3316 const char buff = '!';
3317#endif
3318 ssize_t result;
3319
3320 /* already opened */
3321 if (fd >= 0) {
3322 retry:
3323 if ((result = write(fd, &buff, sizeof(buff))) <= 0) {
3324 int e = errno;
3325 switch (e) {
3326 case EINTR: goto retry;
3327 case EAGAIN:
3328#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
3329 case EWOULDBLOCK:
3330#endif
3331 break;
3332 default:
3333 async_bug_fd("rb_thread_wakeup_timer_thread: write", e, fd);
3334 }
3335 }
3336 if (TT_DEBUG) WRITE_CONST(2, "rb_thread_wakeup_timer_thread: write\n");
3337 }
3338 else {
3339 // ignore wakeup
3340 }
3341}
3342
3343static void
3344timer_thread_wakeup_force(void)
3345{
3346 // should not use RUBY_DEBUG_LOG() because it can be called within signal handlers.
3347 signal_communication_pipe(timer_th.comm_fds[1]);
3348}
3349
3350static void
3351timer_thread_wakeup_locked(rb_vm_t *vm)
3352{
3353 // should be locked before.
3354 ASSERT_ractor_sched_locked(vm, NULL);
3355
3356 if (timer_th.created_fork_gen == current_fork_gen) {
3357 if (vm->ractor.sched.timeslice_wait_inf) {
3358 RUBY_DEBUG_LOG("wakeup with fd:%d", timer_th.comm_fds[1]);
3359 timer_thread_wakeup_force();
3360 }
3361 else {
3362 RUBY_DEBUG_LOG("will be wakeup...");
3363 }
3364 }
3365}
3366
3367static void
3368timer_thread_wakeup(void)
3369{
3370 rb_vm_t *vm = GET_VM();
3371
3372 ractor_sched_lock(vm, NULL);
3373 {
3374 timer_thread_wakeup_locked(vm);
3375 }
3376 ractor_sched_unlock(vm, NULL);
3377}
3378
3379static void
3380rb_thread_create_timer_thread(void)
3381{
3382 rb_serial_t created_fork_gen = timer_th.created_fork_gen;
3383
3384 RUBY_DEBUG_LOG("fork_gen create:%d current:%d", (int)created_fork_gen, (int)current_fork_gen);
3385
3386 timer_th.created_fork_gen = current_fork_gen;
3387
3388 if (created_fork_gen != current_fork_gen) {
3389 if (created_fork_gen != 0) {
3390 RUBY_DEBUG_LOG("forked child process");
3391
3392 CLOSE_INVALIDATE_PAIR(timer_th.comm_fds);
3393#if HAVE_SYS_EPOLL_H && USE_MN_THREADS
3394 close_invalidate(&timer_th.event_fd, "close event_fd");
3395#elif HAVE_SYS_EVENT_H && USE_MN_THREADS
3396 // A kqueue is not inherited across fork: the number names a closed
3397 // fd in the child, and closing it could hit a reused one.
3398 timer_th.event_fd = -1;
3399#endif
3400 // No mutex_destroy for waiting_lock: glibc returns EBUSY (and
3401 // rb_native_mutex_destroy rb_bugs) for a mutex another ractor's
3402 // M:N thread held at the fork moment. The initialize below
3403 // starts over.
3404 }
3405
3406 ccan_list_head_init(&timer_th.waiting);
3407 rb_native_mutex_initialize(&timer_th.waiting_lock);
3408
3409 // open communication channel
3410 setup_communication_pipe_internal(timer_th.comm_fds);
3411
3412 // open event fd
3413 timer_thread_setup_mn();
3414 }
3415
3416 int err = pthread_create(&timer_th.pthread_id, NULL, timer_thread_func, GET_VM());
3417 if (err != 0) {
3418 // The timer thread delivers signals and drives the M:N scheduler;
3419 // running without one only defers the failure to stranger places.
3420 rb_bug_errno("pthread_create (timer thread)", err);
3421 }
3422}
3423
3424static int
3425native_stop_timer_thread(void)
3426{
3427 RUBY_ATOMIC_SET(system_working, 0);
3428
3429 RUBY_DEBUG_LOG("wakeup send %d", timer_th.comm_fds[1]);
3430 timer_thread_wakeup_force();
3431 RUBY_DEBUG_LOG("wakeup sent");
3432 pthread_join(timer_th.pthread_id, NULL);
3433
3434 if (TT_DEBUG) fprintf(stderr, "stop timer thread\n");
3435
3436 return 1;
3437}
3438
3439static void
3440native_reset_timer_thread(void)
3441{
3442 //
3443}
3444
3445#ifdef HAVE_SIGALTSTACK
3446int
3447ruby_stack_overflowed_p(const rb_thread_t *th, const void *addr)
3448{
3449 void *base;
3450 size_t size;
3451 const size_t water_mark = RB_THREAD_PAGE_SIZE;
3452 STACK_GROW_DIR_DETECTION;
3453
3454 if (th) {
3455 size = th->ec->machine.stack_maxsize;
3456 base = (char *)th->ec->machine.stack_start - STACK_DIR_UPPER(0, size);
3457 }
3458#ifdef STACKADDR_AVAILABLE
3459 else if (get_stack(&base, &size) == 0) {
3460# ifdef __APPLE__
3461 // th is NULL in this branch; ask about the calling thread itself.
3462 if (pthread_equal(pthread_self(), native_main_thread.id)) {
3463 struct rlimit rlim;
3464 if (getrlimit(RLIMIT_STACK, &rlim) == 0 && rlim.rlim_cur > size) {
3465 size = (size_t)rlim.rlim_cur;
3466 }
3467 }
3468# endif
3469 base = (char *)base + STACK_DIR_UPPER(+size, -size);
3470 }
3471#endif
3472 else {
3473 return 0;
3474 }
3475
3476 if (size > water_mark) size = water_mark;
3477 if (IS_STACK_DIR_UPPER()) {
3478 if (size > ~(size_t)base+1) size = ~(size_t)base+1;
3479 if (addr > base && addr <= (void *)((char *)base + size)) return 1;
3480 }
3481 else {
3482 if (size > (size_t)base) size = (size_t)base;
3483 if (addr > (void *)((char *)base - size) && addr <= base) return 1;
3484 }
3485 return 0;
3486}
3487#endif
3488
3489int
3490rb_reserved_fd_p(int fd)
3491{
3492 /* no false-positive if out-of-FD at startup */
3493 if (fd < 0) return 0;
3494
3495 if (fd == timer_th.comm_fds[0] ||
3496 fd == timer_th.comm_fds[1]
3497#if (HAVE_SYS_EPOLL_H || HAVE_SYS_EVENT_H) && USE_MN_THREADS
3498 || fd == timer_th.event_fd
3499#endif
3500 ) {
3501 goto check_fork_gen;
3502 }
3503 return 0;
3504
3505 check_fork_gen:
3506 if (timer_th.created_fork_gen == current_fork_gen) {
3507 /* async-signal-safe */
3508 return 1;
3509 }
3510 else {
3511 return 0;
3512 }
3513}
3514
3515rb_nativethread_id_t
3517{
3518 return pthread_self();
3519}
3520
3521#if defined(USE_POLL) && !defined(HAVE_PPOLL)
3522/* TODO: don't ignore sigmask */
3523static int
3524ruby_ppoll(struct pollfd *fds, nfds_t nfds,
3525 const struct timespec *ts, const sigset_t *sigmask)
3526{
3527 int timeout_ms;
3528
3529 if (ts) {
3530 int tmp, tmp2;
3531
3532 if (ts->tv_sec > INT_MAX/1000)
3533 timeout_ms = INT_MAX;
3534 else {
3535 tmp = (int)(ts->tv_sec * 1000);
3536 /* round up 1ns to 1ms to avoid excessive wakeups for <1ms sleep */
3537 tmp2 = (int)((ts->tv_nsec + 999999L) / (1000L * 1000L));
3538 if (INT_MAX - tmp < tmp2)
3539 timeout_ms = INT_MAX;
3540 else
3541 timeout_ms = (int)(tmp + tmp2);
3542 }
3543 }
3544 else
3545 timeout_ms = -1;
3546
3547 return poll(fds, nfds, timeout_ms);
3548}
3549# define ppoll(fds,nfds,ts,sigmask) ruby_ppoll((fds),(nfds),(ts),(sigmask))
3550#endif
3551
3552static void
3553native_sleep(rb_thread_t *th, rb_hrtime_t *rel)
3554{
3555 struct rb_thread_sched *sched = TH_SCHED(th);
3556
3557 RUBY_DEBUG_LOG("rel:%d", rel ? (int)*rel : 0);
3558 if (rel) {
3559 if (th_has_dedicated_nt(th)) {
3560 native_cond_sleep(th, rel);
3561 }
3562 else {
3563 thread_sched_wait_events(sched, th, -1, thread_sched_waiting_timeout, rel);
3564 }
3565 }
3566 else {
3567 thread_sched_to_waiting_until_wakeup(sched, th);
3568 }
3569
3570 RUBY_DEBUG_LOG("wakeup");
3571}
3572
3573// fork read-write lock (only for pthread)
3574static pthread_rwlock_t rb_thread_fork_rw_lock = PTHREAD_RWLOCK_INITIALIZER;
3575
3576void
3577rb_thread_release_fork_lock(void)
3578{
3579 int r;
3580 if ((r = pthread_rwlock_unlock(&rb_thread_fork_rw_lock))) {
3581 rb_bug_errno("pthread_rwlock_unlock", r);
3582 }
3583}
3584
3585void
3586rb_thread_reset_fork_lock(void)
3587{
3588 int r;
3589 if ((r = pthread_rwlock_destroy(&rb_thread_fork_rw_lock))) {
3590 rb_bug_errno("pthread_rwlock_destroy", r);
3591 }
3592
3593 if ((r = pthread_rwlock_init(&rb_thread_fork_rw_lock, NULL))) {
3594 rb_bug_errno("pthread_rwlock_init", r);
3595 }
3596}
3597
3598void *
3599rb_thread_prevent_fork(void *(*func)(void *), void *data)
3600{
3601 int r;
3602 if ((r = pthread_rwlock_rdlock(&rb_thread_fork_rw_lock))) {
3603 rb_bug_errno("pthread_rwlock_rdlock", r);
3604 }
3605 void *result = func(data);
3606 rb_thread_release_fork_lock();
3607 return result;
3608}
3609
3610void
3611rb_thread_acquire_fork_lock(void)
3612{
3613 int r;
3614 if ((r = pthread_rwlock_wrlock(&rb_thread_fork_rw_lock))) {
3615 rb_bug_errno("pthread_rwlock_wrlock", r);
3616 }
3617}
3618
3619// thread internal event hooks (only for pthread)
3620
3621struct rb_internal_thread_event_hook {
3622 rb_internal_thread_event_callback callback;
3623 rb_event_flag_t event;
3624 void *user_data;
3625
3626 struct rb_internal_thread_event_hook *next;
3627};
3628
3629static pthread_rwlock_t rb_internal_thread_event_hooks_rw_lock = PTHREAD_RWLOCK_INITIALIZER;
3630
3631/* For the GC: whether any thread-event hook is registered right now. The rwlock
3632 * makes the answer happen-after any completed registration; a hook registered
3633 * after this read gets no event from the asking thread (rb_thread_execute_hooks
3634 * skips a swept thread), so it never sees the objects the caller may collect. */
3635bool
3636rb_thread_event_hooks_registered_p(void)
3637{
3638 int r;
3639 if ((r = pthread_rwlock_rdlock(&rb_internal_thread_event_hooks_rw_lock))) {
3640 rb_bug_errno("pthread_rwlock_rdlock", r);
3641 }
3642 const bool registered = (rb_internal_thread_event_hooks != NULL);
3643 if ((r = pthread_rwlock_unlock(&rb_internal_thread_event_hooks_rw_lock))) {
3644 rb_bug_errno("pthread_rwlock_unlock", r);
3645 }
3646 return registered;
3647}
3648
3649#if defined(HAVE_WORKING_FORK)
3650static void
3651rb_internal_thread_event_hooks_rw_lock_atfork(void)
3652{
3653 // After fork(), this rwlock may have been held by a now-dead thread.
3654 //
3655 // pthread_rwlock_destroy() on a held lock is undefined behavior, and
3656 // pthread_rwlock_init() on an already-initialized lock is also undefined
3657 // behavior
3658 //
3659 // Direct assignment of PTHREAD_RWLOCK_INITIALIZER is safe and portable.
3660 rb_internal_thread_event_hooks_rw_lock =
3661 (pthread_rwlock_t)PTHREAD_RWLOCK_INITIALIZER;
3662}
3663#endif
3664
3665rb_internal_thread_event_hook_t *
3666rb_internal_thread_add_event_hook(rb_internal_thread_event_callback callback, rb_event_flag_t internal_event, void *user_data)
3667{
3668 rb_internal_thread_event_hook_t *hook = ALLOC_N(rb_internal_thread_event_hook_t, 1);
3669 hook->callback = callback;
3670 hook->user_data = user_data;
3671 hook->event = internal_event;
3672
3673 int r;
3674 if ((r = pthread_rwlock_wrlock(&rb_internal_thread_event_hooks_rw_lock))) {
3675 rb_bug_errno("pthread_rwlock_wrlock", r);
3676 }
3677
3678 hook->next = rb_internal_thread_event_hooks;
3679 ATOMIC_PTR_EXCHANGE(rb_internal_thread_event_hooks, hook);
3680
3681 if ((r = pthread_rwlock_unlock(&rb_internal_thread_event_hooks_rw_lock))) {
3682 rb_bug_errno("pthread_rwlock_unlock", r);
3683 }
3684 return hook;
3685}
3686
3687bool
3688rb_internal_thread_remove_event_hook(rb_internal_thread_event_hook_t * hook)
3689{
3690 int r;
3691 if ((r = pthread_rwlock_wrlock(&rb_internal_thread_event_hooks_rw_lock))) {
3692 rb_bug_errno("pthread_rwlock_wrlock", r);
3693 }
3694
3695 bool success = FALSE;
3696
3697 if (rb_internal_thread_event_hooks == hook) {
3698 ATOMIC_PTR_EXCHANGE(rb_internal_thread_event_hooks, hook->next);
3699 success = TRUE;
3700 }
3701 else {
3702 rb_internal_thread_event_hook_t *h = rb_internal_thread_event_hooks;
3703
3704 do {
3705 if (h->next == hook) {
3706 h->next = hook->next;
3707 success = TRUE;
3708 break;
3709 }
3710 } while ((h = h->next));
3711 }
3712
3713 if ((r = pthread_rwlock_unlock(&rb_internal_thread_event_hooks_rw_lock))) {
3714 rb_bug_errno("pthread_rwlock_unlock", r);
3715 }
3716
3717 if (success) {
3718 SIZED_FREE(hook);
3719 }
3720 return success;
3721}
3722
3723static void
3724rb_thread_execute_hooks(rb_event_flag_t event, rb_thread_t *th)
3725{
3726 int r;
3727
3728 /* th->self == 0: the dying thread's final collection swept its Thread wrapper, so
3729 * no hook existed then; one registered since has no ordering claim to this event. */
3730 if (th->self == 0) return;
3731 if ((r = pthread_rwlock_rdlock(&rb_internal_thread_event_hooks_rw_lock))) {
3732 rb_bug_errno("pthread_rwlock_rdlock", r);
3733 }
3734
3735 if (rb_internal_thread_event_hooks) {
3736 rb_internal_thread_event_hook_t *h = rb_internal_thread_event_hooks;
3737 do {
3738 if (h->event & event) {
3739 rb_internal_thread_event_data_t event_data = {
3740 .thread = th->self,
3741 };
3742 (*h->callback)(event, &event_data, h->user_data);
3743 }
3744 } while((h = h->next));
3745 }
3746 if ((r = pthread_rwlock_unlock(&rb_internal_thread_event_hooks_rw_lock))) {
3747 rb_bug_errno("pthread_rwlock_unlock", r);
3748 }
3749}
3750
3751// return true if the current thread acquires DNT.
3752// return false if the current thread already acquires DNT.
3753bool
3755{
3756 rb_thread_t *th = GET_THREAD();
3757 bool is_snt = th->nt->dedicated == 0;
3758 native_thread_dedicated_inc(th->vm, th->ractor, th->nt);
3759
3760 return is_snt;
3761}
3762
3763void
3764rb_thread_malloc_stack_set(rb_thread_t *th, void *stack, size_t stack_size)
3765{
3766 th->sched.malloc_stack = true;
3767 th->sched.context_stack = stack;
3768 th->sched.context_stack_size = stack_size;
3769}
3770
3771#endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_FETCH_ADD(var, val)
Atomically replaces the value pointed by var with the result of addition of val to the old value of v...
Definition atomic.h:118
#define RUBY_ATOMIC_ADD(var, val)
Identical to RUBY_ATOMIC_FETCH_ADD, except for the return type.
Definition atomic.h:195
#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_SET(var, val)
Identical to RUBY_ATOMIC_EXCHANGE, except for the return type.
Definition atomic.h:185
uint32_t rb_event_flag_t
Represents event(s).
Definition event.h:108
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define ZALLOC
Old name of RB_ZALLOC.
Definition memory.h:402
#define ALLOC_N
Old name of RB_ALLOC_N.
Definition memory.h:399
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define NUM2INT
Old name of RB_NUM2INT.
Definition int.h:44
#define Qnil
Old name of RUBY_Qnil.
#define NIL_P
Old name of RB_NIL_P.
VALUE rb_eNotImpError
NotImplementedError exception.
Definition error.c:1441
void rb_syserr_fail(int e, const char *mesg)
Raises appropriate exception that represents a C errno.
Definition error.c:4042
void rb_bug_errno(const char *mesg, int errno_arg)
This is a wrapper of rb_bug() which automatically constructs appropriate message from the passed errn...
Definition error.c:1151
int rb_cloexec_pipe(int fildes[2])
Opens a pipe with closing on exec.
Definition io.c:429
void rb_update_max_fd(int fd)
Informs the interpreter that the passed fd can be the max.
Definition io.c:250
int rb_reserved_fd_p(int fd)
Queries if the given FD is reserved or not.
void rb_unblock_function_t(void *)
This is the type of UBFs.
Definition thread.h:336
void rb_timespec_now(struct timespec *ts)
Fills the current time into the given struct.
Definition time.c:2021
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
rb_internal_thread_event_hook_t * rb_internal_thread_add_event_hook(rb_internal_thread_event_callback func, rb_event_flag_t events, void *data)
Registers a thread event hook function.
#define RUBY_INTERNAL_THREAD_EVENT_EXITED
Triggered when a thread exits.
Definition thread.h:263
#define RUBY_INTERNAL_THREAD_EVENT_SUSPENDED
Triggered when a thread released the GVL.
Definition thread.h:256
bool rb_thread_lock_native_thread(void)
Declare the current Ruby thread should acquire a dedicated native thread on M:N thread scheduler.
#define RUBY_INTERNAL_THREAD_EVENT_STARTED
Triggered when a new thread is started.
Definition thread.h:235
bool rb_internal_thread_remove_event_hook(rb_internal_thread_event_hook_t *hook)
Unregister the passed hook.
#define RUBY_INTERNAL_THREAD_EVENT_READY
Triggered when a thread attempt to acquire the GVL.
Definition thread.h:242
#define RBIMPL_ATTR_MAYBE_UNUSED()
Wraps (or simulates) [[maybe_unused]]
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
#define rb_fd_select
Waits for multiple file descriptors at once.
Definition posix.h:66
#define RARRAY_AREF(a, i)
Definition rarray.h:402
#define RSTRING_GETMEM(str, ptrvar, lenvar)
Convenient macro to obtain the contents and length at once.
Definition rstring.h:450
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
The data structure which wraps the fd_set bitmap used by select(2).
Definition largesize.h:71
rb_nativethread_id_t rb_nativethread_self(void)
Queries the ID of the native thread that is calling this function.
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_cond_initialize(rb_nativethread_cond_t *cond)
Fills the passed condition variable with an initial value.
int rb_native_mutex_trylock(rb_nativethread_lock_t *lock)
Identical to rb_native_mutex_lock(), except it doesn't block in case rb_native_mutex_lock() would.
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_mutex_destroy(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_destroy.
void rb_native_cond_destroy(rb_nativethread_cond_t *cond)
Destroys the passed condition variable.
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.
void rb_native_cond_timedwait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex, unsigned long msec)
Identical to rb_native_cond_wait(), except it additionally takes timeout in msec resolution.
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40