Ruby 4.1.0dev (2026-10-26 revision 29264f878a52d2c36c75bfca6d04adaefeacbb30)
thread.c (29264f878a52d2c36c75bfca6d04adaefeacbb30)
1/**********************************************************************
2
3 thread.c -
4
5 $Author$
6
7 Copyright (C) 2004-2007 Koichi Sasada
8
9**********************************************************************/
10
11/*
12 YARV Thread Design
13
14 model 1: Userlevel Thread
15 Same as traditional ruby thread.
16
17 model 2: Native Thread with Global VM lock
18 Using pthread (or Windows thread) and Ruby threads run concurrent.
19
20 model 3: Native Thread with fine grain lock
21 Using pthread and Ruby threads run concurrent or parallel.
22
23 model 4: M:N User:Native threads with Global VM lock
24 Combination of model 1 and 2
25
26 model 5: M:N User:Native thread with fine grain lock
27 Combination of model 1 and 3
28
29------------------------------------------------------------------------
30
31 model 2:
32 A thread has mutex (GVL: Global VM Lock or Giant VM Lock) can run.
33 When thread scheduling, running thread release GVL. If running thread
34 try blocking operation, this thread must release GVL and another
35 thread can continue this flow. After blocking operation, thread
36 must check interrupt (RUBY_VM_CHECK_INTS).
37
38 Every VM can run parallel.
39
40 Ruby threads are scheduled by OS thread scheduler.
41
42------------------------------------------------------------------------
43
44 model 3:
45 Every threads run concurrent or parallel and to access shared object
46 exclusive access control is needed. For example, to access String
47 object or Array object, fine grain lock must be locked every time.
48 */
49
50
51/*
52 * FD_SET, FD_CLR and FD_ISSET have a small sanity check when using glibc
53 * 2.15 or later and set _FORTIFY_SOURCE > 0.
54 * However, the implementation is wrong. Even though Linux's select(2)
55 * supports large fd size (>FD_SETSIZE), it wrongly assumes fd is always
56 * less than FD_SETSIZE (i.e. 1024). And then when enabling HAVE_RB_FD_INIT,
57 * it doesn't work correctly and makes program abort. Therefore we need to
58 * disable FORTIFY_SOURCE until glibc fixes it.
59 */
60#undef _FORTIFY_SOURCE
61#undef __USE_FORTIFY_LEVEL
62#define __USE_FORTIFY_LEVEL 0
63
64/* for model 2 */
65
66#include "ruby/internal/config.h"
67
68#ifdef __linux__
69// Normally, gcc(1) translates calls to alloca() with inlined code. This is not done when either the -ansi, -std=c89, -std=c99, or the -std=c11 option is given and the header <alloca.h> is not included.
70# include <alloca.h>
71#endif
72
73#define TH_SCHED(th) (&(th)->ractor->threads.sched)
74
75#include "eval_intern.h"
76#include "hrtime.h"
77#include "internal.h"
78#include "internal/class.h"
79#include "internal/cont.h"
80#include "internal/jit.h"
81#include "internal/coverage.h"
82#include "internal/error.h"
83#include "internal/eval.h"
84#include "internal/gc.h"
85#include "internal/hash.h"
86#include "internal/io.h"
87#include "internal/object.h"
88#include "internal/proc.h"
90#include "internal/scheduler.h"
91#include "internal/signal.h"
92#include "internal/thread.h"
93#include "internal/time.h"
94#include "internal/warnings.h"
95#include "iseq.h"
96#include "ruby/debug.h"
97#include "ruby/io.h"
98#include "ruby/thread.h"
99#include "ruby/thread_native.h"
100#include "timev.h"
101#include "vm_core.h"
102#include "ractor_core.h"
103#include "vm_debug.h"
104#include "vm_sync.h"
105#include "zjit.h"
106
107#include "ccan/list/list.h"
108
109#ifndef USE_NATIVE_THREAD_PRIORITY
110#define USE_NATIVE_THREAD_PRIORITY 0
111#define RUBY_THREAD_PRIORITY_MAX 3
112#define RUBY_THREAD_PRIORITY_MIN -3
113#endif
114
115static VALUE rb_cThreadShield;
116static VALUE cThGroup;
117
118static VALUE sym_immediate;
119static VALUE sym_on_blocking;
120static VALUE sym_never;
121
122static uint32_t thread_default_quantum_ms = 100;
123
124#define THREAD_LOCAL_STORAGE_INITIALISED FL_USER13
125#define THREAD_LOCAL_STORAGE_INITIALISED_P(th) RB_FL_TEST_RAW((th), THREAD_LOCAL_STORAGE_INITIALISED)
126
127static inline VALUE
128rb_thread_local_storage(VALUE thread)
129{
130 if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) {
131 rb_ivar_set(thread, idLocals, rb_hash_new());
132 RB_FL_SET_RAW(thread, THREAD_LOCAL_STORAGE_INITIALISED);
133 }
134 return rb_ivar_get(thread, idLocals);
135}
136
137enum SLEEP_FLAGS {
138 SLEEP_DEADLOCKABLE = 0x01,
139 SLEEP_SPURIOUS_CHECK = 0x02,
140 SLEEP_ALLOW_SPURIOUS = 0x04,
141 SLEEP_NO_CHECKINTS = 0x08,
142};
143
144static void sleep_forever(rb_thread_t *th, unsigned int fl);
145static int sleep_hrtime(rb_thread_t *, rb_hrtime_t, unsigned int fl);
146
147static void rb_thread_sleep_deadly_allow_spurious_wakeup(VALUE blocker, VALUE timeout, rb_hrtime_t end);
148static int rb_threadptr_dead(rb_thread_t *th);
149static void rb_check_deadlock(rb_ractor_t *r);
150static int rb_threadptr_pending_interrupt_empty_p(const rb_thread_t *th);
151static const char *thread_status_name(rb_thread_t *th, int detail);
152static int hrtime_update_expire(rb_hrtime_t *, const rb_hrtime_t);
153NORETURN(static void async_bug_fd(const char *mesg, int errno_arg, int fd));
154MAYBE_UNUSED(static int consume_communication_pipe(int fd));
155
156static rb_atomic_t system_working = 1;
157static rb_internal_thread_specific_key_t specific_key_count;
158
159/********************************************************************************/
160
161#define THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION
162
164 enum rb_thread_status prev_status;
165};
166
167static int unblock_function_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, int flags);
168static void unblock_function_clear(rb_thread_t *th);
169
170static inline int blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region,
171 rb_unblock_function_t *ubf, void *arg, int flags);
172static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region);
173
174#define THREAD_BLOCKING_BEGIN(th) do { \
175 struct rb_thread_sched * const sched = TH_SCHED(th); \
176 RB_VM_SAVE_MACHINE_CONTEXT(th); \
177 thread_sched_to_waiting((sched), (th), true);
178
179#define THREAD_BLOCKING_END(th) \
180 thread_sched_to_running((sched), (th)); \
181 rb_ractor_thread_switch(th->ractor, th, false); \
182} while(0)
183
184#ifdef __GNUC__
185#ifdef HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P
186#define only_if_constant(expr, notconst) __builtin_choose_expr(__builtin_constant_p(expr), (expr), (notconst))
187#else
188#define only_if_constant(expr, notconst) (__builtin_constant_p(expr) ? (expr) : (notconst))
189#endif
190#else
191#define only_if_constant(expr, notconst) notconst
192#endif
193#define RB_NOGVL_FAIL_FLAGS (RB_NOGVL_INTR_FAIL | RB_NOGVL_PENDING_INTR_FAIL)
194#define BLOCKING_REGION(th, exec, ubf, ubfarg, flags) do { \
195 struct rb_blocking_region_buffer __region; \
196 if (blocking_region_begin(th, &__region, (ubf), (ubfarg), flags) || \
197 /* always return true unless one of the fail flags is set */ \
198 !only_if_constant((flags) & RB_NOGVL_FAIL_FLAGS, TRUE)) { \
199 /* Important that this is inlined into the macro, and not part of \
200 * blocking_region_begin - see bug #20493 */ \
201 RB_VM_SAVE_MACHINE_CONTEXT(th); \
202 thread_sched_to_waiting(TH_SCHED(th), th, false); \
203 exec; \
204 blocking_region_end(th, &__region); \
205 }; \
206} while(0)
207
208/*
209 * returns true if this thread was spuriously interrupted, false otherwise
210 * (e.g. hit by Thread#run or ran a Ruby-level Signal.trap handler)
211 */
212#define RUBY_VM_CHECK_INTS_BLOCKING(ec) vm_check_ints_blocking(ec)
213static inline int
214vm_check_ints_blocking(rb_execution_context_t *ec)
215{
216#ifdef RUBY_ASSERT_CRITICAL_SECTION
217 VM_ASSERT(ec->assert_critical_section_entered == 0);
218#endif
219
220 rb_thread_t *th = rb_ec_thread_ptr(ec);
221
222 if (LIKELY(rb_threadptr_pending_interrupt_empty_p(th))) {
223 if (LIKELY(!RUBY_VM_INTERRUPTED_ANY(ec))) return FALSE;
224 }
225 else {
226 th->pending_interrupt_queue_checked = 0;
227 RUBY_VM_SET_INTERRUPT(ec);
228 }
229
230 int result = rb_threadptr_execute_interrupts(th, 1);
231
232 // When a signal is received, we yield to the scheduler as soon as possible:
233 if (result || RUBY_VM_INTERRUPTED(ec)) {
235 if (scheduler != Qnil) {
236 rb_fiber_scheduler_yield(scheduler);
237 }
238 }
239
240 return result;
241}
242
243int
244rb_vm_check_ints_blocking(rb_execution_context_t *ec)
245{
246 return vm_check_ints_blocking(ec);
247}
248
249/*
250 * poll() is supported by many OSes, but so far Linux is the only
251 * one we know of that supports using poll() in all places select()
252 * would work.
253 */
254#if defined(HAVE_POLL)
255# if defined(__linux__)
256# define USE_POLL
257# endif
258# if defined(__FreeBSD_version) && __FreeBSD_version >= 1100000
259# define USE_POLL
260 /* FreeBSD does not set POLLOUT when POLLHUP happens */
261# define POLLERR_SET (POLLHUP | POLLERR)
262# endif
263#endif
264
265static void
266timeout_prepare(rb_hrtime_t **to, rb_hrtime_t *rel, rb_hrtime_t *end,
267 const struct timeval *timeout)
268{
269 if (timeout) {
270 *rel = rb_timeval2hrtime(timeout);
271 *end = rb_hrtime_add(rb_hrtime_now(), *rel);
272 *to = rel;
273 }
274 else {
275 *to = 0;
276 }
277}
278
279MAYBE_UNUSED(NOINLINE(static int thread_start_func_2(rb_thread_t *th, VALUE *stack_start)));
280MAYBE_UNUSED(static bool th_has_dedicated_nt(const rb_thread_t *th));
281MAYBE_UNUSED(static int waitfd_to_waiting_flag(int wfd_event));
282
283#ifdef RB_THREAD_SCHED_NONE
284// The no-thread model is not a set of primitives under the common scheduler:
285// it replaces the scheduler with stubs, so it stands alone.
286# include THREAD_IMPL_SRC
287#else
288// The scheduler pulls in the platform implementation (THREAD_IMPL_SRC) itself:
289// the platform primitives come first, the scheduler is built on top of them.
290# include "thread_sched.c"
291#endif
292
293/*
294 * TODO: somebody with win32 knowledge should be able to get rid of
295 * timer-thread by busy-waiting on signals.
296 */
297#ifndef BUSY_WAIT_SIGNALS
298# define BUSY_WAIT_SIGNALS (0)
299#endif
300
301#ifndef USE_EVENTFD
302# define USE_EVENTFD (0)
303#endif
304
305#include "thread_sync.c"
306
307void
308rb_nativethread_lock_initialize(rb_nativethread_lock_t *lock)
309{
311}
312
313void
314rb_nativethread_lock_destroy(rb_nativethread_lock_t *lock)
315{
317}
318
319void
320rb_nativethread_lock_lock(rb_nativethread_lock_t *lock)
321{
323}
324
325void
326rb_nativethread_lock_unlock(rb_nativethread_lock_t *lock)
327{
329}
330
331static int
332unblock_function_set(rb_thread_t *th, rb_unblock_function_t *func, void *arg, int flags)
333{
334 do {
335 if (flags & RB_NOGVL_INTR_FAIL) {
336 if (RUBY_VM_INTERRUPTED_ANY(th->ec)) {
337 return FALSE;
338 }
339 }
340 else {
341 RUBY_VM_CHECK_INTS(th->ec);
342 }
343 if (flags & RB_NOGVL_PENDING_INTR_FAIL) {
344 if (!rb_threadptr_pending_interrupt_empty_p(th)) {
345 return FALSE;
346 }
347 }
348
349 rb_native_mutex_lock(&th->interrupt_lock);
350 } while (!th->ec->raised_flag && RUBY_VM_INTERRUPTED_ANY(th->ec) &&
351 (rb_native_mutex_unlock(&th->interrupt_lock), TRUE));
352
353 VM_ASSERT(th->unblock.func == NULL);
354
355 th->unblock.func = func;
356 th->unblock.arg = arg;
357 rb_native_mutex_unlock(&th->interrupt_lock);
358
359 return TRUE;
360}
361
362static void
363unblock_function_clear(rb_thread_t *th)
364{
365 rb_native_mutex_lock(&th->interrupt_lock);
366 th->unblock.func = 0;
367 rb_native_mutex_unlock(&th->interrupt_lock);
368}
369
370static void
371threadptr_set_interrupt_locked(rb_thread_t *th, bool trap)
372{
373 // th->interrupt_lock should be acquired here
374
375 RUBY_DEBUG_LOG("th:%u trap:%d", rb_th_serial(th), trap);
376
377 if (trap) {
378 RUBY_VM_SET_TRAP_INTERRUPT(th->ec);
379 }
380 else {
381 RUBY_VM_SET_INTERRUPT(th->ec);
382 }
383
384 if (th->unblock.func != NULL) {
385 (th->unblock.func)(th->unblock.arg);
386 }
387 else {
388 /* none */
389 }
390}
391
392static void
393threadptr_set_interrupt(rb_thread_t *th, int trap)
394{
395 rb_native_mutex_lock(&th->interrupt_lock);
396 {
397 threadptr_set_interrupt_locked(th, trap);
398 }
399 rb_native_mutex_unlock(&th->interrupt_lock);
400}
401
402/* Set interrupt flag on another thread or current thread, and call its UBF if it has one set */
403void
404rb_threadptr_interrupt(rb_thread_t *th)
405{
406 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
407 threadptr_set_interrupt(th, false);
408}
409
410static void
411threadptr_trap_interrupt(rb_thread_t *th)
412{
413 threadptr_set_interrupt(th, true);
414}
415
416static void
417terminate_all(rb_ractor_t *r, const rb_thread_t *main_thread)
418{
419 rb_thread_t *th = 0;
420
421 ccan_list_for_each(&r->threads.set, th, lt_node) {
422 if (th != main_thread) {
423 RUBY_DEBUG_LOG("terminate start th:%u status:%s", rb_th_serial(th), thread_status_name(th, TRUE));
424
425 rb_threadptr_pending_interrupt_enque(th, RUBY_FATAL_THREAD_TERMINATED);
426 rb_threadptr_interrupt(th);
427
428 RUBY_DEBUG_LOG("terminate done th:%u status:%s", rb_th_serial(th), thread_status_name(th, TRUE));
429 }
430 else {
431 RUBY_DEBUG_LOG("main thread th:%u", rb_th_serial(th));
432 }
433 }
434}
435
436static void
437rb_threadptr_join_list_wakeup(rb_thread_t *thread)
438{
439 while (thread->join_list) {
440 struct rb_waiting_list *join_list = thread->join_list;
441
442 // Consume the entry from the join list:
443 thread->join_list = join_list->next;
444
445 rb_thread_t *target_thread = join_list->thread;
446
447 if (target_thread->scheduler != Qnil && join_list->fiber) {
448 rb_fiber_scheduler_unblock(target_thread->scheduler, target_thread->self, rb_fiberptr_self(join_list->fiber));
449 }
450 else {
451 rb_threadptr_interrupt(target_thread);
452
453 switch (target_thread->status) {
454 case THREAD_STOPPED:
455 case THREAD_STOPPED_FOREVER:
456 target_thread->status = THREAD_RUNNABLE;
457 break;
458 default:
459 break;
460 }
461 }
462 }
463}
464
465void
466rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th)
467{
468 while (th->keeping_mutexes) {
469 rb_mutex_t *mutex = th->keeping_mutexes;
470 th->keeping_mutexes = mutex->next_mutex;
471
472 // rb_warn("mutex #<%p> was not unlocked by thread #<%p>", (void *)mutex, (void*)th);
473 VM_ASSERT(mutex->ec_serial);
474 const char *error_message = rb_mutex_unlock_th(mutex, th, 0);
475 if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message);
476 }
477}
478
479void
480rb_thread_terminate_all(rb_thread_t *th)
481{
482 rb_ractor_t *cr = th->ractor;
483 rb_execution_context_t * volatile ec = th->ec;
484 volatile int sleeping = 0;
485
486 if (cr->threads.main != th) {
487 rb_bug("rb_thread_terminate_all: called by child thread (%p, %p)",
488 (void *)cr->threads.main, (void *)th);
489 }
490
491 /* unlock all locking mutexes */
492 rb_threadptr_unlock_all_locking_mutexes(th);
493
494 // tells the last sub-thread to wake this one out of the sleep below. Nothing
495 // clears it: no thread of this Ractor can run again once this returns.
496 cr->threads.terminating = true;
497
498 EC_PUSH_TAG(ec);
499 if (EC_EXEC_TAG() == TAG_NONE) {
500 retry:
501 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
502
503 terminate_all(cr, th);
504
505 while (rb_ractor_living_thread_num(cr) > 1) {
506 rb_hrtime_t rel = RB_HRTIME_PER_SEC;
507 /*q
508 * Thread exiting routine in thread_start_func_2 notify
509 * me when the last sub-thread exit.
510 */
511 sleeping = 1;
512 native_sleep(th, &rel);
513 RUBY_VM_CHECK_INTS_BLOCKING(ec);
514 sleeping = 0;
515 }
516 }
517 else {
518 /*
519 * When caught an exception (e.g. Ctrl+C), let's broadcast
520 * kill request again to ensure killing all threads even
521 * if they are blocked on sleep, mutex, etc.
522 */
523 if (sleeping) {
524 sleeping = 0;
525 goto retry;
526 }
527 }
528 EC_POP_TAG();
529}
530
531void rb_threadptr_root_fiber_terminate(rb_thread_t *th);
532static void threadptr_interrupt_exec_cleanup(rb_thread_t *th);
533
534static void
535thread_cleanup_func_before_exec(void *th_ptr)
536{
537 rb_thread_t *th = th_ptr;
538 th->status = THREAD_KILLED;
539
540 // The thread stack doesn't exist in the forked process:
541 th->ec->machine.stack_start = th->ec->machine.stack_end = NULL;
542
543 threadptr_interrupt_exec_cleanup(th);
544 rb_threadptr_root_fiber_terminate(th);
545}
546
547static void
548thread_cleanup_func(void *th_ptr, int atfork)
549{
550 rb_thread_t *th = th_ptr;
551
552 th->locking_mutex = Qfalse;
553 thread_cleanup_func_before_exec(th_ptr);
554
555 if (atfork) {
556 native_thread_destroy_atfork(th->nt);
557 th->nt = NULL;
558 // The copied interrupt_lock may have been held at the moment of
559 // fork (interrupters run concurrently); reinitialize it so that
560 // thread_free's destroy is well-defined in the child.
561 rb_native_mutex_initialize(&th->interrupt_lock);
562 return;
563 }
564
565 // interrupt_lock is destroyed in thread_free: while th is in its
566 // Ractor's living set, anyone (terminate_all on the Ractor's main
567 // thread, Thread#kill/#raise) may lock it -- and the living set keeps
568 // the Thread object marked, so it cannot reach thread_free while
569 // listed. Destroying it anywhere during teardown leaves a window where
570 // a concurrent interrupter locks a destroyed mutex (EINVAL).
571}
572
573void
574rb_thread_free_native_thread(void *th_ptr)
575{
576 rb_thread_t *th = th_ptr;
577
578 // A thread with a coroutine context does not own its native thread: that
579 // one is in the shared pool, listed there and with its altstack registered
580 // on whichever pthread is running this. See rb_threadptr_sched_free().
581 if (th->sched.context == NULL) {
582 native_thread_destroy_atfork(th->nt);
583 }
584 th->nt = NULL;
585}
586
587static VALUE rb_threadptr_raise(rb_thread_t *, int, VALUE *);
588static VALUE rb_thread_to_s(VALUE thread);
589
590void
591ruby_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame)
592{
593 native_thread_init_stack(th, local_in_parent_frame);
594}
595
596const VALUE *
597rb_vm_proc_local_ep(VALUE proc)
598{
599 const VALUE *ep = vm_proc_ep(proc);
600
601 if (ep) {
602 return rb_vm_ep_local_ep(ep);
603 }
604 else {
605 return NULL;
606 }
607}
608
609// for ractor, defined in vm.c
610VALUE rb_vm_invoke_proc_with_self(rb_execution_context_t *ec, rb_proc_t *proc, VALUE self,
611 int argc, const VALUE *argv, int kw_splat, VALUE passed_block_handler,
612 const rb_cref_t *cref);
613
614static VALUE
615thread_do_start_proc(rb_thread_t *th)
616{
617 VALUE args = th->invoke_arg.proc.args;
618 const VALUE *args_ptr;
619 int args_len;
620 VALUE procval = th->invoke_arg.proc.proc;
621 rb_proc_t *proc;
622 GetProcPtr(procval, proc);
623 const rb_cref_t *cref = rb_proc_refinements_cref_for_call(procval);
624
625 th->ec->errinfo = Qnil;
626 th->ec->root_lep = rb_vm_proc_local_ep(procval);
627 th->ec->root_svar = Qfalse;
628
629 vm_check_ints_blocking(th->ec);
630
631 if (th->invoke_type == thread_invoke_type_ractor_proc) {
632 VALUE self = rb_ractor_self(th->ractor);
633 th->thgroup = th->ractor->thgroup_default = rb_obj_alloc(cThGroup);
634
635 VM_ASSERT(FIXNUM_P(args));
636 args_len = FIX2INT(args);
637 args_ptr = ALLOCA_N(VALUE, args_len);
638 rb_ractor_receive_parameters(th->ec, th->ractor, args_len, (VALUE *)args_ptr);
639 vm_check_ints_blocking(th->ec);
640
641 return rb_vm_invoke_proc_with_self(
642 th->ec, proc, self,
643 args_len, args_ptr,
644 th->invoke_arg.proc.kw_splat,
645 VM_BLOCK_HANDLER_NONE,
646 cref
647 );
648 }
649 else {
650 args_len = RARRAY_LENINT(args);
651 if (args_len < 8) {
652 /* free proc.args if the length is enough small */
653 args_ptr = ALLOCA_N(VALUE, args_len);
654 MEMCPY((VALUE *)args_ptr, RARRAY_CONST_PTR(args), VALUE, args_len);
655 th->invoke_arg.proc.args = Qnil;
656 }
657 else {
658 args_ptr = RARRAY_CONST_PTR(args);
659 }
660
661 vm_check_ints_blocking(th->ec);
662
663 return rb_vm_invoke_proc(
664 th->ec, proc,
665 args_len, args_ptr,
666 th->invoke_arg.proc.kw_splat,
667 VM_BLOCK_HANDLER_NONE,
668 cref
669 );
670 }
671}
672
673static VALUE
674thread_do_start(rb_thread_t *th)
675{
676 native_set_thread_name(th);
677 VALUE result = Qundef;
678
679 switch (th->invoke_type) {
680 case thread_invoke_type_proc:
681 result = thread_do_start_proc(th);
682 break;
683
684 case thread_invoke_type_ractor_proc:
685 result = thread_do_start_proc(th);
686 rb_ractor_atexit(th->ec, result);
687 break;
688
689 case thread_invoke_type_func:
690 result = (*th->invoke_arg.func.func)(th->invoke_arg.func.arg);
691 break;
692
693 case thread_invoke_type_none:
694 rb_bug("unreachable");
695 }
696
697 return result;
698}
699
700void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec);
701
702static int
703thread_start_func_2(rb_thread_t *th, VALUE *stack_start)
704{
705 RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
706 VM_ASSERT(th != th->vm->ractor.main_thread);
707
708 enum ruby_tag_type state;
709 VALUE errinfo = Qnil;
710 rb_thread_t *ractor_main_th = th->ractor->threads.main;
711
712 // setup ractor
713 if (rb_ractor_status_p(th->ractor, ractor_blocking)) {
714 RB_VM_LOCK();
715 {
716 rb_vm_ractor_blocking_cnt_dec(th->vm, th->ractor, __FILE__, __LINE__);
717
718 /* Left 0 at creation (building them then would put them in the parent's
719 * objspace), so build them here out of objects this Ractor owns. The mask
720 * stack starts empty: inheriting it would reference the parent's
721 * unshareable mask Hash. */
722 th->pending_interrupt_queue = rb_ary_hidden_new(0);
723 th->pending_interrupt_mask_stack = rb_ary_hidden_new(0);
724 }
725 RB_VM_UNLOCK();
726 }
727
728 // Ensure that we are not joinable.
729 VM_ASSERT(UNDEF_P(th->value));
730
731 volatile int fiber_scheduler_closed = 0, event_thread_end_hooked = 0;
732 VALUE result = Qundef;
733
734 EC_PUSH_TAG(th->ec);
735
736 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
737 EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_BEGIN, th->self, 0, 0, 0, Qundef);
738
739 result = thread_do_start(th);
740 }
741
742 if (!fiber_scheduler_closed) {
743 fiber_scheduler_closed = 1;
745 }
746
747 if (!event_thread_end_hooked) {
748 event_thread_end_hooked = 1;
749 EXEC_EVENT_HOOK(th->ec, RUBY_EVENT_THREAD_END, th->self, 0, 0, 0, Qundef);
750 }
751
752 if (state == TAG_NONE) {
753 // This must be set AFTER doing all user-level code. At this point, the thread is effectively finished and calls to `Thread#join` will succeed.
754 th->value = result;
755 }
756 else {
757 errinfo = th->ec->errinfo;
758
759 VALUE exc = rb_vm_make_jump_tag_but_local_jump(state, Qundef);
760 if (!NIL_P(exc)) errinfo = exc;
761
762 if (state == TAG_FATAL) {
763 if (th->invoke_type == thread_invoke_type_ractor_proc) {
764 rb_ractor_atexit(th->ec, Qnil);
765 }
766 /* fatal error within this thread, need to stop whole script */
767 }
768 else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) {
769 if (th->invoke_type == thread_invoke_type_ractor_proc) {
770 rb_ractor_atexit_exception(th->ec);
771 }
772
773 /* exit on main_thread. */
774 }
775 else {
776 if (th->report_on_exception) {
777 VALUE mesg = rb_thread_to_s(th->self);
778 rb_str_cat_cstr(mesg, " terminated with exception (report_on_exception is true):\n");
779 rb_write_error_str(mesg);
780 rb_ec_error_print(th->ec, errinfo);
781 }
782
783 if (th->invoke_type == thread_invoke_type_ractor_proc) {
784 rb_ractor_atexit_exception(th->ec);
785 }
786
787 if (th->vm->thread_abort_on_exception ||
788 th->abort_on_exception || RTEST(ruby_debug)) {
789 /* exit on main_thread */
790 }
791 else {
792 errinfo = Qnil;
793 }
794 }
795 th->value = Qnil;
796 }
797
798 // The thread is effectively finished and can be joined.
799 VM_ASSERT(!UNDEF_P(th->value));
800
801 rb_threadptr_join_list_wakeup(th);
802 rb_threadptr_unlock_all_locking_mutexes(th);
803
804 if (th->invoke_type == thread_invoke_type_ractor_proc) {
805 rb_thread_terminate_all(th);
806 rb_ractor_teardown(th->ec);
807 }
808
809 th->status = THREAD_KILLED;
810 RUBY_DEBUG_LOG("killed th:%u", rb_th_serial(th));
811
812 if (th->vm->ractor.main_thread == th) {
813 ruby_stop(0);
814 }
815
816 if (RB_TYPE_P(errinfo, T_OBJECT)) {
817 /* treat with normal error object */
818 rb_threadptr_raise(ractor_main_th, 1, &errinfo);
819 }
820
821 EC_POP_TAG();
822
823 rb_ec_clear_current_thread_trace_func(th->ec);
824
825 /* locking_mutex must be Qfalse */
826 if (th->locking_mutex != Qfalse) {
827 rb_bug("thread_start_func_2: locking_mutex must not be set (%p:%"PRIxVALUE")",
828 (void *)th, th->locking_mutex);
829 }
830
831 if (th->ractor->threads.terminating &&
832 th->ractor->threads.cnt <= 2 /* main thread and this thread */) {
833 /* I'm last thread. wake up main thread from rb_thread_terminate_all */
834 rb_threadptr_interrupt(ractor_main_th);
835 }
836
837 rb_check_deadlock(th->ractor);
838
839 rb_fiber_close(th->ec->fiber_ptr);
840
841 thread_cleanup_func(th, FALSE);
842 VM_ASSERT(th->ec->vm_stack == NULL);
843
844 // A dying Ractor collects its own objspace here, before the scheduler handoff
845 // below, and captures the structs it must free at its last step.
846 struct rb_ractor_postmortem_frees pf = { NULL, NULL };
847 if (th->invoke_type == thread_invoke_type_ractor_proc) {
848 rb_ractor_postmortem(th, &pf);
849 }
850
851#if defined(USE_MN_THREADS) && USE_MN_THREADS
852 if (th_has_coroutine(th)) {
853 // wait out any pending wake while th and its Ractor are still alive
854 rb_thread_wake_fence(th);
855
856 // Run the coroutine thread's epilogue here, while th is still valid;
857 // co_start then only makes the final transfer (see
858 // coroutine_thread_terminated in thread_sched_mn.c).
859 coroutine_thread_terminated(th);
860 rb_ractor_postmortem_free(&pf);
861 return 0;
862 }
863#endif
864
865 if (th->invoke_type == thread_invoke_type_ractor_proc) {
866 // The postmortem epilogue below runs after this Ractor is unlinked and no
867 // longer counted, with the GVL already released, and it frees through
868 // VM-global state (the jit_cont list and its mutex, the fiber pool, the
869 // main objspace's malloc accounting). Nothing else holds the main Ractor
870 // back at that point, so count it like a coroutine epilogue: then
871 // ruby_vm_destruct waits for it (rb_thread_sched_wait_winding) instead of
872 // tearing that state down underneath. th is freed by the epilogue, so
873 // keep the VM pointer.
874 rb_vm_t *const vm = th->vm;
875 rb_thread_sched_winding_begin(vm);
876
877 // after rb_ractor_living_threads_remove()
878 // GC will happen anytime and this ractor can be collected (and destroy GVL).
879 // So gvl_release() should be before it.
880 thread_sched_to_dead(TH_SCHED(th), th);
881 rb_ractor_living_threads_remove(th->ractor, th);
882 rb_ractor_postmortem_free(&pf);
883
884 rb_thread_sched_winding_end(vm);
885 }
886 else {
887 rb_ractor_living_threads_remove(th->ractor, th);
888 thread_sched_to_dead(TH_SCHED(th), th);
889 }
890
891 return 0;
892}
895 enum thread_invoke_type type;
896
897 // for normal proc thread
898 VALUE args;
899 VALUE proc;
900
901 // for ractor
902 rb_ractor_t *g;
903
904 // for func
905 VALUE (*fn)(void *);
906};
907
908static void thread_specific_storage_alloc(rb_thread_t *th);
909
910static VALUE
911thread_create_core(VALUE thval, struct thread_create_params *params)
912{
913 rb_execution_context_t *ec = GET_EC();
914 rb_thread_t *th = rb_thread_ptr(thval), *current_th = rb_ec_thread_ptr(ec);
915 int err;
916
917 thread_specific_storage_alloc(th);
918
919 if (OBJ_FROZEN(current_th->thgroup)) {
920 rb_raise(rb_eThreadError,
921 "can't start a new thread (frozen ThreadGroup)");
922 }
923
924 /* A new Ractor must not inherit the creating thread's fiber storage: its
925 * entries may be objects owned by the creating Ractor. Only threads created
926 * within the same Ractor inherit it. */
927 if (params->type != thread_invoke_type_ractor_proc) {
928 rb_fiber_inherit_storage(ec, th->ec->fiber_ptr);
929 }
930
931 switch (params->type) {
932 case thread_invoke_type_proc:
933 th->invoke_type = thread_invoke_type_proc;
934 th->invoke_arg.proc.args = params->args;
935 th->invoke_arg.proc.proc = params->proc;
936 th->invoke_arg.proc.kw_splat = rb_keyword_given_p();
937 break;
938
939 case thread_invoke_type_ractor_proc:
940 th->invoke_type = thread_invoke_type_ractor_proc;
941 th->ractor = params->g;
942 th->ec->ractor_id = rb_ractor_id(th->ractor);
943 th->ec->serial = th->ractor->next_ec_serial++;
944 th->ractor->threads.main = th;
945 th->invoke_arg.proc.proc = rb_proc_isolate_bang(params->proc, Qnil);
946 th->invoke_arg.proc.args = INT2FIX(RARRAY_LENINT(params->args));
947 th->invoke_arg.proc.kw_splat = rb_keyword_given_p();
948 break;
949
950 case thread_invoke_type_func:
951 th->invoke_type = thread_invoke_type_func;
952 th->invoke_arg.func.func = params->fn;
953 th->invoke_arg.func.arg = (void *)params->args;
954 break;
955
956 default:
957 rb_bug("unreachable");
958 }
959
960 th->priority = current_th->priority;
961 th->thgroup = current_th->thgroup;
962
963 if (th->invoke_type == thread_invoke_type_ractor_proc) {
964 /* Left 0: the child's main thread builds this in its own objspace at start
965 * (thread_start_func_2). Built here it would sit rootless in the parent's
966 * objspace, freed by the parent's local GC before the child starts. */
967 th->pending_interrupt_queue = 0;
968 th->pending_interrupt_mask_stack = 0;
969 th->pending_interrupt_queue_checked = 0;
970 /* Same for the thread group: the parent's lives in the parent's objspace, and
971 * keeping it would point the child's Thread wrapper at a foreign unshareable
972 * object with no shref. Left 0 until thread_do_start_proc builds it. */
973 th->thgroup = 0;
974 }
975 else {
976 th->pending_interrupt_queue = rb_ary_hidden_new(0);
977 th->pending_interrupt_queue_checked = 0;
978 th->pending_interrupt_mask_stack = rb_ary_dup(current_th->pending_interrupt_mask_stack);
979 RBASIC_CLEAR_CLASS(th->pending_interrupt_mask_stack);
980 }
981
982 RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u th:%u", rb_ractor_id(th->ractor), rb_th_serial(th));
983
984 rb_ractor_living_threads_insert(th->ractor, th);
985
986 if (th->invoke_type == thread_invoke_type_ractor_proc) {
987 /* Create the default port and send the arguments only after the child joined
988 * vm->ractor.set, so a global GC in between still marks the port in its root
989 * scan. If either raises (an uncopyable argument, NoMemoryError), undo the
990 * membership: left in place it would make terminate_all wait forever. */
991 enum ruby_tag_type state;
992 EC_PUSH_TAG(ec);
993 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
994 rb_ractor_setup_default_port(params->g);
995 rb_ractor_send_parameters(ec, params->g, params->args);
996 }
997 EC_POP_TAG();
998 if (state != TAG_NONE) {
999 th->status = THREAD_KILLED;
1000 rb_ractor_cancel_creation(params->g, th);
1001 EC_JUMP_TAG(ec, state);
1002 }
1003 }
1004
1005 /* kick thread */
1006 err = native_thread_create(th);
1007 if (err) {
1008 th->status = THREAD_KILLED;
1009 if (th->invoke_type == thread_invoke_type_ractor_proc) {
1010 /* A child Ractor's main thread: the creator runs this, so the ordinary
1011 * removal (which assumes the current Ractor and would run the Ractor exit
1012 * protocol) does not apply. Undo the creation like the send-failure path. */
1013 rb_ractor_cancel_creation(th->ractor, th);
1014 }
1015 else {
1016 rb_ractor_living_threads_remove(th->ractor, th);
1017 }
1018 rb_raise(rb_eThreadError, "can't create Thread: %s", strerror(err));
1019 }
1020 return thval;
1021}
1022
1023#define threadptr_initialized(th) ((th)->invoke_type != thread_invoke_type_none)
1024
1025/*
1026 * call-seq:
1027 * Thread.new { ... } -> thread
1028 * Thread.new(*args, &proc) -> thread
1029 * Thread.new(*args) { |args| ... } -> thread
1030 *
1031 * Creates a new thread executing the given block.
1032 *
1033 * Any +args+ given to ::new will be passed to the block:
1034 *
1035 * arr = []
1036 * a, b, c = 1, 2, 3
1037 * Thread.new(a,b,c) { |d,e,f| arr << d << e << f }.join
1038 * arr #=> [1, 2, 3]
1039 *
1040 * A ThreadError exception is raised if ::new is called without a block.
1041 *
1042 * If you're going to subclass Thread, be sure to call super in your
1043 * +initialize+ method, otherwise a ThreadError will be raised.
1044 */
1045static VALUE
1046thread_s_new(int argc, VALUE *argv, VALUE klass)
1047{
1048 rb_thread_t *th;
1049 VALUE thread = rb_thread_alloc(klass);
1050
1051 if (GET_RACTOR()->threads.main->status == THREAD_KILLED) {
1052 rb_raise(rb_eThreadError, "can't alloc thread");
1053 }
1054
1055 rb_obj_call_init_kw(thread, argc, argv, RB_PASS_CALLED_KEYWORDS);
1056 th = rb_thread_ptr(thread);
1057 if (!threadptr_initialized(th)) {
1058 rb_raise(rb_eThreadError, "uninitialized thread - check '%"PRIsVALUE"#initialize'",
1059 klass);
1060 }
1061 return thread;
1062}
1063
1064/*
1065 * call-seq:
1066 * Thread.start([args]*) {|args| block } -> thread
1067 * Thread.fork([args]*) {|args| block } -> thread
1068 *
1069 * Basically the same as ::new. However, if class Thread is subclassed, then
1070 * calling +start+ in that subclass will not invoke the subclass's
1071 * +initialize+ method.
1072 */
1073
1074static VALUE
1075thread_start(VALUE klass, VALUE args)
1076{
1077 struct thread_create_params params = {
1078 .type = thread_invoke_type_proc,
1079 .args = args,
1080 .proc = rb_block_proc(),
1081 };
1082 return thread_create_core(rb_thread_alloc(klass), &params);
1083}
1084
1085static VALUE
1086threadptr_invoke_proc_location(rb_thread_t *th)
1087{
1088 if (th->invoke_type == thread_invoke_type_proc) {
1089 return rb_proc_location(th->invoke_arg.proc.proc);
1090 }
1091 else {
1092 return Qnil;
1093 }
1094}
1095
1096/* :nodoc: */
1097static VALUE
1098thread_initialize(VALUE thread, VALUE args)
1099{
1100 rb_thread_t *th = rb_thread_ptr(thread);
1101
1102 if (!rb_block_given_p()) {
1103 rb_raise(rb_eThreadError, "must be called with a block");
1104 }
1105 else if (th->invoke_type != thread_invoke_type_none) {
1106 VALUE loc = threadptr_invoke_proc_location(th);
1107 if (!NIL_P(loc)) {
1108 rb_raise(rb_eThreadError,
1109 "already initialized thread - %"PRIsVALUE":%"PRIsVALUE,
1110 RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1));
1111 }
1112 else {
1113 rb_raise(rb_eThreadError, "already initialized thread");
1114 }
1115 }
1116 else {
1117 struct thread_create_params params = {
1118 .type = thread_invoke_type_proc,
1119 .args = args,
1120 .proc = rb_block_proc(),
1121 };
1122 return thread_create_core(thread, &params);
1123 }
1124}
1125
1126VALUE
1127rb_thread_create(VALUE (*fn)(void *), void *arg)
1128{
1129 struct thread_create_params params = {
1130 .type = thread_invoke_type_func,
1131 .fn = fn,
1132 .args = (VALUE)arg,
1133 };
1134 return thread_create_core(rb_thread_alloc(rb_cThread), &params);
1135}
1136
1137static VALUE
1138create_ractor_alloc_thread(rb_ractor_t *r, rb_ractor_t *cr, rb_execution_context_t *ec)
1139{
1140 /* Build the child's main Thread and root Fiber wrappers in the child's objspace,
1141 * so the thread is made of objects it owns. Hand that objspace down rather than
1142 * pointing cr->objspace at it: threads holding no GVL read that slot to charge
1143 * their frees, and one of them must never be sent to a heap a stillborn child is
1144 * about to free.
1145 *
1146 * The child's objspace is not in vm->ractor.set yet, so cover it through its
1147 * creator before the first allocation and keep the cover until vm_insert_ractor
1148 * clears it under the VM lock. One slot suffices: one Ractor creates children
1149 * serially. */
1150 void *const child_objspace = r->objspace;
1151 volatile VALUE thval = Qundef;
1152 const bool multi_objspace = rb_gc_multi_objspace_p();
1153 enum ruby_tag_type alloc_state = TAG_NONE;
1154 RB_VM_LOCKING() {
1155 if (multi_objspace) {
1156 RUBY_ASSERT(cr->creating_child_objspace == NULL);
1157 cr->creating_child_objspace = child_objspace;
1158 }
1159 /* Suppress the child's GC, not the creator's: a cycle here would collect a
1160 * half-built child. Single allocations; this costs only a little growth. */
1161 VALUE gc_was_disabled = rb_gc_objspace_disable_no_rest(child_objspace);
1162 /* The alloc can raise NoMemoryError; a longjmp here would skip the unlock of
1163 * RB_VM_LOCKING, so catch and rethrow outside. */
1164 EC_PUSH_TAG(ec);
1165 if ((alloc_state = EC_EXEC_TAG()) == TAG_NONE) {
1166 thval = rb_thread_alloc_in_objspace(rb_cThread, child_objspace);
1167 }
1168 EC_POP_TAG();
1169 if (gc_was_disabled == Qfalse) rb_gc_objspace_enable(child_objspace);
1170 if (alloc_state != TAG_NONE) {
1171 /* Drop the cover and park the objspace in this same section: between two of
1172 * them another Ractor's global GC would find a populated objspace that is
1173 * neither covered nor a zombie. */
1174 if (multi_objspace) cr->creating_child_objspace = NULL;
1175 if (r->objspace) {
1176 rb_gc_objspace_disown(r->objspace);
1177 r->objspace = NULL;
1178 }
1179 }
1180 }
1181 if (alloc_state != TAG_NONE) EC_JUMP_TAG(ec, alloc_state);
1182 return thval;
1183}
1184
1185VALUE
1186rb_thread_create_ractor(rb_ractor_t *r, VALUE args, VALUE proc)
1187{
1188 struct thread_create_params params = {
1189 .type = thread_invoke_type_ractor_proc,
1190 .g = r,
1191 .args = args,
1192 .proc = proc,
1193 };
1194
1195 rb_ractor_t *cr = GET_RACTOR();
1196 rb_execution_context_t *ec = GET_EC();
1197
1198 VALUE thval = create_ractor_alloc_thread(r, cr, ec);
1199
1200 /* Creation can still fail before vm_insert_ractor (an IsolationError, say), and a
1201 * left-over cover would enumerate the dead child's objspace twice and dangle after
1202 * the merge: on failure hand the objspace to zombie_objspaces under the VM lock,
1203 * drop the cover, NULL r->objspace. */
1204 enum ruby_tag_type state;
1205 VALUE thret = Qundef;
1206 EC_PUSH_TAG(ec);
1207 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
1208 thret = thread_create_core(thval, &params);
1209 }
1210 EC_POP_TAG();
1211 if (state != TAG_NONE) {
1212 RB_VM_LOCKING() {
1213 if (cr->creating_child_objspace == r->objspace) {
1214 cr->creating_child_objspace = NULL;
1215 }
1216 if (r->objspace) {
1217 rb_gc_objspace_disown(r->objspace);
1218 r->objspace = NULL;
1219 }
1220 }
1221 EC_JUMP_TAG(ec, state);
1222 }
1223 return thret;
1224}
1225
1227struct join_arg {
1228 struct rb_waiting_list *waiter;
1229 rb_thread_t *target;
1230 VALUE timeout;
1231 rb_hrtime_t *limit;
1232};
1233
1234static VALUE
1235remove_from_join_list(VALUE arg)
1236{
1237 struct join_arg *p = (struct join_arg *)arg;
1238 rb_thread_t *target_thread = p->target;
1239
1240 if (target_thread->status != THREAD_KILLED) {
1241 struct rb_waiting_list **join_list = &target_thread->join_list;
1242
1243 while (*join_list) {
1244 if (*join_list == p->waiter) {
1245 *join_list = (*join_list)->next;
1246 break;
1247 }
1248
1249 join_list = &(*join_list)->next;
1250 }
1251 }
1252
1253 return Qnil;
1254}
1255
1256static int
1257thread_finished(rb_thread_t *th)
1258{
1259 return th->status == THREAD_KILLED || !UNDEF_P(th->value);
1260}
1261
1262static VALUE
1263thread_join_sleep(VALUE arg)
1264{
1265 struct join_arg *p = (struct join_arg *)arg;
1266 rb_thread_t *target_th = p->target, *th = p->waiter->thread;
1267 rb_hrtime_t end = 0, *limit = p->limit;
1268
1269 if (limit) {
1270 end = rb_hrtime_add(*limit, rb_hrtime_now());
1271 }
1272
1273 while (!thread_finished(target_th)) {
1275
1276 if (!limit) {
1277 if (scheduler != Qnil) {
1278 rb_fiber_scheduler_block(scheduler, target_th->self, Qnil);
1279 }
1280 else {
1281 sleep_forever(th, SLEEP_DEADLOCKABLE | SLEEP_ALLOW_SPURIOUS | SLEEP_NO_CHECKINTS);
1282 }
1283 }
1284 else {
1285 if (hrtime_update_expire(limit, end)) {
1286 RUBY_DEBUG_LOG("timeout target_th:%u", rb_th_serial(target_th));
1287 return Qfalse;
1288 }
1289
1290 if (scheduler != Qnil) {
1291 VALUE timeout = rb_float_new(hrtime2double(*limit));
1292 rb_fiber_scheduler_block(scheduler, target_th->self, timeout);
1293 }
1294 else {
1295 th->status = THREAD_STOPPED;
1296 native_sleep(th, limit);
1297 }
1298 }
1299 RUBY_VM_CHECK_INTS_BLOCKING(th->ec);
1300 th->status = THREAD_RUNNABLE;
1301
1302 RUBY_DEBUG_LOG("interrupted target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE));
1303 }
1304
1305 return Qtrue;
1306}
1307
1308static VALUE
1309thread_join(rb_thread_t *target_th, VALUE timeout, rb_hrtime_t *limit)
1310{
1311 rb_execution_context_t *ec = GET_EC();
1312 rb_thread_t *th = ec->thread_ptr;
1313 rb_fiber_t *fiber = ec->fiber_ptr;
1314
1315 if (th == target_th) {
1316 rb_raise(rb_eThreadError, "Target thread must not be current thread");
1317 }
1318
1319 if (th->ractor->threads.main == target_th) {
1320 rb_raise(rb_eThreadError, "Target thread must not be main thread");
1321 }
1322
1323 RUBY_DEBUG_LOG("target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE));
1324
1325 if (target_th->status != THREAD_KILLED) {
1326 struct rb_waiting_list waiter;
1327 waiter.next = target_th->join_list;
1328 waiter.thread = th;
1329 waiter.fiber = rb_fiberptr_blocking(fiber) ? NULL : fiber;
1330 target_th->join_list = &waiter;
1331
1332 struct join_arg arg;
1333 arg.waiter = &waiter;
1334 arg.target = target_th;
1335 arg.timeout = timeout;
1336 arg.limit = limit;
1337
1338 if (!rb_ensure(thread_join_sleep, (VALUE)&arg, remove_from_join_list, (VALUE)&arg)) {
1339 return Qnil;
1340 }
1341 }
1342
1343 RUBY_DEBUG_LOG("success target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE));
1344
1345 if (target_th->ec->errinfo != Qnil) {
1346 VALUE err = target_th->ec->errinfo;
1347
1348 if (FIXNUM_P(err)) {
1349 switch (err) {
1350 case INT2FIX(TAG_FATAL):
1351 RUBY_DEBUG_LOG("terminated target_th:%u status:%s", rb_th_serial(target_th), thread_status_name(target_th, TRUE));
1352
1353 /* OK. killed. */
1354 break;
1355 default:
1356 if (err == RUBY_FATAL_FIBER_KILLED) { // not integer constant so can't be a case expression
1357 // root fiber killed in non-main thread
1358 break;
1359 }
1360 rb_bug("thread_join: Fixnum (%d) should not reach here.", FIX2INT(err));
1361 }
1362 }
1363 else if (THROW_DATA_P(target_th->ec->errinfo)) {
1364 rb_bug("thread_join: THROW_DATA should not reach here.");
1365 }
1366 else {
1367 /* normal exception */
1368 rb_exc_raise(err);
1369 }
1370 }
1371 return target_th->self;
1372}
1373
1374/*
1375 * call-seq:
1376 * thr.join -> thr
1377 * thr.join(limit) -> thr
1378 *
1379 * The calling thread will suspend execution and run this +thr+.
1380 *
1381 * Does not return until +thr+ exits or until the given +limit+ seconds have
1382 * passed.
1383 *
1384 * If the time limit expires, +nil+ will be returned, otherwise +thr+ is
1385 * returned.
1386 *
1387 * Any threads not joined will be killed when the main program exits.
1388 *
1389 * If +thr+ had previously raised an exception and the ::abort_on_exception or
1390 * $DEBUG flags are not set, (so the exception has not yet been processed), it
1391 * will be processed at this time.
1392 *
1393 * a = Thread.new { print "a"; sleep(10); print "b"; print "c" }
1394 * x = Thread.new { print "x"; Thread.pass; print "y"; print "z" }
1395 * x.join # Let thread x finish, thread a will be killed on exit.
1396 * #=> "axyz"
1397 *
1398 * The following example illustrates the +limit+ parameter.
1399 *
1400 * y = Thread.new { 4.times { sleep 0.1; puts 'tick... ' }}
1401 * puts "Waiting" until y.join(0.15)
1402 *
1403 * This will produce:
1404 *
1405 * tick...
1406 * Waiting
1407 * tick...
1408 * Waiting
1409 * tick...
1410 * tick...
1411 */
1412
1413static VALUE
1414thread_join_m(int argc, VALUE *argv, VALUE self)
1415{
1416 VALUE timeout = Qnil;
1417 rb_hrtime_t rel = 0, *limit = 0;
1418
1419 if (rb_check_arity(argc, 0, 1)) {
1420 timeout = argv[0];
1421 }
1422
1423 // Convert the timeout eagerly, so it's always converted and deterministic
1424 /*
1425 * This supports INFINITY and negative values, so we can't use
1426 * rb_time_interval right now...
1427 */
1428 if (NIL_P(timeout)) {
1429 /* unlimited */
1430 }
1431 else if (FIXNUM_P(timeout)) {
1432 rel = rb_sec2hrtime(NUM2TIMET(timeout));
1433 limit = &rel;
1434 }
1435 else {
1436 limit = double2hrtime(&rel, rb_num2dbl(timeout));
1437 }
1438
1439 return thread_join(rb_thread_ptr(self), timeout, limit);
1440}
1441
1442/*
1443 * call-seq:
1444 * thr.value -> obj
1445 *
1446 * Waits for +thr+ to complete, using #join, and returns its value or raises
1447 * the exception which terminated the thread.
1448 *
1449 * a = Thread.new { 2 + 2 }
1450 * a.value #=> 4
1451 *
1452 * b = Thread.new { raise 'something went wrong' }
1453 * b.value #=> RuntimeError: something went wrong
1454 */
1455
1456static VALUE
1457thread_value(VALUE self)
1458{
1459 rb_thread_t *th = rb_thread_ptr(self);
1460 thread_join(th, Qnil, 0);
1461 if (UNDEF_P(th->value)) {
1462 // If the thread is dead because we forked th->value is still Qundef.
1463 return Qnil;
1464 }
1465 return th->value;
1466}
1467
1468/*
1469 * Thread Scheduling
1470 */
1471
1472static void
1473getclockofday(struct timespec *ts)
1474{
1475#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
1476 if (clock_gettime(CLOCK_MONOTONIC, ts) == 0)
1477 return;
1478#endif
1479 rb_timespec_now(ts);
1480}
1481
1482/*
1483 * Don't inline this, since library call is already time consuming
1484 * and we don't want "struct timespec" on stack too long for GC
1485 */
1486NOINLINE(rb_hrtime_t rb_hrtime_now(void));
1487rb_hrtime_t
1488rb_hrtime_now(void)
1489{
1490 struct timespec ts;
1491
1492 getclockofday(&ts);
1493 return rb_timespec2hrtime(&ts);
1494}
1495
1496/*
1497 * at least gcc 7.2 and 7.3 complains about "rb_hrtime_t end"
1498 * being uninitialized, maybe other versions, too.
1499 */
1500COMPILER_WARNING_PUSH
1501#if defined(__GNUC__) && __GNUC__ == 7 && __GNUC_MINOR__ <= 3
1502COMPILER_WARNING_IGNORED(-Wmaybe-uninitialized)
1503#endif
1504#ifndef PRIu64
1505#define PRIu64 PRI_64_PREFIX "u"
1506#endif
1507/*
1508 * @end is the absolute time when @ts is set to expire
1509 * Returns true if @end has past
1510 * Updates @ts and returns false otherwise
1511 */
1512static int
1513hrtime_update_expire(rb_hrtime_t *timeout, const rb_hrtime_t end)
1514{
1515 rb_hrtime_t now = rb_hrtime_now();
1516
1517 if (now > end) return 1;
1518
1519 RUBY_DEBUG_LOG("%"PRIu64" > %"PRIu64"", (uint64_t)end, (uint64_t)now);
1520
1521 *timeout = end - now;
1522 return 0;
1523}
1524COMPILER_WARNING_POP
1525
1526static int sleep_hrtime_until(rb_thread_t *th, rb_hrtime_t end, unsigned int fl);
1527
1528static int
1529sleep_hrtime(rb_thread_t *th, rb_hrtime_t rel, unsigned int fl)
1530{
1531 return sleep_hrtime_until(th, rb_hrtime_add(rb_hrtime_now(), rel), fl);
1532}
1533
1534static int
1535sleep_hrtime_until(rb_thread_t *th, rb_hrtime_t end, unsigned int fl)
1536{
1537 enum rb_thread_status prev_status = th->status;
1538 int woke;
1539 rb_hrtime_t rel = rb_hrtime_sub(end, rb_hrtime_now());
1540
1541 th->status = THREAD_STOPPED;
1542 RUBY_VM_CHECK_INTS_BLOCKING(th->ec);
1543 while (th->status == THREAD_STOPPED) {
1544 native_sleep(th, &rel);
1545 woke = vm_check_ints_blocking(th->ec);
1546 if (woke && !(fl & SLEEP_SPURIOUS_CHECK))
1547 break;
1548 if (hrtime_update_expire(&rel, end))
1549 break;
1550 woke = 1;
1551 }
1552 th->status = prev_status;
1553 return woke;
1554}
1555
1556static void
1557sleep_forever(rb_thread_t *th, unsigned int fl)
1558{
1559 enum rb_thread_status prev_status = th->status;
1560 enum rb_thread_status status;
1561 int woke;
1562
1563 status = fl & SLEEP_DEADLOCKABLE ? THREAD_STOPPED_FOREVER : THREAD_STOPPED;
1564 th->status = status;
1565
1566 if (!(fl & SLEEP_NO_CHECKINTS)) RUBY_VM_CHECK_INTS_BLOCKING(th->ec);
1567
1568 while (th->status == status) {
1569 if (fl & SLEEP_DEADLOCKABLE) {
1570 rb_ractor_sleeper_threads_inc(th->ractor);
1571 rb_check_deadlock(th->ractor);
1572 }
1573 {
1574 native_sleep(th, 0);
1575 }
1576 if (fl & SLEEP_DEADLOCKABLE) {
1577 rb_ractor_sleeper_threads_dec(th->ractor);
1578 }
1579 if (fl & SLEEP_ALLOW_SPURIOUS) {
1580 break;
1581 }
1582
1583 woke = vm_check_ints_blocking(th->ec);
1584
1585 if (woke && !(fl & SLEEP_SPURIOUS_CHECK)) {
1586 break;
1587 }
1588 }
1589 th->status = prev_status;
1590}
1591
1592void
1594{
1595 RUBY_DEBUG_LOG("forever");
1596 sleep_forever(GET_THREAD(), SLEEP_SPURIOUS_CHECK);
1597}
1598
1599void
1601{
1602 RUBY_DEBUG_LOG("deadly");
1603 sleep_forever(GET_THREAD(), SLEEP_DEADLOCKABLE|SLEEP_SPURIOUS_CHECK);
1604}
1605
1606static void
1607rb_thread_sleep_deadly_allow_spurious_wakeup(VALUE blocker, VALUE timeout, rb_hrtime_t end)
1608{
1609 rb_thread_t *th = GET_THREAD();
1611 if (scheduler != Qnil) {
1612 rb_fiber_scheduler_block(scheduler, blocker, timeout);
1613 }
1614 else {
1615 RUBY_DEBUG_LOG("...");
1616 if (end) {
1617 sleep_hrtime_until(th, end, SLEEP_SPURIOUS_CHECK);
1618 }
1619 else {
1620 sleep_forever(th, SLEEP_DEADLOCKABLE);
1621 }
1622 }
1623}
1624
1625void
1626rb_thread_wait_for(struct timeval time)
1627{
1628 rb_thread_t *th = GET_THREAD();
1629
1630 sleep_hrtime(th, rb_timeval2hrtime(&time), SLEEP_SPURIOUS_CHECK);
1631}
1632
1633void
1634rb_ec_check_ints(rb_execution_context_t *ec)
1635{
1636 RUBY_VM_CHECK_INTS_BLOCKING(ec);
1637}
1638
1639/*
1640 * CAUTION: This function causes thread switching.
1641 * rb_thread_check_ints() check ruby's interrupts.
1642 * some interrupt needs thread switching/invoke handlers,
1643 * and so on.
1644 */
1645
1646void
1648{
1649 rb_ec_check_ints(GET_EC());
1650}
1651
1652/*
1653 * Hidden API for tcl/tk wrapper.
1654 * There is no guarantee to perpetuate it.
1655 */
1656int
1657rb_thread_check_trap_pending(void)
1658{
1659 return rb_signal_buff_size() != 0;
1660}
1661
1662/* This function can be called in blocking region. */
1665{
1666 return (int)RUBY_VM_INTERRUPTED(rb_thread_ptr(thval)->ec);
1667}
1668
1669void
1670rb_thread_sleep(int sec)
1671{
1673}
1674
1675static void
1676rb_thread_schedule_limits(uint32_t limits_us)
1677{
1678 if (!rb_thread_alone()) {
1679 rb_thread_t *th = GET_THREAD();
1680 RUBY_DEBUG_LOG("us:%u", (unsigned int)limits_us);
1681
1682 if (th->running_time_us >= limits_us) {
1683 RUBY_DEBUG_LOG("switch %s", "start");
1684
1685 RB_VM_SAVE_MACHINE_CONTEXT(th);
1686 thread_sched_yield(TH_SCHED(th), th);
1687 rb_ractor_thread_switch(th->ractor, th, true);
1688
1689 RUBY_DEBUG_LOG("switch %s", "done");
1690 }
1691 }
1692}
1693
1694void
1696{
1697 rb_thread_schedule_limits(0);
1698 RUBY_VM_CHECK_INTS(GET_EC());
1699}
1700
1701/* blocking region */
1702
1703static inline int
1704blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region,
1705 rb_unblock_function_t *ubf, void *arg, int flags)
1706{
1707#ifdef RUBY_ASSERT_CRITICAL_SECTION
1708 VM_ASSERT(th->ec->assert_critical_section_entered == 0);
1709#endif
1710 VM_ASSERT(th == GET_THREAD());
1711
1712 region->prev_status = th->status;
1713 if (unblock_function_set(th, ubf, arg, flags)) {
1714 th->blocking_region_buffer = region;
1715 th->status = THREAD_STOPPED;
1716 rb_ractor_blocking_threads_inc(th->ractor, __FILE__, __LINE__);
1717
1718 RUBY_DEBUG_LOG("thread_id:%p", (void *)th->nt->thread_id);
1719 return TRUE;
1720 }
1721 else {
1722 return FALSE;
1723 }
1724}
1725
1726static inline void
1727blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region)
1728{
1729 /* entry to ubf_list still permitted at this point, make it impossible: */
1730 unblock_function_clear(th);
1731 /* entry to ubf_list impossible at this point, so unregister is safe: */
1732 unregister_ubf_list(th);
1733
1734 thread_sched_to_running(TH_SCHED(th), th);
1735 rb_ractor_thread_switch(th->ractor, th, false);
1736
1737 th->blocking_region_buffer = 0;
1738 rb_ractor_blocking_threads_dec(th->ractor, __FILE__, __LINE__);
1739 if (th->status == THREAD_STOPPED) {
1740 th->status = region->prev_status;
1741 }
1742
1743 RUBY_DEBUG_LOG("end");
1744
1745#ifndef _WIN32
1746 // GET_THREAD() clears WSAGetLastError()
1747 VM_ASSERT(th == GET_THREAD());
1748#endif
1749}
1750
1751/*
1752 * Resolve sentinel unblock function values to their actual function pointers
1753 * and appropriate data2 values. This centralizes the logic for handling
1754 * RUBY_UBF_IO and RUBY_UBF_PROCESS sentinel values.
1755 *
1756 * @param unblock_function Pointer to unblock function pointer (modified in place)
1757 * @param data2 Pointer to data2 pointer (modified in place)
1758 * @param thread Thread context for resolving data2 when needed
1759 * @return true if sentinel values were resolved, false otherwise
1760 */
1761bool
1762rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread)
1763{
1764 rb_unblock_function_t *ubf = *unblock_function;
1765
1766 if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) {
1767 *unblock_function = ubf_select;
1768 *data2 = thread;
1769 return true;
1770 }
1771 return false;
1772}
1773
1774void *
1775rb_nogvl(void *(*func)(void *), void *data1,
1776 rb_unblock_function_t *ubf, void *data2,
1777 int flags)
1778{
1779 rb_execution_context_t *ec = GET_EC();
1780 rb_thread_t *th = rb_ec_thread_ptr(ec);
1781
1782 if (
1783 (flags & RB_NOGVL_PENDING_INTR_FAIL) &&
1784 !rb_threadptr_pending_interrupt_empty_p(th)
1785 ) {
1786 /* Match the in-region skip path, which leaves errno at saved_errno (0)
1787 * because the function was never called. */
1788 rb_errno_set(0);
1789 return 0;
1790 }
1791
1792 if (flags & RB_NOGVL_OFFLOAD_SAFE) {
1793 VALUE scheduler = rb_fiber_scheduler_current();
1794 if (scheduler != Qnil) {
1796
1797 VALUE result = rb_fiber_scheduler_blocking_operation_wait(scheduler, func, data1, ubf, data2, flags, &state);
1798
1799 if (!UNDEF_P(result)) {
1800 rb_errno_set(state.saved_errno);
1801 return state.result;
1802 }
1803 }
1804 }
1805
1806 void *val = 0;
1807 rb_vm_t *vm = rb_ec_vm_ptr(ec);
1808 bool is_main_thread = vm->ractor.main_thread == th;
1809 int saved_errno = 0;
1810
1811 bool sentinel_ubf = rb_thread_resolve_unblock_function(&ubf, &data2, th);
1812
1813 if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) {
1814 // ubf_select, which the sentinel ubfs resolve to, takes ubf_list_lock
1815 // and the ractor scheduler lock: not async-signal-safe, whatever the
1816 // caller claims.
1817 if ((flags & RB_NOGVL_UBF_ASYNC_SAFE) && !sentinel_ubf) {
1818 vm->ubf_async_safe = 1;
1819 }
1820 }
1821
1822 rb_vm_t *volatile saved_vm = vm;
1823 BLOCKING_REGION(th, {
1824 val = func(data1);
1825 saved_errno = rb_errno();
1826 }, ubf, data2, flags);
1827 vm = saved_vm;
1828
1829 if (is_main_thread) vm->ubf_async_safe = 0;
1830
1831 if ((flags & RB_NOGVL_INTR_FAIL) == 0) {
1832 RUBY_VM_CHECK_INTS_BLOCKING(ec);
1833 }
1834
1835 rb_errno_set(saved_errno);
1836
1837 return val;
1838}
1839
1840/*
1841 * rb_thread_call_without_gvl - permit concurrent/parallel execution.
1842 * rb_thread_call_without_gvl2 - permit concurrent/parallel execution
1843 * without interrupt process.
1844 *
1845 * rb_thread_call_without_gvl() does:
1846 * (1) Check interrupts.
1847 * (2) release GVL.
1848 * Other Ruby threads may run in parallel.
1849 * (3) call func with data1
1850 * (4) acquire GVL.
1851 * Other Ruby threads can not run in parallel any more.
1852 * (5) Check interrupts.
1853 *
1854 * rb_thread_call_without_gvl2() does:
1855 * (1) Check interrupt and return if interrupted.
1856 * (2) release GVL.
1857 * (3) call func with data1 and a pointer to the flags.
1858 * (4) acquire GVL.
1859 *
1860 * If another thread interrupts this thread (Thread#kill, signal delivery,
1861 * VM-shutdown request, and so on), `ubf()' is called (`ubf()' means
1862 * "un-blocking function"). `ubf()' should interrupt `func()' execution by
1863 * toggling a cancellation flag, canceling the invocation of a call inside
1864 * `func()' or similar. Note that `ubf()' may not be called with the GVL.
1865 *
1866 * There are built-in ubfs and you can specify these ubfs:
1867 *
1868 * * RUBY_UBF_IO: ubf for IO operation
1869 * * RUBY_UBF_PROCESS: ubf for process operation
1870 *
1871 * However, we can not guarantee our built-in ubfs interrupt your `func()'
1872 * correctly. Be careful to use rb_thread_call_without_gvl(). If you don't
1873 * provide proper ubf(), your program will not stop for Control+C or other
1874 * shutdown events.
1875 *
1876 * "Check interrupts" on above list means checking asynchronous
1877 * interrupt events (such as Thread#kill, signal delivery, VM-shutdown
1878 * request, and so on) and calling corresponding procedures
1879 * (such as `trap' for signals, raise an exception for Thread#raise).
1880 * If `func()' finished and received interrupts, you may skip interrupt
1881 * checking. For example, assume the following func() it reads data from file.
1882 *
1883 * read_func(...) {
1884 * // (a) before read
1885 * read(buffer); // (b) reading
1886 * // (c) after read
1887 * }
1888 *
1889 * If an interrupt occurs at (a) or (b), then `ubf()' cancels this
1890 * `read_func()' and interrupts are checked. However, if an interrupt occurs
1891 * at (c), after *read* operation is completed, checking interrupts is harmful
1892 * because it causes irrevocable side-effect, the read data will vanish. To
1893 * avoid such problem, the `read_func()' should be used with
1894 * `rb_thread_call_without_gvl2()'.
1895 *
1896 * If `rb_thread_call_without_gvl2()' detects interrupt, it returns
1897 * immediately. This function does not show when the execution was interrupted.
1898 * For example, there are 4 possible timing (a), (b), (c) and before calling
1899 * read_func(). You need to record progress of a read_func() and check
1900 * the progress after `rb_thread_call_without_gvl2()'. You may need to call
1901 * `rb_thread_check_ints()' correctly or your program can not process proper
1902 * process such as `trap' and so on.
1903 *
1904 * NOTE: You can not execute most of Ruby C API and touch Ruby
1905 * objects in `func()' and `ubf()', including raising an
1906 * exception, because current thread doesn't acquire GVL
1907 * (it causes synchronization problems). If you need to
1908 * call ruby functions either use rb_thread_call_with_gvl()
1909 * or read source code of C APIs and confirm safety by
1910 * yourself.
1911 *
1912 * NOTE: In short, this API is difficult to use safely. I recommend you
1913 * use other ways if you have. We lack experiences to use this API.
1914 * Please report your problem related on it.
1915 *
1916 * NOTE: Releasing GVL and re-acquiring GVL may be expensive operations
1917 * for a short running `func()'. Be sure to benchmark and use this
1918 * mechanism when `func()' consumes enough time.
1919 *
1920 * Safe C API:
1921 * * rb_thread_interrupted() - check interrupt flag
1922 * * ruby_xmalloc(), ruby_xrealloc(), ruby_xfree() -
1923 * they will work without GVL, and may acquire GVL when GC is needed.
1924 */
1925void *
1926rb_thread_call_without_gvl2(void *(*func)(void *), void *data1,
1927 rb_unblock_function_t *ubf, void *data2)
1928{
1929 return rb_nogvl(func, data1, ubf, data2, RB_NOGVL_INTR_FAIL);
1930}
1931
1932void *
1933rb_thread_call_without_gvl(void *(*func)(void *data), void *data1,
1934 rb_unblock_function_t *ubf, void *data2)
1935{
1936 return rb_nogvl(func, data1, ubf, data2, 0);
1937}
1938
1939static int
1940waitfd_to_waiting_flag(int wfd_event)
1941{
1942 return wfd_event << 1;
1943}
1944
1945static struct ccan_list_head *
1946rb_io_blocking_operations(struct rb_io *io)
1947{
1948 rb_serial_t fork_generation = GET_VM()->fork_gen;
1949
1950 // On fork, all existing entries in this list (which are stack allocated) become invalid.
1951 // Therefore, we re-initialize the list which clears it.
1952 if (io->fork_generation != fork_generation) {
1953 ccan_list_head_init(&io->blocking_operations);
1954 io->fork_generation = fork_generation;
1955 }
1956
1957 return &io->blocking_operations;
1958}
1959
1960/*
1961 * Registers a blocking operation for an IO object. This is used to track all threads and fibers
1962 * that are currently blocked on this IO for reading, writing or other operations.
1963 *
1964 * When the IO is closed, all blocking operations will be notified via rb_fiber_scheduler_fiber_interrupt
1965 * for fibers with a scheduler, or via rb_threadptr_interrupt for threads without a scheduler.
1966 *
1967 * @parameter io The IO object on which the operation will block
1968 * @parameter blocking_operation The operation details including the execution context that will be blocked
1969 */
1970static void
1971rb_io_blocking_operation_enter(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation)
1972{
1973 ccan_list_add(rb_io_blocking_operations(io), &blocking_operation->list);
1974}
1975
1976static void
1977rb_io_blocking_operation_pop(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation)
1978{
1979 ccan_list_del(&blocking_operation->list);
1980}
1983 struct rb_io *io;
1984 struct rb_io_blocking_operation *blocking_operation;
1985};
1986
1987static VALUE
1988io_blocking_operation_exit(VALUE _arguments)
1989{
1990 struct io_blocking_operation_arguments *arguments = (void*)_arguments;
1991 struct rb_io_blocking_operation *blocking_operation = arguments->blocking_operation;
1992
1993 rb_io_blocking_operation_pop(arguments->io, blocking_operation);
1994
1995 rb_io_t *io = arguments->io;
1996 rb_thread_t *thread = io->closing_ec->thread_ptr;
1997 rb_fiber_t *fiber = io->closing_ec->fiber_ptr;
1998
1999 if (thread->scheduler != Qnil) {
2000 // This can cause spurious wakeups...
2001 rb_fiber_scheduler_unblock(thread->scheduler, io->self, rb_fiberptr_self(fiber));
2002 }
2003 else {
2004 rb_thread_wakeup(thread->self);
2005 }
2006
2007 return Qnil;
2008}
2009
2010/*
2011 * Called when a blocking operation completes or is interrupted. Removes the operation from
2012 * the IO's blocking_operations list and wakes up any waiting threads/fibers.
2013 *
2014 * If there's a wakeup_mutex (meaning an IO close is in progress), synchronizes the cleanup
2015 * through that mutex to ensure proper coordination with the closing thread.
2016 *
2017 * @parameter io The IO object the operation was performed on
2018 * @parameter blocking_operation The completed operation to clean up
2019 */
2020static void
2021rb_io_blocking_operation_exit(struct rb_io *io, struct rb_io_blocking_operation *blocking_operation)
2022{
2023 VALUE wakeup_mutex = io->wakeup_mutex;
2024
2025 // Indicate that the blocking operation is no longer active:
2026 blocking_operation->ec = NULL;
2027
2028 if (!NIL_P(blocking_operation->scheduler_interrupt_target)) {
2029 rb_fiber_scheduler_interrupt_target_invalidate(blocking_operation->scheduler_interrupt_target);
2030 }
2031
2032 if (RB_TEST(wakeup_mutex)) {
2033 struct io_blocking_operation_arguments arguments = {
2034 .io = io,
2035 .blocking_operation = blocking_operation
2036 };
2037
2038 rb_mutex_synchronize(wakeup_mutex, io_blocking_operation_exit, (VALUE)&arguments);
2039 }
2040 else {
2041 // If there's no wakeup_mutex, we can safely remove the operation directly:
2042 rb_io_blocking_operation_pop(io, blocking_operation);
2043 }
2044}
2045
2046static VALUE
2047rb_thread_io_blocking_operation_ensure(VALUE _argument)
2048{
2049 struct io_blocking_operation_arguments *arguments = (void*)_argument;
2050
2051 rb_io_blocking_operation_exit(arguments->io, arguments->blocking_operation);
2052
2053 return Qnil;
2054}
2057 VALUE (*function)(VALUE);
2058 VALUE argument;
2059 struct rb_io_blocking_operation *blocking_operation;
2060};
2061
2062static VALUE
2063rb_thread_io_blocking_operation_body(VALUE _arguments)
2064{
2065 struct thread_io_blocking_operation_arguments *arguments = (void *)_arguments;
2066 VALUE result = arguments->function(arguments->argument);
2067 VALUE target = arguments->blocking_operation->scheduler_interrupt_target;
2068
2069 if (!NIL_P(target)) {
2070 VALUE exception = rb_fiber_scheduler_interrupt_target_exception(target);
2071
2072 if (!NIL_P(exception)) {
2073 rb_exc_raise(exception);
2074 }
2075 }
2076
2077 return result;
2078}
2079
2080/*
2081 * Executes a function that performs a blocking IO operation, while properly tracking
2082 * the operation in the IO's blocking_operations list. This ensures proper cleanup
2083 * and interruption handling if the IO is closed while blocked.
2084 *
2085 * The operation is automatically removed from the blocking_operations list when the function
2086 * returns, whether normally or due to an exception.
2087 *
2088 * @parameter self The IO object
2089 * @parameter function The function to execute that will perform the blocking operation
2090 * @parameter argument The argument to pass to the function
2091 * @returns The result of the blocking operation function
2092 */
2093VALUE
2094rb_thread_io_blocking_operation(VALUE self, VALUE(*function)(VALUE), VALUE argument)
2095{
2096 struct rb_io *io;
2097 RB_IO_POINTER(self, io);
2098
2099 rb_execution_context_t *ec = GET_EC();
2100 struct rb_io_blocking_operation blocking_operation = {
2101 .ec = ec,
2102 .scheduler_interrupt_target = Qnil,
2103 };
2104 rb_io_blocking_operation_enter(io, &blocking_operation);
2105
2107 .io = io,
2108 .blocking_operation = &blocking_operation
2109 };
2110
2111 struct thread_io_blocking_operation_arguments arguments = {
2112 .function = function,
2113 .argument = argument,
2114 .blocking_operation = &blocking_operation,
2115 };
2116
2117 return rb_ensure(rb_thread_io_blocking_operation_body, (VALUE)&arguments, rb_thread_io_blocking_operation_ensure, (VALUE)&io_blocking_operation_arguments);
2118}
2119
2120static bool
2121thread_io_mn_schedulable(rb_thread_t *th, int events, const struct timeval *timeout)
2122{
2123#if defined(USE_MN_THREADS) && USE_MN_THREADS
2124 // RB_WAITFD_PRI has no thread_sched_waiting_* event: the scheduler would
2125 // register nothing and park the thread forever. POLLPRI works on the
2126 // blocking path.
2127 return !th_has_dedicated_nt(th) && (events || timeout) && th->blocking &&
2128 !(events & ~(RB_WAITFD_IN | RB_WAITFD_OUT));
2129#else
2130 return false;
2131#endif
2132}
2133
2134enum io_wait_result {
2135 io_wait_ready, // the MN scheduler waited and the fd is ready
2136 io_wait_timed_out, // the MN scheduler waited until the timeout expired
2137 io_wait_unhandled, // the MN scheduler did not wait; use the blocking path
2138 io_wait_interrupted, // the MN scheduler stopped for an interrupt; handle it and wait again
2139};
2140
2141// Wait for `fd` on the MN scheduler, if it can take this wait at all.
2142// `known_not_ready`: the caller just saw EAGAIN, so probing the fd would only
2143// repeat an answer we have. Callers with no preceding operation need the probe.
2144static enum io_wait_result
2145thread_io_wait_events(rb_thread_t *th, int fd, int events, const struct timeval *timeout,
2146 bool known_not_ready)
2147{
2148#if defined(USE_MN_THREADS) && USE_MN_THREADS
2149 if (thread_io_mn_schedulable(th, events, timeout)) {
2150 rb_hrtime_t rel, *prel;
2151
2152 if (timeout) {
2153 rel = rb_timeval2hrtime(timeout);
2154 prel = &rel;
2155 }
2156 else {
2157 prel = NULL;
2158 }
2159
2160 VM_ASSERT(prel || (events & (RB_WAITFD_IN | RB_WAITFD_OUT)));
2161
2162 enum thread_sched_waiting_flag flags = waitfd_to_waiting_flag(events);
2163 if (known_not_ready) flags |= thread_sched_waiting_io_force;
2164
2165 switch (thread_sched_wait_events(TH_SCHED(th), th, fd, flags, prel)) {
2166 case thread_sched_wait_event:
2167 return io_wait_ready;
2168 case thread_sched_wait_timeout:
2169 return io_wait_timed_out;
2170 case thread_sched_wait_unavailable:
2171 // Never waited: reporting "ready" here would fabricate readiness and
2172 // spin, so hand the wait back to the caller's blocking path.
2173 return io_wait_unhandled;
2174 case thread_sched_wait_interrupted:
2175 return io_wait_interrupted;
2176 }
2177 }
2178#endif // defined(USE_MN_THREADS) && USE_MN_THREADS
2179 return io_wait_unhandled;
2180}
2181
2182// assume read/write
2183static bool
2184blocking_call_retryable_p(int r, int eno)
2185{
2186 if (r != -1) return false;
2187
2188 switch (eno) {
2189 case EAGAIN:
2190#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
2191 case EWOULDBLOCK:
2192#endif
2193 return true;
2194 default:
2195 return false;
2196 }
2197}
2198
2199bool
2200rb_thread_mn_schedulable(VALUE thval)
2201{
2202 rb_thread_t *th = rb_thread_ptr(thval);
2203 return th->mn_schedulable;
2204}
2205
2206VALUE
2207rb_thread_io_blocking_call(struct rb_io* io, rb_blocking_function_t *func, void *data1, int events)
2208{
2209 rb_execution_context_t * volatile ec = GET_EC();
2210 rb_thread_t * volatile th = rb_ec_thread_ptr(ec);
2211
2212 RUBY_DEBUG_LOG("th:%u fd:%d ev:%d", rb_th_serial(th), io->fd, events);
2213
2214 volatile VALUE val = Qundef; /* shouldn't be used */
2215 volatile int saved_errno = 0;
2216 enum ruby_tag_type state;
2217 volatile bool prev_mn_schedulable = th->mn_schedulable;
2218 th->mn_schedulable = thread_io_mn_schedulable(th, events, NULL);
2219
2220 int fd = io->fd;
2221
2222 struct rb_io_blocking_operation blocking_operation = {
2223 .ec = ec,
2224 .scheduler_interrupt_target = Qnil,
2225 };
2226 rb_io_blocking_operation_enter(io, &blocking_operation);
2227
2228 {
2229 EC_PUSH_TAG(ec);
2230 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
2231 volatile enum ruby_tag_type saved_state = state; /* for BLOCKING_REGION */
2232 retry:
2233 BLOCKING_REGION(th, {
2234 // `errno` is only valid when there is an actual error - but we can't
2235 // extract that from the return value of `func` alone, so we clear any
2236 // prior `errno` value here so that we can later check if it was set by
2237 // `func` or not (as opposed to some previously set value).
2238 errno = 0;
2239 val = func(data1);
2240 saved_errno = errno;
2241 }, ubf_select, th, FALSE);
2242
2243 RUBY_ASSERT(th == rb_ec_thread_ptr(ec));
2244 if (events && blocking_call_retryable_p((int)val, saved_errno)) {
2245 // `func` just returned EAGAIN, so the fd is known not to be ready.
2246 // Any M:N outcome but unhandled (ready, interrupted, woken early) retries `func`.
2247 if (thread_io_wait_events(th, fd, events, NULL, true) != io_wait_unhandled) {
2248 RUBY_VM_CHECK_INTS_BLOCKING(ec);
2249 goto retry;
2250 }
2251 else if (th->mn_schedulable) {
2252 // Retrying now would spin and returning would leak EAGAIN to
2253 // Ruby, so wait the ordinary blocking way, then retry.
2254 rb_thread_wait_for_single_fd(th, fd, events, NULL);
2255 RUBY_VM_CHECK_INTS_BLOCKING(ec);
2256 goto retry;
2257 }
2258 }
2259
2260 RUBY_VM_CHECK_INTS_BLOCKING(ec);
2261
2262 state = saved_state;
2263 }
2264 EC_POP_TAG();
2265
2266 th = rb_ec_thread_ptr(ec);
2267 th->mn_schedulable = prev_mn_schedulable;
2268 }
2269
2270 rb_io_blocking_operation_exit(io, &blocking_operation);
2271
2272 if (state) {
2273 EC_JUMP_TAG(ec, state);
2274 }
2275
2276 // If the error was a timeout, we raise a specific exception for that:
2277 if (saved_errno == ETIMEDOUT) {
2278 rb_raise(rb_eIOTimeoutError, "Blocking operation timed out!");
2279 }
2280
2281 errno = saved_errno;
2282
2283 return val;
2284}
2285
2286VALUE
2287rb_thread_io_blocking_region(struct rb_io *io, rb_blocking_function_t *func, void *data1)
2288{
2289 return rb_thread_io_blocking_call(io, func, data1, 0);
2290}
2291
2292/*
2293 * rb_thread_call_with_gvl - re-enter the Ruby world after GVL release.
2294 *
2295 * After releasing GVL using
2296 * rb_thread_call_without_gvl() you can not access Ruby values or invoke
2297 * methods. If you need to access Ruby you must use this function
2298 * rb_thread_call_with_gvl().
2299 *
2300 * This function rb_thread_call_with_gvl() does:
2301 * (1) acquire GVL.
2302 * (2) call passed function `func'.
2303 * (3) release GVL.
2304 * (4) return a value which is returned at (2).
2305 *
2306 * NOTE: You should not return Ruby object at (2) because such Object
2307 * will not be marked.
2308 *
2309 * NOTE: If an exception is raised in `func', this function DOES NOT
2310 * protect (catch) the exception. If you have any resources
2311 * which should free before throwing exception, you need use
2312 * rb_protect() in `func' and return a value which represents
2313 * exception was raised.
2314 *
2315 * NOTE: This function should not be called by a thread which was not
2316 * created as Ruby thread (created by Thread.new or so). In other
2317 * words, this function *DOES NOT* associate or convert a NON-Ruby
2318 * thread to a Ruby thread.
2319 *
2320 * NOTE: If this thread has already acquired the GVL, then the method call
2321 * is performed without acquiring or releasing the GVL (from Ruby 4.0).
2322 */
2323void *
2324rb_thread_call_with_gvl(void *(*func)(void *), void *data1)
2325{
2326 rb_thread_t *th = ruby_thread_from_native();
2327 struct rb_blocking_region_buffer *brb;
2328 struct rb_unblock_callback prev_unblock;
2329 void *r;
2330
2331 if (th == 0) {
2332 /* Error has occurred, but we can't use rb_bug()
2333 * because this thread is not Ruby's thread.
2334 * What should we do?
2335 */
2336 bp();
2337 fprintf(stderr, "[BUG] rb_thread_call_with_gvl() is called by non-ruby thread\n");
2338 exit(EXIT_FAILURE);
2339 }
2340
2341 brb = (struct rb_blocking_region_buffer *)th->blocking_region_buffer;
2342 prev_unblock = th->unblock;
2343
2344 if (brb == 0) {
2345 /* the GVL is already acquired, call method directly */
2346 return (*func)(data1);
2347 }
2348
2349 blocking_region_end(th, brb);
2350 /* enter to Ruby world: You can access Ruby values, methods and so on. */
2351 r = (*func)(data1);
2352 /* leave from Ruby world: You can not access Ruby values, etc. */
2353 int released = blocking_region_begin(th, brb, prev_unblock.func, prev_unblock.arg, FALSE);
2354 RUBY_ASSERT_ALWAYS(released);
2355 RB_VM_SAVE_MACHINE_CONTEXT(th);
2356 thread_sched_to_waiting(TH_SCHED(th), th, true);
2357 return r;
2358}
2359
2360/*
2361 * ruby_thread_has_gvl_p - check if current native thread has GVL.
2362 */
2363
2365ruby_thread_has_gvl_p(void)
2366{
2367 rb_thread_t *th = ruby_thread_from_native();
2368
2369 if (th && th->blocking_region_buffer == 0) {
2370 return 1;
2371 }
2372 else {
2373 return 0;
2374 }
2375}
2376
2377/*
2378 * call-seq:
2379 * Thread.pass -> nil
2380 *
2381 * Give the thread scheduler a hint to pass execution to another thread.
2382 * A running thread may or may not switch, it depends on OS and processor.
2383 */
2384
2385static VALUE
2386thread_s_pass(VALUE klass)
2387{
2389 return Qnil;
2390}
2391
2392/*****************************************************/
2393
2394/*
2395 * rb_threadptr_pending_interrupt_* - manage asynchronous error queue
2396 *
2397 * Async events such as an exception thrown by Thread#raise,
2398 * Thread#kill and thread termination (after main thread termination)
2399 * will be queued to th->pending_interrupt_queue.
2400 * - clear: clear the queue.
2401 * - enque: enqueue err object into queue.
2402 * - deque: dequeue err object from queue.
2403 * - active_p: return 1 if the queue should be checked.
2404 *
2405 * All rb_threadptr_pending_interrupt_* functions are called by
2406 * a GVL acquired thread, of course.
2407 * Note that all "rb_" prefix APIs need GVL to call.
2408 */
2409
2410void
2411rb_threadptr_pending_interrupt_clear(rb_thread_t *th)
2412{
2413 rb_ary_clear(th->pending_interrupt_queue);
2414}
2415
2416void
2417rb_threadptr_pending_interrupt_enque(rb_thread_t *th, VALUE v)
2418{
2419 rb_ary_push(th->pending_interrupt_queue, v);
2420 th->pending_interrupt_queue_checked = 0;
2421}
2422
2423static void
2424threadptr_check_pending_interrupt_queue(rb_thread_t *th)
2425{
2426 if (!th->pending_interrupt_queue) {
2427 rb_raise(rb_eThreadError, "uninitialized thread");
2428 }
2429}
2430
2431enum handle_interrupt_timing {
2432 INTERRUPT_NONE,
2433 INTERRUPT_IMMEDIATE,
2434 INTERRUPT_ON_BLOCKING,
2435 INTERRUPT_NEVER
2436};
2437
2438static enum handle_interrupt_timing
2439rb_threadptr_pending_interrupt_from_symbol(rb_thread_t *th, VALUE sym)
2440{
2441 if (sym == sym_immediate) {
2442 return INTERRUPT_IMMEDIATE;
2443 }
2444 else if (sym == sym_on_blocking) {
2445 return INTERRUPT_ON_BLOCKING;
2446 }
2447 else if (sym == sym_never) {
2448 return INTERRUPT_NEVER;
2449 }
2450 else {
2451 rb_raise(rb_eThreadError, "unknown mask signature");
2452 }
2453}
2454
2455static enum handle_interrupt_timing
2456rb_threadptr_pending_interrupt_check_mask(rb_thread_t *th, VALUE err)
2457{
2458 VALUE mask;
2459 rb_len_t mask_stack_len = RARRAY_LEN(th->pending_interrupt_mask_stack);
2460 const VALUE *mask_stack = RARRAY_CONST_PTR(th->pending_interrupt_mask_stack);
2461 VALUE mod;
2462 rb_len_t i;
2463
2464 for (i=0; i<mask_stack_len; i++) {
2465 mask = mask_stack[mask_stack_len-(i+1)];
2466
2467 if (SYMBOL_P(mask)) {
2468 /* do not match RUBY_FATAL_THREAD_KILLED etc */
2469 if (err != rb_cInteger) {
2470 return rb_threadptr_pending_interrupt_from_symbol(th, mask);
2471 }
2472 else {
2473 continue;
2474 }
2475 }
2476
2477 for (mod = err; mod; mod = RCLASS_SUPER(mod)) {
2478 VALUE klass = mod;
2479 VALUE sym;
2480
2481 if (BUILTIN_TYPE(mod) == T_ICLASS) {
2482 klass = RBASIC(mod)->klass;
2483 }
2484 else if (mod != RCLASS_ORIGIN(mod)) {
2485 continue;
2486 }
2487
2488 if ((sym = rb_hash_aref(mask, klass)) != Qnil) {
2489 return rb_threadptr_pending_interrupt_from_symbol(th, sym);
2490 }
2491 }
2492 /* try to next mask */
2493 }
2494 return INTERRUPT_NONE;
2495}
2496
2497static int
2498rb_threadptr_pending_interrupt_empty_p(const rb_thread_t *th)
2499{
2500 return RARRAY_LEN(th->pending_interrupt_queue) == 0;
2501}
2502
2503static int
2504rb_threadptr_pending_interrupt_include_p(rb_thread_t *th, VALUE err)
2505{
2506 int i;
2507 for (i=0; i<RARRAY_LEN(th->pending_interrupt_queue); i++) {
2508 VALUE e = RARRAY_AREF(th->pending_interrupt_queue, i);
2509 if (rb_obj_is_kind_of(e, err)) {
2510 return TRUE;
2511 }
2512 }
2513 return FALSE;
2514}
2515
2516static VALUE
2517rb_threadptr_pending_interrupt_deque(rb_thread_t *th, enum handle_interrupt_timing timing)
2518{
2519#if 1 /* 1 to enable Thread#handle_interrupt, 0 to ignore it */
2520 int i;
2521
2522 for (i=0; i<RARRAY_LEN(th->pending_interrupt_queue); i++) {
2523 VALUE err = RARRAY_AREF(th->pending_interrupt_queue, i);
2524
2525 enum handle_interrupt_timing mask_timing = rb_threadptr_pending_interrupt_check_mask(th, CLASS_OF(err));
2526
2527 switch (mask_timing) {
2528 case INTERRUPT_ON_BLOCKING:
2529 if (timing != INTERRUPT_ON_BLOCKING) {
2530 break;
2531 }
2532 /* fall through */
2533 case INTERRUPT_NONE: /* default: IMMEDIATE */
2534 case INTERRUPT_IMMEDIATE:
2535 rb_ary_delete_at(th->pending_interrupt_queue, i);
2536 return err;
2537 case INTERRUPT_NEVER:
2538 break;
2539 }
2540 }
2541
2542 th->pending_interrupt_queue_checked = 1;
2543 return Qundef;
2544#else
2545 VALUE err = rb_ary_shift(th->pending_interrupt_queue);
2546 if (rb_threadptr_pending_interrupt_empty_p(th)) {
2547 th->pending_interrupt_queue_checked = 1;
2548 }
2549 return err;
2550#endif
2551}
2552
2553static int
2554threadptr_pending_interrupt_active_p(rb_thread_t *th)
2555{
2556 /*
2557 * For optimization, we don't check async errinfo queue
2558 * if the queue and the thread interrupt mask were not changed
2559 * since last check.
2560 */
2561 if (th->pending_interrupt_queue_checked) {
2562 return 0;
2563 }
2564
2565 if (rb_threadptr_pending_interrupt_empty_p(th)) {
2566 return 0;
2567 }
2568
2569 return 1;
2570}
2571
2572static int
2573handle_interrupt_arg_check_i(VALUE key, VALUE val, VALUE args)
2574{
2575 VALUE *maskp = (VALUE *)args;
2576
2577 if (val != sym_immediate && val != sym_on_blocking && val != sym_never) {
2578 rb_raise(rb_eArgError, "unknown mask signature");
2579 }
2580
2581 if (key == rb_eException && (UNDEF_P(*maskp) || NIL_P(*maskp))) {
2582 *maskp = val;
2583 return ST_CONTINUE;
2584 }
2585
2586 if (RTEST(*maskp)) {
2587 if (!RB_TYPE_P(*maskp, T_HASH)) {
2588 VALUE prev = *maskp;
2589 *maskp = rb_ident_hash_new();
2590 if (SYMBOL_P(prev)) {
2591 rb_hash_aset(*maskp, rb_eException, prev);
2592 }
2593 }
2594 rb_hash_aset(*maskp, key, val);
2595 }
2596 else {
2597 *maskp = Qfalse;
2598 }
2599
2600 return ST_CONTINUE;
2601}
2602
2603/*
2604 * call-seq:
2605 * Thread.handle_interrupt(hash) { ... } -> result of the block
2606 *
2607 * Changes asynchronous interrupt timing.
2608 *
2609 * _interrupt_ means asynchronous event and corresponding procedure
2610 * by Thread#raise, Thread#kill, signal trap (not supported yet)
2611 * and main thread termination (if main thread terminates, then all
2612 * other thread will be killed).
2613 *
2614 * The given +hash+ has pairs like <code>ExceptionClass =>
2615 * :TimingSymbol</code>. Where the ExceptionClass is the interrupt handled by
2616 * the given block. The TimingSymbol can be one of the following symbols:
2617 *
2618 * [+:immediate+] Invoke interrupts immediately.
2619 * [+:on_blocking+] Invoke interrupts while _BlockingOperation_.
2620 * [+:never+] Never invoke all interrupts.
2621 *
2622 * _BlockingOperation_ means that the operation will block the calling thread,
2623 * such as read and write. On CRuby implementation, _BlockingOperation_ is any
2624 * operation executed without GVL.
2625 *
2626 * Masked asynchronous interrupts are delayed until they are enabled.
2627 * This method is similar to sigprocmask(3).
2628 *
2629 * === NOTE
2630 *
2631 * Asynchronous interrupts are difficult to use.
2632 *
2633 * If you need to communicate between threads, please consider to use another way such as Queue.
2634 *
2635 * Or use them with deep understanding about this method.
2636 *
2637 * === Usage
2638 *
2639 * In this example, we can guard from Thread#raise exceptions.
2640 *
2641 * Using the +:never+ TimingSymbol the RuntimeError exception will always be
2642 * ignored in the first block of the main thread. In the second
2643 * ::handle_interrupt block we can purposefully handle RuntimeError exceptions.
2644 *
2645 * th = Thread.new do
2646 * Thread.handle_interrupt(RuntimeError => :never) {
2647 * begin
2648 * # You can write resource allocation code safely.
2649 * Thread.handle_interrupt(RuntimeError => :immediate) {
2650 * # ...
2651 * }
2652 * ensure
2653 * # You can write resource deallocation code safely.
2654 * end
2655 * }
2656 * end
2657 * Thread.pass
2658 * # ...
2659 * th.raise "stop"
2660 *
2661 * While we are ignoring the RuntimeError exception, it's safe to write our
2662 * resource allocation code. Then, the ensure block is where we can safely
2663 * deallocate your resources.
2664 *
2665 * ==== Stack control settings
2666 *
2667 * It's possible to stack multiple levels of ::handle_interrupt blocks in order
2668 * to control more than one ExceptionClass and TimingSymbol at a time.
2669 *
2670 * Thread.handle_interrupt(FooError => :never) {
2671 * Thread.handle_interrupt(BarError => :never) {
2672 * # FooError and BarError are prohibited.
2673 * }
2674 * }
2675 *
2676 * ==== Inheritance with ExceptionClass
2677 *
2678 * All exceptions inherited from the ExceptionClass parameter will be considered.
2679 *
2680 * Thread.handle_interrupt(Exception => :never) {
2681 * # all exceptions inherited from Exception are prohibited.
2682 * }
2683 *
2684 * For handling all interrupts, use +Object+ and not +Exception+
2685 * as the ExceptionClass, as kill/terminate interrupts are not handled by +Exception+.
2686 */
2687static VALUE
2688rb_thread_s_handle_interrupt(VALUE self, VALUE mask_arg)
2689{
2690 VALUE mask = Qundef;
2691 rb_execution_context_t * volatile ec = GET_EC();
2692 rb_thread_t * volatile th = rb_ec_thread_ptr(ec);
2693 volatile VALUE r = Qnil;
2694 enum ruby_tag_type state;
2695
2696 if (!rb_block_given_p()) {
2697 rb_raise(rb_eArgError, "block is needed.");
2698 }
2699
2700 mask_arg = rb_to_hash_type(mask_arg);
2701
2702 if (OBJ_FROZEN(mask_arg) && rb_hash_compare_by_id_p(mask_arg)) {
2703 mask = Qnil;
2704 }
2705
2706 rb_hash_foreach(mask_arg, handle_interrupt_arg_check_i, (VALUE)&mask);
2707
2708 if (UNDEF_P(mask)) {
2709 return rb_yield(Qnil);
2710 }
2711
2712 if (!RTEST(mask)) {
2713 mask = mask_arg;
2714 }
2715 else if (RB_TYPE_P(mask, T_HASH)) {
2716 OBJ_FREEZE(mask);
2717 }
2718
2719 rb_ary_push(th->pending_interrupt_mask_stack, mask);
2720 if (!rb_threadptr_pending_interrupt_empty_p(th)) {
2721 th->pending_interrupt_queue_checked = 0;
2722 RUBY_VM_SET_INTERRUPT(th->ec);
2723 }
2724
2725 EC_PUSH_TAG(th->ec);
2726 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
2727 r = rb_yield(Qnil);
2728 }
2729 EC_POP_TAG();
2730
2731 rb_ary_pop(th->pending_interrupt_mask_stack);
2732 if (!rb_threadptr_pending_interrupt_empty_p(th)) {
2733 th->pending_interrupt_queue_checked = 0;
2734 RUBY_VM_SET_INTERRUPT(th->ec);
2735 }
2736
2737 RUBY_VM_CHECK_INTS(th->ec);
2738
2739 if (state) {
2740 EC_JUMP_TAG(th->ec, state);
2741 }
2742
2743 return r;
2744}
2745
2746/*
2747 * call-seq:
2748 * target_thread.pending_interrupt?(error = nil) -> true/false
2749 *
2750 * Returns whether or not the asynchronous queue is empty for the target thread.
2751 *
2752 * If +error+ is given, then check only for +error+ type deferred events.
2753 *
2754 * See ::pending_interrupt? for more information.
2755 */
2756static VALUE
2757rb_thread_pending_interrupt_p(int argc, VALUE *argv, VALUE target_thread)
2758{
2759 rb_thread_t *target_th = rb_thread_ptr(target_thread);
2760
2761 if (!target_th->pending_interrupt_queue) {
2762 return Qfalse;
2763 }
2764 if (rb_threadptr_pending_interrupt_empty_p(target_th)) {
2765 return Qfalse;
2766 }
2767 if (rb_check_arity(argc, 0, 1)) {
2768 VALUE err = argv[0];
2769 if (!rb_obj_is_kind_of(err, rb_cModule)) {
2770 rb_raise(rb_eTypeError, "class or module required for rescue clause");
2771 }
2772 return RBOOL(rb_threadptr_pending_interrupt_include_p(target_th, err));
2773 }
2774 else {
2775 return Qtrue;
2776 }
2777}
2778
2779/*
2780 * call-seq:
2781 * Thread.pending_interrupt?(error = nil) -> true/false
2782 *
2783 * Returns whether or not the asynchronous queue is empty.
2784 *
2785 * Since Thread::handle_interrupt can be used to defer asynchronous events,
2786 * this method can be used to determine if there are any deferred events.
2787 *
2788 * If you find this method returns true, then you may finish +:never+ blocks.
2789 *
2790 * For example, the following method processes deferred asynchronous events
2791 * immediately.
2792 *
2793 * def Thread.kick_interrupt_immediately
2794 * Thread.handle_interrupt(Object => :immediate) {
2795 * Thread.pass
2796 * }
2797 * end
2798 *
2799 * If +error+ is given, then check only for +error+ type deferred events.
2800 *
2801 * === Usage
2802 *
2803 * th = Thread.new{
2804 * Thread.handle_interrupt(RuntimeError => :on_blocking){
2805 * while true
2806 * ...
2807 * # reach safe point to invoke interrupt
2808 * if Thread.pending_interrupt?
2809 * Thread.handle_interrupt(Object => :immediate){}
2810 * end
2811 * ...
2812 * end
2813 * }
2814 * }
2815 * ...
2816 * th.raise # stop thread
2817 *
2818 * This example can also be written as the following, which you should use to
2819 * avoid asynchronous interrupts.
2820 *
2821 * flag = true
2822 * th = Thread.new{
2823 * Thread.handle_interrupt(RuntimeError => :on_blocking){
2824 * while true
2825 * ...
2826 * # reach safe point to invoke interrupt
2827 * break if flag == false
2828 * ...
2829 * end
2830 * }
2831 * }
2832 * ...
2833 * flag = false # stop thread
2834 */
2835
2836static VALUE
2837rb_thread_s_pending_interrupt_p(int argc, VALUE *argv, VALUE self)
2838{
2839 return rb_thread_pending_interrupt_p(argc, argv, GET_THREAD()->self);
2840}
2841
2842NORETURN(static void rb_threadptr_to_kill(rb_thread_t *th));
2843
2844static void
2845rb_threadptr_to_kill(rb_thread_t *th)
2846{
2847 VM_ASSERT(GET_THREAD() == th);
2848 rb_threadptr_pending_interrupt_clear(th);
2849 th->status = THREAD_RUNNABLE;
2850 th->to_kill = 1;
2851 th->ec->errinfo = INT2FIX(TAG_FATAL);
2852 EC_JUMP_TAG(th->ec, TAG_FATAL);
2853}
2854
2855static inline rb_atomic_t
2856threadptr_get_interrupts(rb_thread_t *th)
2857{
2858 rb_execution_context_t *ec = th->ec;
2859 rb_atomic_t interrupt;
2860 rb_atomic_t old;
2861
2862 old = ATOMIC_LOAD_RELAXED(ec->interrupt_flag);
2863 do {
2864 interrupt = old;
2865 old = ATOMIC_CAS(ec->interrupt_flag, interrupt, interrupt & ec->interrupt_mask);
2866 } while (old != interrupt);
2867 return interrupt & (rb_atomic_t)~ec->interrupt_mask;
2868}
2869
2870static void threadptr_interrupt_exec_exec(rb_thread_t *th);
2871
2872// Execute interrupts on currently running thread
2873// In certain situations, calling this function will raise an exception. Some examples are:
2874// * during VM shutdown (`rb_ractor_terminate_all`)
2875// * Call to Thread#exit for current thread (`rb_thread_kill`)
2876// * Call to Thread#raise for current thread
2877int
2878rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing)
2879{
2880 rb_atomic_t interrupt;
2881 int postponed_job_interrupt = 0;
2882 int ret = FALSE;
2883
2884 VM_ASSERT(GET_THREAD() == th);
2885
2886 if (th->ec->raised_flag) return ret;
2887
2888 /* Interrupts are checked at arbitrary points, including while a jump
2889 * (break/next/redo/return/raise/throw) is being propagated.
2890 * Save errinfo as the interrupt might run Ruby code that could clear it. */
2891 const VALUE saved_errinfo = th->ec->errinfo;
2892
2893 while ((interrupt = threadptr_get_interrupts(th)) != 0) {
2894 int sig;
2895 int timer_interrupt;
2896 int pending_interrupt;
2897 int trap_interrupt;
2898 int terminate_interrupt;
2899
2900 timer_interrupt = interrupt & TIMER_INTERRUPT_MASK;
2901 pending_interrupt = interrupt & PENDING_INTERRUPT_MASK;
2902 postponed_job_interrupt = interrupt & POSTPONED_JOB_INTERRUPT_MASK;
2903 trap_interrupt = interrupt & TRAP_INTERRUPT_MASK;
2904 terminate_interrupt = interrupt & TERMINATE_INTERRUPT_MASK; // request from other ractors
2905
2906 if (interrupt & VM_BARRIER_INTERRUPT_MASK) {
2907 RB_VM_LOCKING();
2908 }
2909
2910 if (postponed_job_interrupt) {
2911 rb_postponed_job_flush();
2912 }
2913
2914 if (trap_interrupt) {
2915 /* signal handling */
2916 if (th == th->vm->ractor.main_thread) {
2917 enum rb_thread_status prev_status = th->status;
2918
2919 th->status = THREAD_RUNNABLE;
2920 {
2921 while ((sig = rb_get_next_signal()) != 0) {
2922 ret |= rb_signal_exec(th, sig);
2923 }
2924 }
2925 th->status = prev_status;
2926 }
2927
2928 if (!ccan_list_empty(&th->interrupt_exec_tasks)) {
2929 enum rb_thread_status prev_status = th->status;
2930
2931 th->status = THREAD_RUNNABLE;
2932 {
2933 threadptr_interrupt_exec_exec(th);
2934 }
2935 th->status = prev_status;
2936 }
2937 }
2938
2939 /* exception from another thread */
2940 if (pending_interrupt && threadptr_pending_interrupt_active_p(th)) {
2941 VALUE err = rb_threadptr_pending_interrupt_deque(th, blocking_timing ? INTERRUPT_ON_BLOCKING : INTERRUPT_NONE);
2942 RUBY_DEBUG_LOG("err:%"PRIdVALUE, err);
2943 ret = TRUE;
2944
2945 if (UNDEF_P(err)) {
2946 /* no error */
2947 }
2948 else if (err == RUBY_FATAL_THREAD_KILLED /* Thread#kill received */ ||
2949 err == RUBY_FATAL_THREAD_TERMINATED /* Terminate thread */ ||
2950 err == INT2FIX(TAG_FATAL) /* Thread.exit etc. */ ) {
2951 terminate_interrupt = 1;
2952 }
2953 else {
2954 if (err == th->vm->special_exceptions[ruby_error_stream_closed]) {
2955 /* the only special exception to be queued across thread */
2956 err = ruby_vm_special_exception_copy(err);
2957 }
2958 /* set runnable if th was slept. */
2959 if (th->status == THREAD_STOPPED ||
2960 th->status == THREAD_STOPPED_FOREVER)
2961 th->status = THREAD_RUNNABLE;
2962 rb_exc_raise(err);
2963 }
2964 }
2965
2966 if (terminate_interrupt) {
2967 rb_threadptr_to_kill(th);
2968 }
2969
2970 if (timer_interrupt) {
2971 uint32_t limits_us = thread_default_quantum_ms * 1000;
2972
2973 if (th->priority > 0)
2974 limits_us <<= th->priority;
2975 else
2976 limits_us >>= -th->priority;
2977
2978 if (th->status == THREAD_RUNNABLE)
2979 th->running_time_us += 10 * 1000; // 10ms = 10_000us // TODO: use macro
2980
2981 VM_ASSERT(th->ec->cfp);
2982 EXEC_EVENT_HOOK(th->ec, RUBY_INTERNAL_EVENT_SWITCH, th->ec->cfp->self,
2983 0, 0, 0, Qundef);
2984
2985 rb_thread_schedule_limits(limits_us);
2986 }
2987 }
2988
2989 th->ec->errinfo = saved_errinfo;
2990
2991 return ret;
2992}
2993
2994void
2995rb_thread_execute_interrupts(VALUE thval)
2996{
2997 rb_threadptr_execute_interrupts(rb_thread_ptr(thval), 1);
2998}
2999
3000static void
3001rb_threadptr_ready(rb_thread_t *th)
3002{
3003 rb_threadptr_interrupt(th);
3004}
3005
3006static VALUE
3007rb_threadptr_raise(rb_thread_t *target_th, int argc, VALUE *argv)
3008{
3009 VALUE exc;
3010
3011 if (rb_threadptr_dead(target_th)) {
3012 return Qnil;
3013 }
3014
3015 if (argc == 0) {
3016 exc = rb_exc_new(rb_eRuntimeError, 0, 0);
3017 }
3018 else {
3019 exc = rb_make_exception(argc, argv);
3020 }
3021
3022 /* making an exception object can switch thread,
3023 so we need to check thread deadness again */
3024 if (rb_threadptr_dead(target_th)) {
3025 return Qnil;
3026 }
3027
3028 rb_ec_setup_exception(GET_EC(), exc, Qundef);
3029 rb_threadptr_pending_interrupt_enque(target_th, exc);
3030 rb_threadptr_interrupt(target_th);
3031
3032 return Qnil;
3033}
3034
3035void
3036rb_threadptr_signal_raise(rb_thread_t *th, int sig)
3037{
3038 VALUE argv[2];
3039
3040 argv[0] = rb_eSignal;
3041 argv[1] = INT2FIX(sig);
3042 rb_threadptr_raise(th->vm->ractor.main_thread, 2, argv);
3043}
3044
3045void
3046rb_threadptr_interrupt_raise(rb_thread_t *th)
3047{
3048 rb_thread_t *target_th = th->vm->ractor.main_thread;
3049
3050 if (rb_threadptr_dead(target_th)) {
3051 return;
3052 }
3053
3054 /* Preserve the traditional no-message Interrupt from default SIGINT. */
3055 VALUE exc = rb_exc_new(rb_eInterrupt, 0, 0);
3056
3057 /* making an exception object can switch thread,
3058 so we need to check thread deadness again */
3059 if (rb_threadptr_dead(target_th)) {
3060 return;
3061 }
3062
3063 rb_ec_setup_exception(GET_EC(), exc, Qundef);
3064 rb_threadptr_pending_interrupt_enque(target_th, exc);
3065 rb_threadptr_interrupt(target_th);
3066}
3067
3068void
3069rb_threadptr_signal_exit(rb_thread_t *th)
3070{
3071 VALUE argv[2];
3072
3073 argv[0] = rb_eSystemExit;
3074 argv[1] = rb_str_new2("exit");
3075
3076 // TODO: check signal raise deliverly
3077 rb_threadptr_raise(th->vm->ractor.main_thread, 2, argv);
3078}
3079
3080int
3081rb_ec_set_raised(rb_execution_context_t *ec)
3082{
3083 if (ec->raised_flag & RAISED_EXCEPTION) {
3084 return 1;
3085 }
3086 ec->raised_flag |= RAISED_EXCEPTION;
3087 return 0;
3088}
3089
3090int
3091rb_ec_reset_raised(rb_execution_context_t *ec)
3092{
3093 if (!(ec->raised_flag & RAISED_EXCEPTION)) {
3094 return 0;
3095 }
3096 ec->raised_flag &= ~RAISED_EXCEPTION;
3097 return 1;
3098}
3099
3100/*
3101 * Thread-safe IO closing mechanism.
3102 *
3103 * When an IO is closed while other threads or fibers are blocked on it, we need to:
3104 * 1. Track and notify all blocking operations through io->blocking_operations
3105 * 2. Ensure only one thread can close at a time using io->closing_ec
3106 * 3. Synchronize cleanup using wakeup_mutex
3107 *
3108 * The close process works as follows:
3109 * - First check if any thread is already closing (io->closing_ec)
3110 * - Set up wakeup_mutex for synchronization
3111 * - Iterate through all blocking operations in io->blocking_operations
3112 * - For each blocked fiber with a scheduler:
3113 * - Notify via rb_fiber_scheduler_fiber_interrupt
3114 * - For each blocked thread without a scheduler:
3115 * - Enqueue IOError via rb_threadptr_pending_interrupt_enque
3116 * - Wake via rb_threadptr_interrupt
3117 * - Wait on wakeup_mutex until all operations are cleaned up
3118 * - Only then clear closing state and allow actual close to proceed
3119 */
3120static VALUE
3121thread_io_close_notify_all(VALUE _io)
3122{
3123 struct rb_io *io = (struct rb_io *)_io;
3124
3125 size_t count = 0;
3126 rb_vm_t *vm = io->closing_ec->thread_ptr->vm;
3127 VALUE error = vm->special_exceptions[ruby_error_stream_closed];
3128
3129 struct rb_io_blocking_operation *blocking_operation;
3130 ccan_list_for_each(rb_io_blocking_operations(io), blocking_operation, list) {
3131 rb_execution_context_t *ec = blocking_operation->ec;
3132
3133 // If the operation is in progress, we need to interrupt it:
3134 if (ec) {
3135 rb_thread_t *thread = ec->thread_ptr;
3136
3137 if (thread->scheduler != Qnil) {
3138 VALUE target = blocking_operation->scheduler_interrupt_target;
3139
3140 if (NIL_P(target)) {
3141 VALUE fiber = rb_fiberptr_self(ec->fiber_ptr);
3142 target = rb_fiber_scheduler_interrupt_target_new(fiber, error);
3143 blocking_operation->scheduler_interrupt_target = target;
3144 }
3145
3146 rb_fiber_scheduler_fiber_interrupt(thread->scheduler, target, error);
3147 RB_GC_GUARD(target);
3148 }
3149 else {
3150 // If the thread is not the current thread, we need to enqueue an error:
3151 rb_threadptr_pending_interrupt_enque(thread, error);
3152 rb_threadptr_interrupt(thread);
3153 }
3154 }
3155
3156 count += 1;
3157 }
3158
3159 return (VALUE)count;
3160}
3161
3162size_t
3163rb_thread_io_close_interrupt(struct rb_io *io)
3164{
3165 // We guard this operation based on `io->closing_ec` -> only one thread will ever enter this function.
3166 if (io->closing_ec) {
3167 return 0;
3168 }
3169
3170 // If there are no blocking operations, we are done:
3171 if (ccan_list_empty(rb_io_blocking_operations(io))) {
3172 return 0;
3173 }
3174
3175 // Otherwise, we are now closing the IO:
3176 rb_execution_context_t *ec = GET_EC();
3177 io->closing_ec = ec;
3178
3179 // This is used to ensure the correct execution context is woken up after the blocking operation is interrupted:
3180 io->wakeup_mutex = rb_mutex_new();
3181 rb_mutex_allow_trap(io->wakeup_mutex, 1);
3182
3183 // We need to use a mutex here as entering the fiber scheduler may cause a context switch:
3184 VALUE result = rb_mutex_synchronize(io->wakeup_mutex, thread_io_close_notify_all, (VALUE)io);
3185
3186 return (size_t)result;
3187}
3188
3189void
3190rb_thread_io_close_wait(struct rb_io* io)
3191{
3192 VALUE wakeup_mutex = io->wakeup_mutex;
3193
3194 if (!RB_TEST(wakeup_mutex)) {
3195 // There was nobody else using this file when we closed it, so we never bothered to allocate a mutex:
3196 return;
3197 }
3198
3199 rb_mutex_lock(wakeup_mutex);
3200 while (!ccan_list_empty(rb_io_blocking_operations(io))) {
3201 rb_mutex_sleep(wakeup_mutex, Qnil);
3202 }
3203 rb_mutex_unlock(wakeup_mutex);
3204
3205 // We are done closing:
3206 io->wakeup_mutex = Qnil;
3207 io->closing_ec = NULL;
3208}
3209
3210void
3211rb_thread_fd_close(int fd)
3212{
3213 rb_warn("rb_thread_fd_close is deprecated (and is now a no-op).");
3214}
3215
3216/*
3217 * call-seq:
3218 * raise(exception, message = exception.to_s, backtrace = nil, cause: $!)
3219 * raise(message = nil, cause: $!)
3220 *
3221 * Raises an exception from the given thread. The caller does not have to be
3222 * +thr+. See Kernel#raise for more information on arguments.
3223 *
3224 * Thread.abort_on_exception = true
3225 * a = Thread.new { sleep(200) }
3226 * a.raise("Gotcha")
3227 *
3228 * This will produce:
3229 *
3230 * prog.rb:3: Gotcha (RuntimeError)
3231 * from prog.rb:2:in `initialize'
3232 * from prog.rb:2:in `new'
3233 * from prog.rb:2
3234 */
3235
3236static VALUE
3237thread_raise_m(int argc, VALUE *argv, VALUE self)
3238{
3239 rb_thread_t *target_th = rb_thread_ptr(self);
3240 const rb_thread_t *current_th = GET_THREAD();
3241
3242 threadptr_check_pending_interrupt_queue(target_th);
3243
3244 if (rb_threadptr_dead(target_th)) {
3245 return Qnil;
3246 }
3247
3248 VALUE exception = rb_exception_setup(argc, argv);
3249 rb_threadptr_pending_interrupt_enque(target_th, exception);
3250 rb_threadptr_interrupt(target_th);
3251
3252 /* To perform Thread.current.raise as Kernel.raise */
3253 if (current_th == target_th) {
3254 RUBY_VM_CHECK_INTS(target_th->ec);
3255 }
3256 return Qnil;
3257}
3258
3259
3260/*
3261 * call-seq:
3262 * thr.exit -> thr
3263 * thr.kill -> thr
3264 * thr.terminate -> thr
3265 *
3266 * Terminates +thr+ and schedules another thread to be run, returning
3267 * the terminated Thread. If this is the main thread, or the last
3268 * thread, exits the process. Note that the caller does not wait for
3269 * the thread to terminate if the receiver is different from the currently
3270 * running thread. The termination is asynchronous, and the thread can still
3271 * run a small amount of ruby code before exiting.
3272 */
3273
3275rb_thread_kill(VALUE thread)
3276{
3277 rb_thread_t *target_th = rb_thread_ptr(thread);
3278
3279 if (target_th->to_kill || target_th->status == THREAD_KILLED) {
3280 return thread;
3281 }
3282 if (target_th == target_th->vm->ractor.main_thread) {
3283 rb_exit(EXIT_SUCCESS);
3284 }
3285
3286 RUBY_DEBUG_LOG("target_th:%u", rb_th_serial(target_th));
3287
3288 if (target_th == GET_THREAD()) {
3289 /* kill myself immediately */
3290 rb_threadptr_to_kill(target_th);
3291 }
3292 else {
3293 threadptr_check_pending_interrupt_queue(target_th);
3294 rb_threadptr_pending_interrupt_enque(target_th, RUBY_FATAL_THREAD_KILLED);
3295 rb_threadptr_interrupt(target_th);
3296 }
3297
3298 return thread;
3299}
3300
3301int
3302rb_thread_to_be_killed(VALUE thread)
3303{
3304 rb_thread_t *target_th = rb_thread_ptr(thread);
3305
3306 if (target_th->to_kill || target_th->status == THREAD_KILLED) {
3307 return TRUE;
3308 }
3309 return FALSE;
3310}
3311
3312/*
3313 * call-seq:
3314 * Thread.kill(thread) -> thread
3315 *
3316 * Causes the given +thread+ to exit, see also Thread::exit.
3317 *
3318 * count = 0
3319 * a = Thread.new { loop { count += 1 } }
3320 * sleep(0.1) #=> 0
3321 * Thread.kill(a) #=> #<Thread:0x401b3d30 dead>
3322 * count #=> 93947
3323 * a.alive? #=> false
3324 */
3325
3326static VALUE
3327rb_thread_s_kill(VALUE obj, VALUE th)
3328{
3329 return rb_thread_kill(th);
3330}
3331
3332
3333/*
3334 * call-seq:
3335 * Thread.exit -> thread
3336 *
3337 * Terminates the currently running thread and schedules another thread to be
3338 * run.
3339 *
3340 * If this thread is already marked to be killed, ::exit returns the Thread.
3341 *
3342 * If this is the main thread, or the last thread, exit the process.
3343 */
3344
3345static VALUE
3346rb_thread_exit(VALUE _)
3347{
3348 rb_thread_t *th = GET_THREAD();
3349 return rb_thread_kill(th->self);
3350}
3351
3352
3353/*
3354 * call-seq:
3355 * thr.wakeup -> thr
3356 *
3357 * Marks a given thread as eligible for scheduling, however it may still
3358 * remain blocked on I/O.
3359 *
3360 * *Note:* This does not invoke the scheduler, see #run for more information.
3361 *
3362 * c = Thread.new { Thread.stop; puts "hey!" }
3363 * sleep 0.1 while c.status!='sleep'
3364 * c.wakeup
3365 * c.join
3366 * #=> "hey!"
3367 */
3368
3370rb_thread_wakeup(VALUE thread)
3371{
3372 if (!RTEST(rb_thread_wakeup_alive(thread))) {
3373 rb_raise(rb_eThreadError, "killed thread");
3374 }
3375 return thread;
3376}
3377
3380{
3381 rb_thread_t *target_th = rb_thread_ptr(thread);
3382 if (target_th->status == THREAD_KILLED) return Qnil;
3383
3384 rb_threadptr_ready(target_th);
3385
3386 if (target_th->status == THREAD_STOPPED ||
3387 target_th->status == THREAD_STOPPED_FOREVER) {
3388 target_th->status = THREAD_RUNNABLE;
3389 }
3390
3391 return thread;
3392}
3393
3394
3395/*
3396 * call-seq:
3397 * thr.run -> thr
3398 *
3399 * Wakes up +thr+, making it eligible for scheduling.
3400 *
3401 * a = Thread.new { puts "a"; Thread.stop; puts "c" }
3402 * sleep 0.1 while a.status!='sleep'
3403 * puts "Got here"
3404 * a.run
3405 * a.join
3406 *
3407 * This will produce:
3408 *
3409 * a
3410 * Got here
3411 * c
3412 *
3413 * See also the instance method #wakeup.
3414 */
3415
3417rb_thread_run(VALUE thread)
3418{
3419 rb_thread_wakeup(thread);
3421 return thread;
3422}
3423
3424
3426rb_thread_stop(void)
3427{
3428 if (rb_thread_alone()) {
3429 rb_raise(rb_eThreadError,
3430 "stopping only thread\n\tnote: use sleep to stop forever");
3431 }
3433 return Qnil;
3434}
3435
3436/*
3437 * call-seq:
3438 * Thread.stop -> nil
3439 *
3440 * Stops execution of the current thread, putting it into a ``sleep'' state,
3441 * and schedules execution of another thread.
3442 *
3443 * a = Thread.new { print "a"; Thread.stop; print "c" }
3444 * sleep 0.1 while a.status!='sleep'
3445 * print "b"
3446 * a.run
3447 * a.join
3448 * #=> "abc"
3449 */
3450
3451static VALUE
3452thread_stop(VALUE _)
3453{
3454 return rb_thread_stop();
3455}
3456
3457/********************************************************************/
3458
3459VALUE
3460rb_thread_list(void)
3461{
3462 // TODO
3463 return rb_ractor_thread_list();
3464}
3465
3466/*
3467 * call-seq:
3468 * Thread.list -> array
3469 *
3470 * Returns an array of Thread objects for all threads that are either runnable
3471 * or stopped.
3472 *
3473 * Thread.new { sleep(200) }
3474 * Thread.new { 1000000.times {|i| i*i } }
3475 * Thread.new { Thread.stop }
3476 * Thread.list.each {|t| p t}
3477 *
3478 * This will produce:
3479 *
3480 * #<Thread:0x401b3e84 sleep>
3481 * #<Thread:0x401b3f38 run>
3482 * #<Thread:0x401b3fb0 sleep>
3483 * #<Thread:0x401bdf4c run>
3484 */
3485
3486static VALUE
3487thread_list(VALUE _)
3488{
3489 return rb_thread_list();
3490}
3491
3494{
3495 return GET_THREAD()->self;
3496}
3497
3498/*
3499 * call-seq:
3500 * Thread.current -> thread
3501 *
3502 * Returns the currently executing thread.
3503 *
3504 * Thread.current #=> #<Thread:0x401bdf4c run>
3505 */
3506
3507static VALUE
3508thread_s_current(VALUE klass)
3509{
3510 return rb_thread_current();
3511}
3512
3514rb_thread_main(void)
3515{
3516 return GET_RACTOR()->threads.main->self;
3517}
3518
3519/*
3520 * call-seq:
3521 * Thread.main -> thread
3522 *
3523 * Returns the main thread.
3524 */
3525
3526static VALUE
3527rb_thread_s_main(VALUE klass)
3528{
3529 return rb_thread_main();
3530}
3531
3532
3533/*
3534 * call-seq:
3535 * Thread.abort_on_exception -> true or false
3536 *
3537 * Returns the status of the global ``abort on exception'' condition.
3538 *
3539 * The default is +false+.
3540 *
3541 * When set to +true+, if any thread is aborted by an exception, the
3542 * raised exception will be re-raised in the main thread.
3543 *
3544 * Can also be specified by the global $DEBUG flag or command line option
3545 * +-d+.
3546 *
3547 * See also ::abort_on_exception=.
3548 *
3549 * There is also an instance level method to set this for a specific thread,
3550 * see #abort_on_exception.
3551 */
3552
3553static VALUE
3554rb_thread_s_abort_exc(VALUE _)
3555{
3556 return RBOOL(GET_THREAD()->vm->thread_abort_on_exception);
3557}
3558
3559
3560/*
3561 * call-seq:
3562 * Thread.abort_on_exception= boolean -> true or false
3563 *
3564 * When set to +true+, if any thread is aborted by an exception, the
3565 * raised exception will be re-raised in the main thread.
3566 * Returns the new state.
3567 *
3568 * Thread.abort_on_exception = true
3569 * t1 = Thread.new do
3570 * puts "In new thread"
3571 * raise "Exception from thread"
3572 * end
3573 * sleep(1)
3574 * puts "not reached"
3575 *
3576 * This will produce:
3577 *
3578 * In new thread
3579 * prog.rb:4: Exception from thread (RuntimeError)
3580 * from prog.rb:2:in `initialize'
3581 * from prog.rb:2:in `new'
3582 * from prog.rb:2
3583 *
3584 * See also ::abort_on_exception.
3585 *
3586 * There is also an instance level method to set this for a specific thread,
3587 * see #abort_on_exception=.
3588 */
3589
3590static VALUE
3591rb_thread_s_abort_exc_set(VALUE self, VALUE val)
3592{
3593 GET_THREAD()->vm->thread_abort_on_exception = RTEST(val);
3594 return val;
3595}
3596
3597
3598/*
3599 * call-seq:
3600 * thr.abort_on_exception -> true or false
3601 *
3602 * Returns the status of the thread-local ``abort on exception'' condition for
3603 * this +thr+.
3604 *
3605 * The default is +false+.
3606 *
3607 * See also #abort_on_exception=.
3608 *
3609 * There is also a class level method to set this for all threads, see
3610 * ::abort_on_exception.
3611 */
3612
3613static VALUE
3614rb_thread_abort_exc(VALUE thread)
3615{
3616 return RBOOL(rb_thread_ptr(thread)->abort_on_exception);
3617}
3618
3619
3620/*
3621 * call-seq:
3622 * thr.abort_on_exception= boolean -> true or false
3623 *
3624 * When set to +true+, if this +thr+ is aborted by an exception, the
3625 * raised exception will be re-raised in the main thread.
3626 *
3627 * See also #abort_on_exception.
3628 *
3629 * There is also a class level method to set this for all threads, see
3630 * ::abort_on_exception=.
3631 */
3632
3633static VALUE
3634rb_thread_abort_exc_set(VALUE thread, VALUE val)
3635{
3636 rb_thread_ptr(thread)->abort_on_exception = RTEST(val);
3637 return val;
3638}
3639
3640
3641/*
3642 * call-seq:
3643 * Thread.report_on_exception -> true or false
3644 *
3645 * Returns the status of the global ``report on exception'' condition.
3646 *
3647 * The default is +true+ since Ruby 2.5.
3648 *
3649 * All threads created when this flag is true will report
3650 * a message on $stderr if an exception kills the thread.
3651 *
3652 * Thread.new { 1.times { raise } }
3653 *
3654 * will produce this output on $stderr:
3655 *
3656 * #<Thread:...> terminated with exception (report_on_exception is true):
3657 * Traceback (most recent call last):
3658 * 2: from -e:1:in `block in <main>'
3659 * 1: from -e:1:in `times'
3660 *
3661 * This is done to catch errors in threads early.
3662 * In some cases, you might not want this output.
3663 * There are multiple ways to avoid the extra output:
3664 *
3665 * * If the exception is not intended, the best is to fix the cause of
3666 * the exception so it does not happen anymore.
3667 * * If the exception is intended, it might be better to rescue it closer to
3668 * where it is raised rather then let it kill the Thread.
3669 * * If it is guaranteed the Thread will be joined with Thread#join or
3670 * Thread#value, then it is safe to disable this report with
3671 * <code>Thread.current.report_on_exception = false</code>
3672 * when starting the Thread.
3673 * However, this might handle the exception much later, or not at all
3674 * if the Thread is never joined due to the parent thread being blocked, etc.
3675 *
3676 * See also ::report_on_exception=.
3677 *
3678 * There is also an instance level method to set this for a specific thread,
3679 * see #report_on_exception=.
3680 *
3681 */
3682
3683static VALUE
3684rb_thread_s_report_exc(VALUE _)
3685{
3686 return RBOOL(GET_THREAD()->vm->thread_report_on_exception);
3687}
3688
3689
3690/*
3691 * call-seq:
3692 * Thread.report_on_exception= boolean -> true or false
3693 *
3694 * Returns the new state.
3695 * When set to +true+, all threads created afterwards will inherit the
3696 * condition and report a message on $stderr if an exception kills a thread:
3697 *
3698 * Thread.report_on_exception = true
3699 * t1 = Thread.new do
3700 * puts "In new thread"
3701 * raise "Exception from thread"
3702 * end
3703 * sleep(1)
3704 * puts "In the main thread"
3705 *
3706 * This will produce:
3707 *
3708 * In new thread
3709 * #<Thread:...prog.rb:2> terminated with exception (report_on_exception is true):
3710 * Traceback (most recent call last):
3711 * prog.rb:4:in `block in <main>': Exception from thread (RuntimeError)
3712 * In the main thread
3713 *
3714 * See also ::report_on_exception.
3715 *
3716 * There is also an instance level method to set this for a specific thread,
3717 * see #report_on_exception=.
3718 */
3719
3720static VALUE
3721rb_thread_s_report_exc_set(VALUE self, VALUE val)
3722{
3723 GET_THREAD()->vm->thread_report_on_exception = RTEST(val);
3724 return val;
3725}
3726
3727
3728/*
3729 * call-seq:
3730 * Thread.ignore_deadlock -> true or false
3731 *
3732 * Returns the status of the global ``ignore deadlock'' condition.
3733 * The default is +false+, so that deadlock conditions are not ignored.
3734 *
3735 * See also ::ignore_deadlock=.
3736 *
3737 */
3738
3739static VALUE
3740rb_thread_s_ignore_deadlock(VALUE _)
3741{
3742 return RBOOL(GET_THREAD()->vm->thread_ignore_deadlock);
3743}
3744
3745
3746/*
3747 * call-seq:
3748 * Thread.ignore_deadlock = boolean -> true or false
3749 *
3750 * Returns the new state.
3751 * When set to +true+, the VM will not check for deadlock conditions.
3752 * It is only useful to set this if your application can break a
3753 * deadlock condition via some other means, such as a signal.
3754 *
3755 * Thread.ignore_deadlock = true
3756 * queue = Thread::Queue.new
3757 *
3758 * trap(:SIGUSR1){queue.push "Received signal"}
3759 *
3760 * # raises fatal error unless ignoring deadlock
3761 * puts queue.pop
3762 *
3763 * See also ::ignore_deadlock.
3764 */
3765
3766static VALUE
3767rb_thread_s_ignore_deadlock_set(VALUE self, VALUE val)
3768{
3769 GET_THREAD()->vm->thread_ignore_deadlock = RTEST(val);
3770 return val;
3771}
3772
3773
3774/*
3775 * call-seq:
3776 * thr.report_on_exception -> true or false
3777 *
3778 * Returns the status of the thread-local ``report on exception'' condition for
3779 * this +thr+.
3780 *
3781 * The default value when creating a Thread is the value of
3782 * the global flag Thread.report_on_exception.
3783 *
3784 * See also #report_on_exception=.
3785 *
3786 * There is also a class level method to set this for all new threads, see
3787 * ::report_on_exception=.
3788 */
3789
3790static VALUE
3791rb_thread_report_exc(VALUE thread)
3792{
3793 return RBOOL(rb_thread_ptr(thread)->report_on_exception);
3794}
3795
3796
3797/*
3798 * call-seq:
3799 * thr.report_on_exception= boolean -> true or false
3800 *
3801 * When set to +true+, a message is printed on $stderr if an exception
3802 * kills this +thr+. See ::report_on_exception for details.
3803 *
3804 * See also #report_on_exception.
3805 *
3806 * There is also a class level method to set this for all new threads, see
3807 * ::report_on_exception=.
3808 */
3809
3810static VALUE
3811rb_thread_report_exc_set(VALUE thread, VALUE val)
3812{
3813 rb_thread_ptr(thread)->report_on_exception = RTEST(val);
3814 return val;
3815}
3816
3817
3818/*
3819 * call-seq:
3820 * thr.group -> thgrp or nil
3821 *
3822 * Returns the ThreadGroup which contains the given thread.
3823 *
3824 * Thread.main.group #=> #<ThreadGroup:0x4029d914>
3825 */
3826
3827VALUE
3828rb_thread_group(VALUE thread)
3829{
3830 return rb_thread_ptr(thread)->thgroup;
3831}
3832
3833static const char *
3834thread_status_name(rb_thread_t *th, int detail)
3835{
3836 switch (th->status) {
3837 case THREAD_RUNNABLE:
3838 return th->to_kill ? "aborting" : "run";
3839 case THREAD_STOPPED_FOREVER:
3840 if (detail) return "sleep_forever";
3841 case THREAD_STOPPED:
3842 return "sleep";
3843 case THREAD_KILLED:
3844 return "dead";
3845 default:
3846 return "unknown";
3847 }
3848}
3849
3850static int
3851rb_threadptr_dead(rb_thread_t *th)
3852{
3853 return th->status == THREAD_KILLED;
3854}
3855
3856
3857/*
3858 * call-seq:
3859 * thr.status -> string, false or nil
3860 *
3861 * Returns the status of +thr+.
3862 *
3863 * [<tt>"sleep"</tt>]
3864 * Returned if this thread is sleeping or waiting on I/O
3865 * [<tt>"run"</tt>]
3866 * When this thread is executing
3867 * [<tt>"aborting"</tt>]
3868 * If this thread is aborting
3869 * [+false+]
3870 * When this thread is terminated normally
3871 * [+nil+]
3872 * If terminated with an exception.
3873 *
3874 * a = Thread.new { raise("die now") }
3875 * b = Thread.new { Thread.stop }
3876 * c = Thread.new { Thread.exit }
3877 * d = Thread.new { sleep }
3878 * d.kill #=> #<Thread:0x401b3678 aborting>
3879 * a.status #=> nil
3880 * b.status #=> "sleep"
3881 * c.status #=> false
3882 * d.status #=> "aborting"
3883 * Thread.current.status #=> "run"
3884 *
3885 * See also the instance methods #alive? and #stop?
3886 */
3887
3888static VALUE
3889rb_thread_status(VALUE thread)
3890{
3891 rb_thread_t *target_th = rb_thread_ptr(thread);
3892
3893 if (rb_threadptr_dead(target_th)) {
3894 if (!NIL_P(target_th->ec->errinfo) &&
3895 !FIXNUM_P(target_th->ec->errinfo)) {
3896 return Qnil;
3897 }
3898 else {
3899 return Qfalse;
3900 }
3901 }
3902 else {
3903 return rb_str_new2(thread_status_name(target_th, FALSE));
3904 }
3905}
3906
3907
3908/*
3909 * call-seq:
3910 * thr.alive? -> true or false
3911 *
3912 * Returns +true+ if +thr+ is running or sleeping.
3913 *
3914 * thr = Thread.new { }
3915 * thr.join #=> #<Thread:0x401b3fb0 dead>
3916 * Thread.current.alive? #=> true
3917 * thr.alive? #=> false
3918 *
3919 * See also #stop? and #status.
3920 */
3921
3922static VALUE
3923rb_thread_alive_p(VALUE thread)
3924{
3925 return RBOOL(!thread_finished(rb_thread_ptr(thread)));
3926}
3927
3928/*
3929 * call-seq:
3930 * thr.stop? -> true or false
3931 *
3932 * Returns +true+ if +thr+ is dead or sleeping.
3933 *
3934 * a = Thread.new { Thread.stop }
3935 * b = Thread.current
3936 * a.stop? #=> true
3937 * b.stop? #=> false
3938 *
3939 * See also #alive? and #status.
3940 */
3941
3942static VALUE
3943rb_thread_stop_p(VALUE thread)
3944{
3945 rb_thread_t *th = rb_thread_ptr(thread);
3946
3947 if (rb_threadptr_dead(th)) {
3948 return Qtrue;
3949 }
3950 return RBOOL(th->status == THREAD_STOPPED || th->status == THREAD_STOPPED_FOREVER);
3951}
3952
3953/*
3954 * call-seq:
3955 * thr.name -> string
3956 *
3957 * show the name of the thread.
3958 */
3959
3960static VALUE
3961rb_thread_getname(VALUE thread)
3962{
3963 return rb_thread_ptr(thread)->name;
3964}
3965
3966/*
3967 * call-seq:
3968 * thr.name=(name) -> string
3969 *
3970 * set given name to the ruby thread.
3971 * On some platform, it may set the name to pthread and/or kernel.
3972 */
3973
3974static VALUE
3975rb_thread_setname(VALUE thread, VALUE name)
3976{
3977 rb_thread_t *target_th = rb_thread_ptr(thread);
3978
3979 if (!NIL_P(name)) {
3980 rb_encoding *enc;
3981 StringValueCStr(name);
3982 enc = rb_enc_get(name);
3983 if (!rb_enc_asciicompat(enc)) {
3984 rb_raise(rb_eArgError, "ASCII incompatible encoding (%s)",
3985 rb_enc_name(enc));
3986 }
3987 name = rb_str_new_frozen(name);
3988 }
3989 target_th->name = name;
3990 if (threadptr_initialized(target_th) && target_th->has_dedicated_nt &&
3991 target_th->status != THREAD_KILLED) {
3992 native_set_another_thread_name(target_th->nt->thread_id, name);
3993 }
3994 return name;
3995}
3996
3997#if USE_NATIVE_THREAD_NATIVE_THREAD_ID
3998/*
3999 * call-seq:
4000 * thr.native_thread_id -> integer
4001 *
4002 * Return the native thread ID which is used by the Ruby thread.
4003 *
4004 * The ID depends on the OS. (not POSIX thread ID returned by pthread_self(3))
4005 * * On Linux it is TID returned by gettid(2).
4006 * * On macOS it is the system-wide unique integral ID of thread returned
4007 * by pthread_threadid_np(3).
4008 * * On FreeBSD it is the unique integral ID of the thread returned by
4009 * pthread_getthreadid_np(3).
4010 * * On Windows it is the thread identifier returned by GetThreadId().
4011 * * On other platforms, it raises NotImplementedError.
4012 *
4013 * NOTE:
4014 * If the thread is not associated yet or already deassociated with a native
4015 * thread, it returns _nil_.
4016 * If the Ruby implementation uses M:N thread model, the ID may change
4017 * depending on the timing.
4018 */
4019
4020static VALUE
4021rb_thread_native_thread_id(VALUE thread)
4022{
4023 rb_thread_t *target_th = rb_thread_ptr(thread);
4024 if (rb_threadptr_dead(target_th)) return Qnil;
4025 return native_thread_native_thread_id(target_th);
4026}
4027#else
4028# define rb_thread_native_thread_id rb_f_notimplement
4029#endif
4030
4031/*
4032 * call-seq:
4033 * thr.to_s -> string
4034 *
4035 * Dump the name, id, and status of _thr_ to a string.
4036 */
4037
4038static VALUE
4039rb_thread_to_s(VALUE thread)
4040{
4041 VALUE cname = rb_class_path(rb_obj_class(thread));
4042 rb_thread_t *target_th = rb_thread_ptr(thread);
4043 const char *status;
4044 VALUE str, loc;
4045
4046 status = thread_status_name(target_th, TRUE);
4047 str = rb_sprintf("#<%"PRIsVALUE":%p", cname, (void *)thread);
4048 if (!NIL_P(target_th->name)) {
4049 rb_str_catf(str, "@%"PRIsVALUE, target_th->name);
4050 }
4051 if ((loc = threadptr_invoke_proc_location(target_th)) != Qnil) {
4052 rb_str_catf(str, " %"PRIsVALUE":%"PRIsVALUE,
4053 RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1));
4054 }
4055 rb_str_catf(str, " %s>", status);
4056
4057 return str;
4058}
4059
4060/* variables for recursive traversals */
4061#define recursive_key id__recursive_key__
4062
4063static VALUE
4064threadptr_local_aref(rb_thread_t *th, ID id)
4065{
4066 if (id == recursive_key) {
4067 return th->ec->local_storage_recursive_hash;
4068 }
4069 else {
4070 VALUE val;
4071 struct rb_id_table *local_storage = th->ec->local_storage;
4072
4073 if (local_storage != NULL && rb_id_table_lookup(local_storage, id, &val)) {
4074 return val;
4075 }
4076 else {
4077 return Qnil;
4078 }
4079 }
4080}
4081
4083rb_thread_local_aref(VALUE thread, ID id)
4084{
4085 return threadptr_local_aref(rb_thread_ptr(thread), id);
4086}
4087
4088/*
4089 * call-seq:
4090 * thr[sym] -> obj or nil
4091 *
4092 * Attribute Reference---Returns the value of a fiber-local variable (current thread's root fiber
4093 * if not explicitly inside a Fiber), using either a symbol or a string name.
4094 * If the specified variable does not exist, returns +nil+.
4095 *
4096 * [
4097 * Thread.new { Thread.current["name"] = "A" },
4098 * Thread.new { Thread.current[:name] = "B" },
4099 * Thread.new { Thread.current["name"] = "C" }
4100 * ].each do |th|
4101 * th.join
4102 * puts "#{th.inspect}: #{th[:name]}"
4103 * end
4104 *
4105 * This will produce:
4106 *
4107 * #<Thread:0x00000002a54220 dead>: A
4108 * #<Thread:0x00000002a541a8 dead>: B
4109 * #<Thread:0x00000002a54130 dead>: C
4110 *
4111 * Thread#[] and Thread#[]= are not thread-local but fiber-local.
4112 * This confusion did not exist in Ruby 1.8 because
4113 * fibers are only available since Ruby 1.9.
4114 * Ruby 1.9 chooses that the methods behaves fiber-local to save
4115 * following idiom for dynamic scope.
4116 *
4117 * def meth(newvalue)
4118 * begin
4119 * oldvalue = Thread.current[:name]
4120 * Thread.current[:name] = newvalue
4121 * yield
4122 * ensure
4123 * Thread.current[:name] = oldvalue
4124 * end
4125 * end
4126 *
4127 * The idiom may not work as dynamic scope if the methods are thread-local
4128 * and a given block switches fiber.
4129 *
4130 * f = Fiber.new {
4131 * meth(1) {
4132 * Fiber.yield
4133 * }
4134 * }
4135 * meth(2) {
4136 * f.resume
4137 * }
4138 * f.resume
4139 * p Thread.current[:name]
4140 * #=> nil if fiber-local
4141 * #=> 2 if thread-local (The value 2 is leaked to outside of meth method.)
4142 *
4143 * For thread-local variables, please see #thread_variable_get and
4144 * #thread_variable_set.
4145 *
4146 */
4147
4148static VALUE
4149rb_thread_aref(VALUE thread, VALUE key)
4150{
4151 ID id = rb_check_id(&key);
4152 if (!id) return Qnil;
4153 return rb_thread_local_aref(thread, id);
4154}
4155
4156/*
4157 * call-seq:
4158 * thr.fetch(sym) -> obj
4159 * thr.fetch(sym) { } -> obj
4160 * thr.fetch(sym, default) -> obj
4161 *
4162 * Returns a fiber-local for the given key. If the key can't be
4163 * found, there are several options: With no other arguments, it will
4164 * raise a KeyError exception; if <i>default</i> is given, then that
4165 * will be returned; if the optional code block is specified, then
4166 * that will be run and its result returned. See Thread#[] and
4167 * Hash#fetch.
4168 */
4169static VALUE
4170rb_thread_fetch(int argc, VALUE *argv, VALUE self)
4171{
4172 VALUE key, val;
4173 ID id;
4174 rb_thread_t *target_th = rb_thread_ptr(self);
4175 int block_given;
4176
4177 rb_check_arity(argc, 1, 2);
4178 key = argv[0];
4179
4180 block_given = rb_block_given_p();
4181 if (block_given && argc == 2) {
4182 rb_warn("block supersedes default value argument");
4183 }
4184
4185 id = rb_check_id(&key);
4186
4187 if (id == recursive_key) {
4188 return target_th->ec->local_storage_recursive_hash;
4189 }
4190 else if (id && target_th->ec->local_storage &&
4191 rb_id_table_lookup(target_th->ec->local_storage, id, &val)) {
4192 return val;
4193 }
4194 else if (block_given) {
4195 return rb_yield(key);
4196 }
4197 else if (argc == 1) {
4198 rb_key_err_raise(rb_sprintf("key not found: %+"PRIsVALUE, key), self, key);
4199 }
4200 else {
4201 return argv[1];
4202 }
4203}
4204
4205static VALUE
4206threadptr_local_aset(rb_thread_t *th, ID id, VALUE val)
4207{
4208 if (id == recursive_key) {
4209 th->ec->local_storage_recursive_hash = val;
4210 return val;
4211 }
4212 else {
4213 struct rb_id_table *local_storage = th->ec->local_storage;
4214
4215 if (NIL_P(val)) {
4216 if (!local_storage) return Qnil;
4217 rb_id_table_delete(local_storage, id);
4218 return Qnil;
4219 }
4220 else {
4221 if (local_storage == NULL) {
4222 th->ec->local_storage = local_storage = rb_id_table_create(0);
4223 }
4224 rb_id_table_insert(local_storage, id, val);
4225 return val;
4226 }
4227 }
4228}
4229
4231rb_thread_local_aset(VALUE thread, ID id, VALUE val)
4232{
4233 if (OBJ_FROZEN(thread)) {
4234 rb_frozen_error_raise(thread, "can't modify frozen thread locals");
4235 }
4236
4237 return threadptr_local_aset(rb_thread_ptr(thread), id, val);
4238}
4239
4240/*
4241 * call-seq:
4242 * thr[sym] = obj -> obj
4243 *
4244 * Attribute Assignment---Sets or creates the value of a fiber-local variable,
4245 * using either a symbol or a string.
4246 * Assigning +nil+ deletes the variable.
4247 *
4248 * See also Thread#[].
4249 *
4250 * For thread-local variables, please see #thread_variable_set and
4251 * #thread_variable_get.
4252 */
4253
4254static VALUE
4255rb_thread_aset(VALUE self, VALUE id, VALUE val)
4256{
4257 return rb_thread_local_aset(self, rb_to_id(id), val);
4258}
4259
4260/*
4261 * call-seq:
4262 * thr.thread_variable_get(key) -> obj or nil
4263 *
4264 * Returns the value of a thread local variable that has been set. Note that
4265 * these are different than fiber local values. For fiber local values,
4266 * please see Thread#[] and Thread#[]=.
4267 *
4268 * Thread local values are carried along with threads, and do not respect
4269 * fibers. For example:
4270 *
4271 * Thread.new {
4272 * Thread.current.thread_variable_set("foo", "bar") # set a thread local
4273 * Thread.current["foo"] = "bar" # set a fiber local
4274 *
4275 * Fiber.new {
4276 * Fiber.yield [
4277 * Thread.current.thread_variable_get("foo"), # get the thread local
4278 * Thread.current["foo"], # get the fiber local
4279 * ]
4280 * }.resume
4281 * }.join.value # => ['bar', nil]
4282 *
4283 * The value "bar" is returned for the thread local, where nil is returned
4284 * for the fiber local. The fiber is executed in the same thread, so the
4285 * thread local values are available.
4286 */
4287
4288static VALUE
4289rb_thread_variable_get(VALUE thread, VALUE key)
4290{
4291 VALUE locals;
4292 VALUE symbol = rb_to_symbol(key);
4293
4294 if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) {
4295 return Qnil;
4296 }
4297 locals = rb_thread_local_storage(thread);
4298 return rb_hash_aref(locals, symbol);
4299}
4300
4301/*
4302 * call-seq:
4303 * thr.thread_variable_set(key, value)
4304 *
4305 * Sets a thread local with +key+ to +value+. Note that these are local to
4306 * threads, and not to fibers. Please see Thread#thread_variable_get and
4307 * Thread#[] for more information.
4308 * Assigning +nil+ deletes the variable.
4309 */
4310
4311static VALUE
4312rb_thread_variable_set(VALUE thread, VALUE key, VALUE val)
4313{
4314 VALUE locals;
4315 VALUE symbol;
4316
4317 if (OBJ_FROZEN(thread)) {
4318 rb_frozen_error_raise(thread, "can't modify frozen thread locals");
4319 }
4320
4321 symbol = rb_to_symbol(key);
4322
4323 if (NIL_P(val)) {
4324 if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) {
4325 return Qnil;
4326 }
4327
4328 locals = rb_thread_local_storage(thread);
4329 rb_hash_delete(locals, symbol);
4330 return Qnil;
4331 }
4332
4333 locals = rb_thread_local_storage(thread);
4334 return rb_hash_aset(locals, symbol, val);
4335}
4336
4337/*
4338 * call-seq:
4339 * thr.key?(sym) -> true or false
4340 *
4341 * Returns +true+ if the given string (or symbol) exists as a fiber-local
4342 * variable.
4343 *
4344 * me = Thread.current
4345 * me[:oliver] = "a"
4346 * me.key?(:oliver) #=> true
4347 * me.key?(:stanley) #=> false
4348 */
4349
4350static VALUE
4351rb_thread_key_p(VALUE self, VALUE key)
4352{
4353 VALUE val;
4354 ID id = rb_check_id(&key);
4355 struct rb_id_table *local_storage = rb_thread_ptr(self)->ec->local_storage;
4356
4357 if (!id || local_storage == NULL) {
4358 return Qfalse;
4359 }
4360 return RBOOL(rb_id_table_lookup(local_storage, id, &val));
4361}
4362
4363static enum rb_id_table_iterator_result
4364thread_keys_i(ID key, VALUE value, void *ary)
4365{
4366 rb_ary_push((VALUE)ary, ID2SYM(key));
4367 return ID_TABLE_CONTINUE;
4368}
4369
4371rb_thread_alone(void)
4372{
4373 // TODO
4374 return rb_ractor_living_thread_num(GET_RACTOR()) == 1;
4375}
4376
4377/*
4378 * call-seq:
4379 * thr.keys -> array
4380 *
4381 * Returns an array of the names of the fiber-local variables (as Symbols).
4382 *
4383 * thr = Thread.new do
4384 * Thread.current[:cat] = 'meow'
4385 * Thread.current["dog"] = 'woof'
4386 * end
4387 * thr.join #=> #<Thread:0x401b3f10 dead>
4388 * thr.keys #=> [:dog, :cat]
4389 */
4390
4391static VALUE
4392rb_thread_keys(VALUE self)
4393{
4394 struct rb_id_table *local_storage = rb_thread_ptr(self)->ec->local_storage;
4395 VALUE ary = rb_ary_new();
4396
4397 if (local_storage) {
4398 rb_id_table_foreach(local_storage, thread_keys_i, (void *)ary);
4399 }
4400 return ary;
4401}
4402
4403static int
4404keys_i(VALUE key, VALUE value, VALUE ary)
4405{
4406 rb_ary_push(ary, key);
4407 return ST_CONTINUE;
4408}
4409
4410/*
4411 * call-seq:
4412 * thr.thread_variables -> array
4413 *
4414 * Returns an array of the names of the thread-local variables (as Symbols).
4415 *
4416 * thr = Thread.new do
4417 * Thread.current.thread_variable_set(:cat, 'meow')
4418 * Thread.current.thread_variable_set("dog", 'woof')
4419 * end
4420 * thr.join #=> #<Thread:0x401b3f10 dead>
4421 * thr.thread_variables #=> [:dog, :cat]
4422 *
4423 * Note that these are not fiber local variables. Please see Thread#[] and
4424 * Thread#thread_variable_get for more details.
4425 */
4426
4427static VALUE
4428rb_thread_variables(VALUE thread)
4429{
4430 VALUE locals;
4431 VALUE ary;
4432
4433 ary = rb_ary_new();
4434 if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) {
4435 return ary;
4436 }
4437 locals = rb_thread_local_storage(thread);
4438 rb_hash_foreach(locals, keys_i, ary);
4439
4440 return ary;
4441}
4442
4443/*
4444 * call-seq:
4445 * thr.thread_variable?(key) -> true or false
4446 *
4447 * Returns +true+ if the given string (or symbol) exists as a thread-local
4448 * variable.
4449 *
4450 * me = Thread.current
4451 * me.thread_variable_set(:oliver, "a")
4452 * me.thread_variable?(:oliver) #=> true
4453 * me.thread_variable?(:stanley) #=> false
4454 *
4455 * Note that these are not fiber local variables. Please see Thread#[] and
4456 * Thread#thread_variable_get for more details.
4457 */
4458
4459static VALUE
4460rb_thread_variable_p(VALUE thread, VALUE key)
4461{
4462 VALUE locals;
4463 VALUE symbol = rb_to_symbol(key);
4464
4465 if (LIKELY(!THREAD_LOCAL_STORAGE_INITIALISED_P(thread))) {
4466 return Qfalse;
4467 }
4468 locals = rb_thread_local_storage(thread);
4469
4470 return RBOOL(rb_hash_lookup(locals, symbol) != Qnil);
4471}
4472
4473/*
4474 * call-seq:
4475 * thr.priority -> integer
4476 *
4477 * Returns the priority of <i>thr</i>. Default is inherited from the
4478 * current thread which creating the new thread, or zero for the
4479 * initial main thread; higher-priority thread will run more frequently
4480 * than lower-priority threads (but lower-priority threads can also run).
4481 *
4482 * This is just hint for Ruby thread scheduler. It may be ignored on some
4483 * platform.
4484 *
4485 * Thread.current.priority #=> 0
4486 */
4487
4488static VALUE
4489rb_thread_priority(VALUE thread)
4490{
4491 return INT2NUM(rb_thread_ptr(thread)->priority);
4492}
4493
4494
4495/*
4496 * call-seq:
4497 * thr.priority= integer -> thr
4498 *
4499 * Sets the priority of <i>thr</i> to <i>integer</i>. Higher-priority threads
4500 * will run more frequently than lower-priority threads (but lower-priority
4501 * threads can also run).
4502 *
4503 * This is just hint for Ruby thread scheduler. It may be ignored on some
4504 * platform.
4505 *
4506 * count1 = count2 = 0
4507 * a = Thread.new do
4508 * loop { count1 += 1 }
4509 * end
4510 * a.priority = -1
4511 *
4512 * b = Thread.new do
4513 * loop { count2 += 1 }
4514 * end
4515 * b.priority = -2
4516 * sleep 1 #=> 1
4517 * count1 #=> 622504
4518 * count2 #=> 5832
4519 */
4520
4521static VALUE
4522rb_thread_priority_set(VALUE thread, VALUE prio)
4523{
4524 rb_thread_t *target_th = rb_thread_ptr(thread);
4525 int priority;
4526
4527#if USE_NATIVE_THREAD_PRIORITY
4528 target_th->priority = NUM2INT(prio);
4529 native_thread_apply_priority(th);
4530#else
4531 priority = NUM2INT(prio);
4532 if (priority > RUBY_THREAD_PRIORITY_MAX) {
4533 priority = RUBY_THREAD_PRIORITY_MAX;
4534 }
4535 else if (priority < RUBY_THREAD_PRIORITY_MIN) {
4536 priority = RUBY_THREAD_PRIORITY_MIN;
4537 }
4538 target_th->priority = (int8_t)priority;
4539#endif
4540 return INT2NUM(target_th->priority);
4541}
4542
4543/* for IO */
4544
4545#if defined(NFDBITS) && defined(HAVE_RB_FD_INIT)
4546
4547/*
4548 * several Unix platforms support file descriptors bigger than FD_SETSIZE
4549 * in select(2) system call.
4550 *
4551 * - Linux 2.2.12 (?)
4552 * - NetBSD 1.2 (src/sys/kern/sys_generic.c:1.25)
4553 * select(2) documents how to allocate fd_set dynamically.
4554 * http://netbsd.gw.com/cgi-bin/man-cgi?select++NetBSD-4.0
4555 * - FreeBSD 2.2 (src/sys/kern/sys_generic.c:1.19)
4556 * - OpenBSD 2.0 (src/sys/kern/sys_generic.c:1.4)
4557 * select(2) documents how to allocate fd_set dynamically.
4558 * http://www.openbsd.org/cgi-bin/man.cgi?query=select&manpath=OpenBSD+4.4
4559 * - Solaris 8 has select_large_fdset
4560 * - Mac OS X 10.7 (Lion)
4561 * select(2) returns EINVAL if nfds is greater than FD_SET_SIZE and
4562 * _DARWIN_UNLIMITED_SELECT (or _DARWIN_C_SOURCE) isn't defined.
4563 * https://developer.apple.com/library/archive/releasenotes/Darwin/SymbolVariantsRelNotes/index.html
4564 *
4565 * When fd_set is not big enough to hold big file descriptors,
4566 * it should be allocated dynamically.
4567 * Note that this assumes fd_set is structured as bitmap.
4568 *
4569 * rb_fd_init allocates the memory.
4570 * rb_fd_term free the memory.
4571 * rb_fd_set may re-allocates bitmap.
4572 *
4573 * So rb_fd_set doesn't reject file descriptors bigger than FD_SETSIZE.
4574 */
4575
4576void
4578{
4579 fds->maxfd = 0;
4580 fds->fdset = ALLOC(fd_set);
4581 FD_ZERO(fds->fdset);
4582}
4583
4584static inline size_t
4585fdset_memsize(int maxfd)
4586{
4587 size_t o = howmany(maxfd, NFDBITS) * sizeof(fd_mask);
4588 if (o < sizeof(fd_set)) {
4589 return sizeof(fd_set);
4590 }
4591 return o;
4592}
4593
4594void
4595rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src)
4596{
4597 size_t size = fdset_memsize(rb_fd_max(src));
4598 dst->maxfd = src->maxfd;
4599 dst->fdset = xmalloc(size);
4600 memcpy(dst->fdset, src->fdset, size);
4601}
4602
4603void
4605{
4606 ruby_xfree_sized(fds->fdset, fdset_memsize(fds->maxfd));
4607 fds->maxfd = 0;
4608 fds->fdset = 0;
4609}
4610
4611void
4613{
4614 if (fds->fdset)
4615 MEMZERO(fds->fdset, fd_mask, howmany(fds->maxfd, NFDBITS));
4616}
4617
4618static void
4619rb_fd_resize(int n, rb_fdset_t *fds)
4620{
4621 size_t m = fdset_memsize(n + 1);
4622 size_t o = fdset_memsize(fds->maxfd);
4623
4624 if (m > o) {
4625 fds->fdset = ruby_xrealloc_sized(fds->fdset, m, o);
4626 memset((char *)fds->fdset + o, 0, m - o);
4627 }
4628 if (n >= fds->maxfd) fds->maxfd = n + 1;
4629}
4630
4631void
4632rb_fd_set(int n, rb_fdset_t *fds)
4633{
4634 rb_fd_resize(n, fds);
4635 FD_SET(n, fds->fdset);
4636}
4637
4638void
4639rb_fd_clr(int n, rb_fdset_t *fds)
4640{
4641 if (n >= fds->maxfd) return;
4642 FD_CLR(n, fds->fdset);
4643}
4644
4645int
4646rb_fd_isset(int n, const rb_fdset_t *fds)
4647{
4648 if (n >= fds->maxfd) return 0;
4649 return FD_ISSET(n, fds->fdset) != 0; /* "!= 0" avoids FreeBSD PR 91421 */
4650}
4651
4652void
4653rb_fd_copy(rb_fdset_t *dst, const fd_set *src, int max)
4654{
4655 size_t size = fdset_memsize(max);
4656 dst->fdset = ruby_xrealloc_sized(dst->fdset, size, fdset_memsize(dst->maxfd));
4657 dst->maxfd = max;
4658 memcpy(dst->fdset, src, size);
4659}
4660
4661void
4662rb_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src)
4663{
4664 size_t size = fdset_memsize(rb_fd_max(src));
4665 dst->fdset = ruby_xrealloc_sized(dst->fdset, size, fdset_memsize(dst->maxfd));
4666 dst->maxfd = src->maxfd;
4667 memcpy(dst->fdset, src->fdset, size);
4668}
4669
4670int
4671rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout)
4672{
4673 fd_set *r = NULL, *w = NULL, *e = NULL;
4674 if (readfds) {
4675 rb_fd_resize(n - 1, readfds);
4676 r = rb_fd_ptr(readfds);
4677 }
4678 if (writefds) {
4679 rb_fd_resize(n - 1, writefds);
4680 w = rb_fd_ptr(writefds);
4681 }
4682 if (exceptfds) {
4683 rb_fd_resize(n - 1, exceptfds);
4684 e = rb_fd_ptr(exceptfds);
4685 }
4686 return select(n, r, w, e, timeout);
4687}
4688
4689#define rb_fd_no_init(fds) ((void)((fds)->fdset = 0), (void)((fds)->maxfd = 0))
4690
4691#undef FD_ZERO
4692#undef FD_SET
4693#undef FD_CLR
4694#undef FD_ISSET
4695
4696#define FD_ZERO(f) rb_fd_zero(f)
4697#define FD_SET(i, f) rb_fd_set((i), (f))
4698#define FD_CLR(i, f) rb_fd_clr((i), (f))
4699#define FD_ISSET(i, f) rb_fd_isset((i), (f))
4700
4701#elif defined(_WIN32)
4702
4703void
4705{
4706 set->capa = FD_SETSIZE;
4707 set->fdset = ALLOC(fd_set);
4708 FD_ZERO(set->fdset);
4709}
4710
4711void
4712rb_fd_init_copy(rb_fdset_t *dst, rb_fdset_t *src)
4713{
4714 rb_fd_init(dst);
4715 rb_fd_dup(dst, src);
4716}
4717
4718static inline size_t
4719fdset_memsize(int capa)
4720{
4721 return offsetof(fd_set, fd_array) + (capa * sizeof(SOCKET));
4722}
4723
4724void
4726{
4727 ruby_xfree_sized(set->fdset, fdset_memsize(set->capa));
4728 set->fdset = NULL;
4729 set->capa = 0;
4730}
4731
4732void
4733rb_fd_set(int fd, rb_fdset_t *set)
4734{
4735 unsigned int i;
4736 SOCKET s = rb_w32_get_osfhandle(fd);
4737
4738 for (i = 0; i < set->fdset->fd_count; i++) {
4739 if (set->fdset->fd_array[i] == s) {
4740 return;
4741 }
4742 }
4743 if (set->fdset->fd_count >= (unsigned)set->capa) {
4744 set->capa = (set->fdset->fd_count / FD_SETSIZE + 1) * FD_SETSIZE;
4745 set->fdset =
4746 rb_xrealloc_mul_add(
4747 set->fdset, set->capa, sizeof(SOCKET), offsetof(fd_set, fd_array));
4748 }
4749 set->fdset->fd_array[set->fdset->fd_count++] = s;
4750}
4751
4752#undef FD_ZERO
4753#undef FD_SET
4754#undef FD_CLR
4755#undef FD_ISSET
4756
4757#define FD_ZERO(f) rb_fd_zero(f)
4758#define FD_SET(i, f) rb_fd_set((i), (f))
4759#define FD_CLR(i, f) rb_fd_clr((i), (f))
4760#define FD_ISSET(i, f) rb_fd_isset((i), (f))
4761
4762#define rb_fd_no_init(fds) (void)((fds)->fdset = 0)
4763
4764#endif
4765
4766#ifndef rb_fd_no_init
4767#define rb_fd_no_init(fds) (void)(fds)
4768#endif
4769
4770static int
4771wait_retryable(volatile int *result, int errnum, rb_hrtime_t *rel, rb_hrtime_t end)
4772{
4773 int r = *result;
4774 if (r < 0) {
4775 switch (errnum) {
4776 case EINTR:
4777#ifdef ERESTART
4778 case ERESTART:
4779#endif
4780 *result = 0;
4781 if (rel && hrtime_update_expire(rel, end)) {
4782 *rel = 0;
4783 }
4784 return TRUE;
4785 }
4786 return FALSE;
4787 }
4788 else if (r == 0) {
4789 /* check for spurious wakeup */
4790 if (rel) {
4791 return !hrtime_update_expire(rel, end);
4792 }
4793 return TRUE;
4794 }
4795 return FALSE;
4796}
4798struct select_set {
4799 int max;
4800 rb_thread_t *th;
4801 rb_fdset_t *rset;
4802 rb_fdset_t *wset;
4803 rb_fdset_t *eset;
4804 rb_fdset_t orig_rset;
4805 rb_fdset_t orig_wset;
4806 rb_fdset_t orig_eset;
4807 struct timeval *timeout;
4808};
4809
4810static VALUE
4811select_set_free(VALUE p)
4812{
4813 struct select_set *set = (struct select_set *)p;
4814
4815 rb_fd_term(&set->orig_rset);
4816 rb_fd_term(&set->orig_wset);
4817 rb_fd_term(&set->orig_eset);
4818
4819 return Qfalse;
4820}
4821
4822static VALUE
4823do_select(VALUE p)
4824{
4825 struct select_set *set = (struct select_set *)p;
4826 volatile int result = 0;
4827 int lerrno;
4828 rb_hrtime_t *to, rel, end = 0;
4829
4830 timeout_prepare(&to, &rel, &end, set->timeout);
4831 volatile rb_hrtime_t endtime = end;
4832#define restore_fdset(dst, src) \
4833 ((dst) ? rb_fd_dup(dst, src) : (void)0)
4834#define do_select_update() \
4835 (restore_fdset(set->rset, &set->orig_rset), \
4836 restore_fdset(set->wset, &set->orig_wset), \
4837 restore_fdset(set->eset, &set->orig_eset), \
4838 TRUE)
4839
4840 do {
4841 // A skipped select is not a timeout; report EINTR so wait_retryable selects again.
4842 result = -1;
4843 lerrno = EINTR;
4844
4845 BLOCKING_REGION(set->th, {
4846 struct timeval tv;
4847
4848 if (!RUBY_VM_INTERRUPTED(set->th->ec)) {
4849 result = native_fd_select(set->max,
4850 set->rset, set->wset, set->eset,
4851 rb_hrtime2timeval(&tv, to), set->th);
4852 lerrno = result < 0 ? errno : 0;
4853 }
4854 }, ubf_select, set->th, TRUE);
4855
4856 RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec); /* may raise */
4857 } while (wait_retryable(&result, lerrno, to, endtime) && do_select_update());
4858
4859 RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec);
4860
4861 if (result < 0) {
4862 errno = lerrno;
4863 }
4864
4865 return (VALUE)result;
4866}
4867
4869rb_thread_fd_select(int max, rb_fdset_t * read, rb_fdset_t * write, rb_fdset_t * except,
4870 struct timeval *timeout)
4871{
4872 struct select_set set;
4873
4874 set.th = GET_THREAD();
4875 RUBY_VM_CHECK_INTS_BLOCKING(set.th->ec);
4876 set.max = max;
4877 set.rset = read;
4878 set.wset = write;
4879 set.eset = except;
4880 set.timeout = timeout;
4881
4882 if (!set.rset && !set.wset && !set.eset) {
4883 if (!timeout) {
4885 return 0;
4886 }
4887 rb_thread_wait_for(*timeout);
4888 return 0;
4889 }
4890
4891#define fd_init_copy(f) do { \
4892 if (set.f) { \
4893 rb_fd_resize(set.max - 1, set.f); \
4894 if (&set.orig_##f != set.f) { /* sigwait_fd */ \
4895 rb_fd_init_copy(&set.orig_##f, set.f); \
4896 } \
4897 } \
4898 else { \
4899 rb_fd_no_init(&set.orig_##f); \
4900 } \
4901 } while (0)
4902 fd_init_copy(rset);
4903 fd_init_copy(wset);
4904 fd_init_copy(eset);
4905#undef fd_init_copy
4906
4907 return (int)rb_ensure(do_select, (VALUE)&set, select_set_free, (VALUE)&set);
4908}
4909
4910#ifdef USE_POLL
4911
4912/* The same with linux kernel. TODO: make platform independent definition. */
4913#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)
4914#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
4915#define POLLEX_SET (POLLPRI)
4916
4917#ifndef POLLERR_SET /* defined for FreeBSD for now */
4918# define POLLERR_SET (0)
4919#endif
4920
4921static int
4922wait_for_single_fd_blocking_region(rb_thread_t *th, struct pollfd *fds, nfds_t nfds,
4923 rb_hrtime_t *const to, volatile int *lerrno)
4924{
4925 struct timespec ts;
4926 // A skipped ppoll is not a timeout; report EINTR so wait_retryable polls again.
4927 volatile int result = -1;
4928
4929 *lerrno = EINTR;
4930 BLOCKING_REGION(th, {
4931 if (!RUBY_VM_INTERRUPTED(th->ec)) {
4932 result = ppoll(fds, nfds, rb_hrtime2timespec(&ts, to), 0);
4933 *lerrno = result < 0 ? errno : 0;
4934 }
4935 }, ubf_select, th, TRUE);
4936 return result;
4937}
4938
4939/*
4940 * returns a mask of events
4941 */
4942static int
4943thread_io_wait(rb_thread_t *th, struct rb_io *io, int fd, int events, struct timeval *timeout)
4944{
4945 struct pollfd fds[1] = {{
4946 .fd = fd,
4947 .events = (short)events,
4948 .revents = 0,
4949 }};
4950 volatile int result = 0;
4951 nfds_t nfds;
4952 struct rb_io_blocking_operation blocking_operation;
4953 enum ruby_tag_type state = TAG_NONE;
4954 volatile int lerrno;
4955
4956 RUBY_ASSERT(th);
4957 rb_execution_context_t *ec = th->ec;
4958
4959 if (io) {
4960 blocking_operation.ec = ec;
4961 blocking_operation.scheduler_interrupt_target = Qnil;
4962COMPILER_WARNING_PUSH
4963#if RBIMPL_COMPILER_SINCE(GCC, 12, 0, 0)
4964COMPILER_WARNING_IGNORED(-Wdangling-pointer)
4965#endif
4966 // rb_io_blocking_operation_exit() below unlinks it on every path.
4967 rb_io_blocking_operation_enter(io, &blocking_operation);
4968COMPILER_WARNING_POP
4969 }
4970
4971 struct timeval tv_rest;
4972 struct timeval *volatile wait_timeout = timeout;
4973 const rb_hrtime_t deadline = timeout ? rb_hrtime_add(rb_hrtime_now(), rb_timeval2hrtime(timeout)) : 0;
4974
4975 // The interrupt checks below may raise; the tag lets the exit path unlink blocking_operation.
4976 EC_PUSH_TAG(ec);
4977 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
4978 enum io_wait_result mn_result;
4979
4980 while (1) {
4981 // A zero timeout is a plain probe; ppoll answers it without parking.
4982 bool mn_wait = wait_timeout == NULL || wait_timeout->tv_sec != 0 || wait_timeout->tv_usec != 0;
4983 mn_result = mn_wait ? thread_io_wait_events(th, fd, events, wait_timeout, false) : io_wait_unhandled;
4984 if (mn_result == io_wait_ready) break;
4985
4986 // Charge what the M:N wait spent against the deadline the caller asked for.
4987 rb_hrtime_t rest = 0;
4988 if (wait_timeout) {
4989 rb_hrtime_t now = rb_hrtime_now();
4990 rest = now < deadline ? deadline - now : 0;
4991 rb_hrtime2timeval(&tv_rest, &rest);
4992 wait_timeout = &tv_rest;
4993 }
4994 if (mn_result == io_wait_unhandled) break;
4995 if (mn_result == io_wait_timed_out && wait_timeout && rest == 0) break;
4996
4997 // Stopped without an event before the deadline (an interrupt, or a wakeup
4998 // for a masked one): handle it and wait again. At the deadline this probes once.
4999 RUBY_VM_CHECK_INTS_BLOCKING(ec);
5000 }
5001
5002 switch (mn_result) {
5003 case io_wait_ready:
5004 fds[0].revents = events;
5005 errno = 0;
5006 break;
5007 case io_wait_timed_out:
5008 // revents stays 0, so the result below becomes 0 as with ppoll's timeout.
5009 errno = 0;
5010 break;
5011 case io_wait_unhandled: {
5012 rb_hrtime_t *to, rel, end = 0;
5013 RUBY_VM_CHECK_INTS_BLOCKING(ec);
5014 timeout_prepare(&to, &rel, &end, wait_timeout);
5015 do {
5016 nfds = numberof(fds);
5017 result = wait_for_single_fd_blocking_region(th, fds, nfds, to, &lerrno);
5018
5019 RUBY_VM_CHECK_INTS_BLOCKING(ec);
5020 } while (wait_retryable(&result, lerrno, to, end));
5021
5022 RUBY_VM_CHECK_INTS_BLOCKING(ec);
5023 break;
5024 }
5025 case io_wait_interrupted:
5027 }
5028 }
5029 EC_POP_TAG();
5030
5031 if (io) {
5032 rb_io_blocking_operation_exit(io, &blocking_operation);
5033 }
5034
5035 if (state) {
5036 EC_JUMP_TAG(ec, state);
5037 }
5038
5039 if (result < 0) {
5040 errno = lerrno;
5041 return -1;
5042 }
5043
5044 if (fds[0].revents & POLLNVAL) {
5045 errno = EBADF;
5046 return -1;
5047 }
5048
5049 /*
5050 * POLLIN, POLLOUT have a different meanings from select(2)'s read/write bit.
5051 * Therefore we need to fix it up.
5052 */
5053 result = 0;
5054 if (fds[0].revents & POLLIN_SET)
5055 result |= RB_WAITFD_IN;
5056 if (fds[0].revents & POLLOUT_SET)
5057 result |= RB_WAITFD_OUT;
5058 if (fds[0].revents & POLLEX_SET)
5059 result |= RB_WAITFD_PRI;
5060
5061 /* all requested events are ready if there is an error */
5062 if (fds[0].revents & POLLERR_SET)
5063 result |= events;
5064
5065 return result;
5066}
5067#else /* ! USE_POLL - implement rb_io_poll_fd() using select() */
5068struct select_args {
5069 struct rb_io *io;
5070 struct rb_io_blocking_operation *blocking_operation;
5071
5072 union {
5073 int fd;
5074 int error;
5075 } as;
5076 rb_fdset_t *read;
5077 rb_fdset_t *write;
5078 rb_fdset_t *except;
5079 struct timeval *tv;
5080};
5081
5082static VALUE
5083select_single(VALUE ptr)
5084{
5085 struct select_args *args = (struct select_args *)ptr;
5086 int r;
5087
5088 r = rb_thread_fd_select(args->as.fd + 1,
5089 args->read, args->write, args->except, args->tv);
5090 if (r == -1)
5091 args->as.error = errno;
5092 if (r > 0) {
5093 r = 0;
5094 if (args->read && rb_fd_isset(args->as.fd, args->read))
5095 r |= RB_WAITFD_IN;
5096 if (args->write && rb_fd_isset(args->as.fd, args->write))
5097 r |= RB_WAITFD_OUT;
5098 if (args->except && rb_fd_isset(args->as.fd, args->except))
5099 r |= RB_WAITFD_PRI;
5100 }
5101 return (VALUE)r;
5102}
5103
5104static VALUE
5105select_single_cleanup(VALUE ptr)
5106{
5107 struct select_args *args = (struct select_args *)ptr;
5108
5109 if (args->blocking_operation) {
5110 rb_io_blocking_operation_exit(args->io, args->blocking_operation);
5111 }
5112
5113 if (args->read) rb_fd_term(args->read);
5114 if (args->write) rb_fd_term(args->write);
5115 if (args->except) rb_fd_term(args->except);
5116
5117 return (VALUE)-1;
5118}
5119
5120static rb_fdset_t *
5121init_set_fd(int fd, rb_fdset_t *fds)
5122{
5123 if (fd < 0) {
5124 return 0;
5125 }
5126 rb_fd_init(fds);
5127 rb_fd_set(fd, fds);
5128
5129 return fds;
5130}
5131
5132static int
5133thread_io_wait(rb_thread_t *th, struct rb_io *io, int fd, int events, struct timeval *timeout)
5134{
5135 rb_fdset_t rfds, wfds, efds;
5136 struct select_args args;
5137 VALUE ptr = (VALUE)&args;
5138
5139 struct rb_io_blocking_operation blocking_operation;
5140 if (io) {
5141 args.io = io;
5142 blocking_operation.ec = th->ec;
5143 blocking_operation.scheduler_interrupt_target = Qnil;
5144 rb_io_blocking_operation_enter(io, &blocking_operation);
5145 args.blocking_operation = &blocking_operation;
5146 }
5147 else {
5148 args.io = NULL;
5149 blocking_operation.ec = NULL;
5150 blocking_operation.scheduler_interrupt_target = Qnil;
5151 args.blocking_operation = NULL;
5152 }
5153
5154 args.as.fd = fd;
5155 args.read = (events & RB_WAITFD_IN) ? init_set_fd(fd, &rfds) : NULL;
5156 args.write = (events & RB_WAITFD_OUT) ? init_set_fd(fd, &wfds) : NULL;
5157 args.except = (events & RB_WAITFD_PRI) ? init_set_fd(fd, &efds) : NULL;
5158 args.tv = timeout;
5159
5160 int result = (int)rb_ensure(select_single, ptr, select_single_cleanup, ptr);
5161 if (result == -1)
5162 errno = args.as.error;
5163
5164 return result;
5165}
5166#endif /* ! USE_POLL */
5167
5168int
5169rb_thread_wait_for_single_fd(rb_thread_t *th, int fd, int events, struct timeval *timeout)
5170{
5171 return thread_io_wait(th, NULL, fd, events, timeout);
5172}
5173
5174int
5175rb_thread_io_wait(rb_thread_t *th, struct rb_io *io, int events, struct timeval * timeout)
5176{
5177 return thread_io_wait(th, io, io->fd, events, timeout);
5178}
5179
5180/*
5181 * for GC
5182 */
5183
5184#ifdef USE_CONSERVATIVE_STACK_END
5185void
5186rb_gc_set_stack_end(VALUE **stack_end_p)
5187{
5188 VALUE stack_end;
5189COMPILER_WARNING_PUSH
5190#if RBIMPL_COMPILER_SINCE(GCC, 12, 0, 0)
5191COMPILER_WARNING_IGNORED(-Wdangling-pointer);
5192#endif
5193 *stack_end_p = &stack_end;
5194COMPILER_WARNING_POP
5195}
5196#endif
5197
5198/*
5199 *
5200 */
5201
5202void
5203rb_threadptr_check_signal(rb_thread_t *mth)
5204{
5205 /* mth must be main_thread */
5206 if (rb_signal_buff_size() > 0) {
5207 /* wakeup main thread */
5208 threadptr_trap_interrupt(mth);
5209 }
5210}
5211
5212static void
5213async_bug_fd(const char *mesg, int errno_arg, int fd)
5214{
5215 char buff[64];
5216 size_t n = strlcpy(buff, mesg, sizeof(buff));
5217 if (n < sizeof(buff)-3) {
5218 ruby_snprintf(buff+n, sizeof(buff)-n, "(%d)", fd);
5219 }
5220 rb_async_bug_errno(buff, errno_arg);
5221}
5222
5223/* VM-dependent API is not available for this function */
5224static int
5225consume_communication_pipe(int fd)
5226{
5227#if USE_EVENTFD
5228 uint64_t buff[1];
5229#else
5230 /* buffer can be shared because no one refers to them. */
5231 static char buff[1024];
5232#endif
5233 ssize_t result;
5234 int ret = FALSE; /* for rb_sigwait_sleep */
5235
5236 while (1) {
5237 result = read(fd, buff, sizeof(buff));
5238#if USE_EVENTFD
5239 RUBY_DEBUG_LOG("resultf:%d buff:%lu", (int)result, (unsigned long)buff[0]);
5240#else
5241 RUBY_DEBUG_LOG("result:%d", (int)result);
5242#endif
5243 if (result > 0) {
5244 ret = TRUE;
5245 if (USE_EVENTFD || result < (ssize_t)sizeof(buff)) {
5246 return ret;
5247 }
5248 }
5249 else if (result == 0) {
5250 return ret;
5251 }
5252 else if (result < 0) {
5253 int e = errno;
5254 switch (e) {
5255 case EINTR:
5256 continue; /* retry */
5257 case EAGAIN:
5258#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
5259 case EWOULDBLOCK:
5260#endif
5261 return ret;
5262 default:
5263 async_bug_fd("consume_communication_pipe: read", e, fd);
5264 }
5265 }
5266 }
5267}
5268
5269void
5270rb_thread_stop_timer_thread(void)
5271{
5272 if (TIMER_THREAD_CREATED_P() && native_stop_timer_thread()) {
5273 native_reset_timer_thread();
5274 }
5275}
5276
5277void
5278rb_thread_reset_timer_thread(void)
5279{
5280 native_reset_timer_thread();
5281}
5282
5283void
5284rb_thread_start_timer_thread(void)
5285{
5286 system_working = 1;
5287 rb_thread_create_timer_thread();
5288}
5289
5290static int
5291clear_coverage_i(st_data_t key, st_data_t val, st_data_t dummy)
5292{
5293 int i;
5294 VALUE coverage = (VALUE)val;
5295 VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
5296 VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES);
5297
5298 if (lines) {
5299 if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) {
5300 rb_ary_clear(lines);
5301 }
5302 else {
5303 int i;
5304 for (i = 0; i < RARRAY_LEN(lines); i++) {
5305 if (RARRAY_AREF(lines, i) != Qnil)
5306 RARRAY_ASET(lines, i, INT2FIX(0));
5307 }
5308 }
5309 }
5310 if (branches) {
5311 VALUE counters = RARRAY_AREF(branches, 1);
5312 for (i = 0; i < RARRAY_LEN(counters); i++) {
5313 RARRAY_ASET(counters, i, INT2FIX(0));
5314 }
5315 }
5316
5317 return ST_CONTINUE;
5318}
5319
5320void
5321rb_clear_coverages(void)
5322{
5323 VALUE coverages = rb_get_coverages();
5324 if (RTEST(coverages)) {
5325 rb_hash_foreach(coverages, clear_coverage_i, 0);
5326 }
5327}
5328
5329#if defined(HAVE_WORKING_FORK)
5330
5331static void
5332rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const rb_thread_t *))
5333{
5334 rb_thread_t *i = 0;
5335 rb_vm_t *vm = th->vm;
5336 rb_ractor_t *r = th->ractor;
5337 vm->ractor.main_ractor = r;
5338 vm->ractor.main_thread = th;
5339 r->threads.main = th;
5340 r->status_ = ractor_created;
5341
5342 thread_sched_atfork(TH_SCHED(th));
5343 ubf_list_atfork();
5344 rb_signal_atfork();
5345
5346 // OK. Only this thread accesses:
5347 rb_ractor_t *r_next;
5348 ccan_list_for_each_safe(&vm->ractor.set, r, r_next, vmlr_node) {
5349 if (r != vm->ractor.main_ractor) {
5350 rb_ractor_terminate_atfork(vm, r);
5351 }
5352 ccan_list_for_each(&r->threads.set, i, lt_node) {
5353 atfork(i, th);
5354 }
5355 }
5356
5357 ccan_list_head_init(&vm->ractor.set);
5358
5359 rb_ractor_atfork(vm, th);
5360 rb_vm_postponed_job_atfork();
5361
5362 /* may be held by any thread in parent */
5363 rb_native_mutex_initialize(&th->interrupt_lock);
5364 rb_native_mutex_initialize(&vm->once_lock);
5365 rb_native_cond_initialize(&vm->once_cond);
5366 rb_jit_cont_init(); // cont.c's jit_cont_lock, likewise
5367 rb_gc_zombie_objspaces_atfork();
5368 rb_gc_atfork_global_locks();
5369 rb_generic_fields_lock_atfork();
5370 rb_fiber_pool_lock_atfork();
5371 ccan_list_head_init(&th->interrupt_exec_tasks);
5372
5373 vm->fork_gen++;
5374 rb_ractor_sleeper_threads_clear(th->ractor);
5375 rb_clear_coverages();
5376
5377 // restart timer thread (timer threads access to `vm->waitpid_lock` and so on.
5378 rb_thread_reset_timer_thread();
5379 rb_thread_start_timer_thread();
5380
5381 VM_ASSERT(vm->ractor.blocking_cnt == 0);
5382 VM_ASSERT(vm->ractor.cnt == 1);
5383}
5384
5385static void
5386terminate_atfork_i(rb_thread_t *th, const rb_thread_t *current_th)
5387{
5388 if (th != current_th) {
5389 // Clear the scheduler as it is no longer operational:
5390 th->scheduler = Qnil;
5391
5392 rb_native_mutex_initialize(&th->interrupt_lock);
5393 rb_mutex_abandon_keeping_mutexes(th);
5394 rb_mutex_abandon_locking_mutex(th);
5395 thread_cleanup_func(th, TRUE);
5396 }
5397}
5398
5399void rb_fiber_atfork(rb_thread_t *);
5400void
5401rb_thread_atfork(void)
5402{
5403 rb_thread_t *th = GET_THREAD();
5404 rb_threadptr_pending_interrupt_clear(th);
5405 rb_thread_atfork_internal(th, terminate_atfork_i);
5406 th->join_list = NULL;
5407 th->scheduler = Qnil;
5408 rb_fiber_atfork(th);
5409
5410 /* We don't want reproduce CVE-2003-0900. */
5412}
5413
5414static void
5415terminate_atfork_before_exec_i(rb_thread_t *th, const rb_thread_t *current_th)
5416{
5417 if (th != current_th) {
5418 thread_cleanup_func_before_exec(th);
5419 }
5420}
5421
5422void
5424{
5425 rb_thread_t *th = GET_THREAD();
5426 rb_thread_atfork_internal(th, terminate_atfork_before_exec_i);
5427}
5428#else
5429void
5430rb_thread_atfork(void)
5431{
5432}
5433
5434void
5436{
5437}
5438#endif
5440struct thgroup {
5441 int enclosed;
5442};
5443
5444static const rb_data_type_t thgroup_data_type = {
5445 "thgroup",
5446 {
5447 0,
5449 NULL, // No external memory to report
5450 },
5451 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
5452};
5453
5454/*
5455 * Document-class: ThreadGroup
5456 *
5457 * ThreadGroup provides a means of keeping track of a number of threads as a
5458 * group.
5459 *
5460 * A given Thread object can only belong to one ThreadGroup at a time; adding
5461 * a thread to a new group will remove it from any previous group.
5462 *
5463 * Newly created threads belong to the same group as the thread from which they
5464 * were created.
5465 */
5466
5467/*
5468 * Document-const: Default
5469 *
5470 * The default ThreadGroup created when Ruby starts; all Threads belong to it
5471 * by default.
5472 */
5473static VALUE
5474thgroup_s_alloc(VALUE klass)
5475{
5476 VALUE group;
5477 struct thgroup *data;
5478
5479 group = TypedData_Make_Struct(klass, struct thgroup, &thgroup_data_type, data);
5480 data->enclosed = 0;
5481
5482 return group;
5483}
5484
5485/*
5486 * call-seq:
5487 * thgrp.list -> array
5488 *
5489 * Returns an array of all existing Thread objects that belong to this group.
5490 *
5491 * ThreadGroup::Default.list #=> [#<Thread:0x401bdf4c run>]
5492 */
5493
5494static VALUE
5495thgroup_list(VALUE group)
5496{
5497 VALUE ary = rb_ary_new();
5498 rb_thread_t *th = 0;
5499 rb_ractor_t *r = GET_RACTOR();
5500
5501 ccan_list_for_each(&r->threads.set, th, lt_node) {
5502 if (th->thgroup == group) {
5503 rb_ary_push(ary, th->self);
5504 }
5505 }
5506 return ary;
5507}
5508
5509
5510/*
5511 * call-seq:
5512 * thgrp.enclose -> thgrp
5513 *
5514 * Prevents threads from being added to or removed from the receiving
5515 * ThreadGroup.
5516 *
5517 * New threads can still be started in an enclosed ThreadGroup.
5518 *
5519 * ThreadGroup::Default.enclose #=> #<ThreadGroup:0x4029d914>
5520 * thr = Thread.new { Thread.stop } #=> #<Thread:0x402a7210 sleep>
5521 * tg = ThreadGroup.new #=> #<ThreadGroup:0x402752d4>
5522 * tg.add thr
5523 * #=> ThreadError: can't move from the enclosed thread group
5524 */
5525
5526static VALUE
5527thgroup_enclose(VALUE group)
5528{
5529 struct thgroup *data;
5530
5531 TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
5532 data->enclosed = 1;
5533
5534 return group;
5535}
5536
5537
5538/*
5539 * call-seq:
5540 * thgrp.enclosed? -> true or false
5541 *
5542 * Returns +true+ if the +thgrp+ is enclosed. See also ThreadGroup#enclose.
5543 */
5544
5545static VALUE
5546thgroup_enclosed_p(VALUE group)
5547{
5548 struct thgroup *data;
5549
5550 TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
5551 return RBOOL(data->enclosed);
5552}
5553
5554
5555/*
5556 * call-seq:
5557 * thgrp.add(thread) -> thgrp
5558 *
5559 * Adds the given +thread+ to this group, removing it from any other
5560 * group to which it may have previously been a member.
5561 *
5562 * puts "Initial group is #{ThreadGroup::Default.list}"
5563 * tg = ThreadGroup.new
5564 * t1 = Thread.new { sleep }
5565 * t2 = Thread.new { sleep }
5566 * puts "t1 is #{t1}"
5567 * puts "t2 is #{t2}"
5568 * tg.add(t1)
5569 * puts "Initial group now #{ThreadGroup::Default.list}"
5570 * puts "tg group now #{tg.list}"
5571 *
5572 * This will produce:
5573 *
5574 * Initial group is #<Thread:0x401bdf4c>
5575 * t1 is #<Thread:0x401b3c90>
5576 * t2 is #<Thread:0x401b3c18>
5577 * Initial group now #<Thread:0x401b3c18>#<Thread:0x401bdf4c>
5578 * tg group now #<Thread:0x401b3c90>
5579 */
5580
5581static VALUE
5582thgroup_add(VALUE group, VALUE thread)
5583{
5584 rb_thread_t *target_th = rb_thread_ptr(thread);
5585 struct thgroup *data;
5586
5587 if (OBJ_FROZEN(group)) {
5588 rb_raise(rb_eThreadError, "can't move to the frozen thread group");
5589 }
5590 TypedData_Get_Struct(group, struct thgroup, &thgroup_data_type, data);
5591 if (data->enclosed) {
5592 rb_raise(rb_eThreadError, "can't move to the enclosed thread group");
5593 }
5594
5595 if (OBJ_FROZEN(target_th->thgroup)) {
5596 rb_raise(rb_eThreadError, "can't move from the frozen thread group");
5597 }
5598 TypedData_Get_Struct(target_th->thgroup, struct thgroup, &thgroup_data_type, data);
5599 if (data->enclosed) {
5600 rb_raise(rb_eThreadError,
5601 "can't move from the enclosed thread group");
5602 }
5603
5604 target_th->thgroup = group;
5605 return group;
5606}
5607
5608/*
5609 * Document-class: ThreadShield
5610 */
5611static void
5612thread_shield_mark(void *ptr)
5613{
5614 rb_gc_mark((VALUE)ptr);
5615}
5616
5617static const rb_data_type_t thread_shield_data_type = {
5618 "thread_shield",
5619 {thread_shield_mark, 0, 0,},
5620 0, 0, RUBY_TYPED_THREAD_SAFE_FREE
5621};
5622
5623static VALUE
5624thread_shield_alloc(VALUE klass)
5625{
5626 return TypedData_Wrap_Struct(klass, &thread_shield_data_type, (void *)mutex_alloc(0));
5627}
5628
5629#define GetThreadShieldPtr(obj) ((VALUE)rb_check_typeddata((obj), &thread_shield_data_type))
5630#define THREAD_SHIELD_WAITING_MASK (((FL_USER19-1)&~(FL_USER0-1))|FL_USER19)
5631#define THREAD_SHIELD_WAITING_SHIFT (FL_USHIFT)
5632#define THREAD_SHIELD_WAITING_MAX (THREAD_SHIELD_WAITING_MASK>>THREAD_SHIELD_WAITING_SHIFT)
5633STATIC_ASSERT(THREAD_SHIELD_WAITING_MAX, THREAD_SHIELD_WAITING_MAX <= UINT_MAX);
5634static inline unsigned int
5635rb_thread_shield_waiting(VALUE b)
5636{
5637 return ((RBASIC(b)->flags&THREAD_SHIELD_WAITING_MASK)>>THREAD_SHIELD_WAITING_SHIFT);
5638}
5639
5640static inline void
5641rb_thread_shield_waiting_inc(VALUE b)
5642{
5643 unsigned int w = rb_thread_shield_waiting(b);
5644 w++;
5645 if (w > THREAD_SHIELD_WAITING_MAX)
5646 rb_raise(rb_eRuntimeError, "waiting count overflow");
5647 RBASIC(b)->flags &= ~THREAD_SHIELD_WAITING_MASK;
5648 RBASIC(b)->flags |= ((VALUE)w << THREAD_SHIELD_WAITING_SHIFT);
5649}
5650
5651static inline void
5652rb_thread_shield_waiting_dec(VALUE b)
5653{
5654 unsigned int w = rb_thread_shield_waiting(b);
5655 if (!w) rb_raise(rb_eRuntimeError, "waiting count underflow");
5656 w--;
5657 RBASIC(b)->flags &= ~THREAD_SHIELD_WAITING_MASK;
5658 RBASIC(b)->flags |= ((VALUE)w << THREAD_SHIELD_WAITING_SHIFT);
5659}
5660
5661VALUE
5662rb_thread_shield_new(void)
5663{
5664 VALUE thread_shield = thread_shield_alloc(rb_cThreadShield);
5665 rb_mutex_lock((VALUE)DATA_PTR(thread_shield));
5666 return thread_shield;
5667}
5668
5669bool
5670rb_thread_shield_owned(VALUE self)
5671{
5672 VALUE mutex = GetThreadShieldPtr(self);
5673 if (!mutex) return false;
5674
5675 rb_mutex_t *m = mutex_ptr(mutex);
5676
5677 return m->ec_serial == rb_ec_serial(GET_EC());
5678}
5679
5680/*
5681 * Wait a thread shield.
5682 *
5683 * Returns
5684 * true: acquired the thread shield
5685 * false: the thread shield was destroyed and no other threads waiting
5686 * nil: the thread shield was destroyed but still in use
5687 */
5688VALUE
5689rb_thread_shield_wait(VALUE self)
5690{
5691 VALUE mutex = GetThreadShieldPtr(self);
5692 rb_mutex_t *m;
5693
5694 if (!mutex) return Qfalse;
5695 m = mutex_ptr(mutex);
5696 if (m->ec_serial == rb_ec_serial(GET_EC())) return Qnil;
5697 rb_thread_shield_waiting_inc(self);
5698 rb_mutex_lock(mutex);
5699 rb_thread_shield_waiting_dec(self);
5700 if (DATA_PTR(self)) return Qtrue;
5701 rb_mutex_unlock(mutex);
5702 return rb_thread_shield_waiting(self) > 0 ? Qnil : Qfalse;
5703}
5704
5705static VALUE
5706thread_shield_get_mutex(VALUE self)
5707{
5708 VALUE mutex = GetThreadShieldPtr(self);
5709 if (!mutex)
5710 rb_raise(rb_eThreadError, "destroyed thread shield - %p", (void *)self);
5711 return mutex;
5712}
5713
5714/*
5715 * Release a thread shield, and return true if it has waiting threads.
5716 */
5717VALUE
5718rb_thread_shield_release(VALUE self)
5719{
5720 VALUE mutex = thread_shield_get_mutex(self);
5721 rb_mutex_unlock(mutex);
5722 return RBOOL(rb_thread_shield_waiting(self) > 0);
5723}
5724
5725/*
5726 * Release and destroy a thread shield, and return true if it has waiting threads.
5727 */
5728VALUE
5729rb_thread_shield_destroy(VALUE self)
5730{
5731 VALUE mutex = thread_shield_get_mutex(self);
5732 DATA_PTR(self) = 0;
5733 rb_mutex_unlock(mutex);
5734 return RBOOL(rb_thread_shield_waiting(self) > 0);
5735}
5736
5737static VALUE
5738threadptr_recursive_hash(rb_thread_t *th)
5739{
5740 return th->ec->local_storage_recursive_hash;
5741}
5742
5743static void
5744threadptr_recursive_hash_set(rb_thread_t *th, VALUE hash)
5745{
5746 th->ec->local_storage_recursive_hash = hash;
5747}
5748
5750
5751/*
5752 * Returns the current "recursive list" used to detect recursion.
5753 * This list is a hash table, unique for the current thread and for
5754 * the current __callee__.
5755 */
5756
5757static VALUE
5758recursive_list_access(VALUE sym)
5759{
5760 rb_thread_t *th = GET_THREAD();
5761 VALUE hash = threadptr_recursive_hash(th);
5762 VALUE list;
5763 if (NIL_P(hash) || !RB_TYPE_P(hash, T_HASH)) {
5764 hash = rb_ident_hash_new();
5765 threadptr_recursive_hash_set(th, hash);
5766 list = Qnil;
5767 }
5768 else {
5769 list = rb_hash_aref(hash, sym);
5770 }
5771 if (NIL_P(list) || !RB_TYPE_P(list, T_HASH)) {
5772 list = rb_ident_hash_new();
5773 rb_hash_aset(hash, sym, list);
5774 }
5775 return list;
5776}
5777
5778/*
5779 * Returns true if and only if obj (or the pair <obj, paired_obj>) is already
5780 * in the recursion list.
5781 * Assumes the recursion list is valid.
5782 */
5783
5784static bool
5785recursive_check(VALUE list, VALUE obj, VALUE paired_obj_id)
5786{
5787#if SIZEOF_LONG == SIZEOF_VOIDP
5788 #define OBJ_ID_EQL(obj_id, other) ((obj_id) == (other))
5789#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
5790 #define OBJ_ID_EQL(obj_id, other) (RB_BIGNUM_TYPE_P((obj_id)) ? \
5791 rb_big_eql((obj_id), (other)) : ((obj_id) == (other)))
5792#endif
5793
5794 VALUE pair_list = rb_hash_lookup2(list, obj, Qundef);
5795 if (UNDEF_P(pair_list))
5796 return false;
5797 if (paired_obj_id) {
5798 if (!RB_TYPE_P(pair_list, T_HASH)) {
5799 if (!OBJ_ID_EQL(paired_obj_id, pair_list))
5800 return false;
5801 }
5802 else {
5803 if (NIL_P(rb_hash_lookup(pair_list, paired_obj_id)))
5804 return false;
5805 }
5806 }
5807 return true;
5808}
5809
5810/*
5811 * Pushes obj (or the pair <obj, paired_obj>) in the recursion list.
5812 * For a single obj, it sets list[obj] to Qtrue.
5813 * For a pair, it sets list[obj] to paired_obj_id if possible,
5814 * otherwise list[obj] becomes a hash like:
5815 * {paired_obj_id_1 => true, paired_obj_id_2 => true, ... }
5816 * Assumes the recursion list is valid.
5817 */
5818
5819static void
5820recursive_push(VALUE list, VALUE obj, VALUE paired_obj)
5821{
5822 VALUE pair_list;
5823
5824 if (!paired_obj) {
5825 rb_hash_aset(list, obj, Qtrue);
5826 }
5827 else if (UNDEF_P(pair_list = rb_hash_lookup2(list, obj, Qundef))) {
5828 rb_hash_aset(list, obj, paired_obj);
5829 }
5830 else {
5831 if (!RB_TYPE_P(pair_list, T_HASH)){
5832 VALUE other_paired_obj = pair_list;
5833 pair_list = rb_hash_new();
5834 rb_hash_aset(pair_list, other_paired_obj, Qtrue);
5835 rb_hash_aset(list, obj, pair_list);
5836 }
5837 rb_hash_aset(pair_list, paired_obj, Qtrue);
5838 }
5839}
5840
5841/*
5842 * Pops obj (or the pair <obj, paired_obj>) from the recursion list.
5843 * For a pair, if list[obj] is a hash, then paired_obj_id is
5844 * removed from the hash and no attempt is made to simplify
5845 * list[obj] from {only_one_paired_id => true} to only_one_paired_id
5846 * Assumes the recursion list is valid.
5847 */
5848
5849static int
5850recursive_pop(VALUE list, VALUE obj, VALUE paired_obj)
5851{
5852 if (paired_obj) {
5853 VALUE pair_list = rb_hash_lookup2(list, obj, Qundef);
5854 if (UNDEF_P(pair_list)) {
5855 return 0;
5856 }
5857 if (RB_TYPE_P(pair_list, T_HASH)) {
5858 rb_hash_delete_entry(pair_list, paired_obj);
5859 if (!RHASH_EMPTY_P(pair_list)) {
5860 return 1; /* keep hash until is empty */
5861 }
5862 }
5863 }
5864 rb_hash_delete_entry(list, obj);
5865 return 1;
5866}
5868struct exec_recursive_params {
5869 VALUE (*func) (VALUE, VALUE, int);
5870 VALUE list;
5871 VALUE obj;
5872 VALUE pairid;
5873 VALUE arg;
5874};
5875
5876static VALUE
5877exec_recursive_i(RB_BLOCK_CALL_FUNC_ARGLIST(tag, data))
5878{
5879 struct exec_recursive_params *p = (void *)data;
5880 return (*p->func)(p->obj, p->arg, FALSE);
5881}
5882
5883/*
5884 * Calls func(obj, arg, recursive), where recursive is non-zero if the
5885 * current method is called recursively on obj, or on the pair <obj, pairid>
5886 * If outer is 0, then the innermost func will be called with recursive set
5887 * to true, otherwise the outermost func will be called. In the latter case,
5888 * all inner func are short-circuited by throw.
5889 * Implementation details: the value thrown is the recursive list which is
5890 * proper to the current method and unlikely to be caught anywhere else.
5891 * list[recursive_key] is used as a flag for the outermost call.
5892 */
5893
5894static VALUE
5895exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE pairid, VALUE arg, int outer, ID mid)
5896{
5897 VALUE result = Qundef;
5898 const VALUE sym = mid ? ID2SYM(mid) : ID2SYM(idNULL);
5899 struct exec_recursive_params p;
5900 int outermost;
5901 p.list = recursive_list_access(sym);
5902 p.obj = obj;
5903 p.pairid = pairid;
5904 p.arg = arg;
5905 outermost = outer && !recursive_check(p.list, ID2SYM(recursive_key), 0);
5906
5907 if (recursive_check(p.list, p.obj, pairid)) {
5908 if (outer && !outermost) {
5909 rb_throw_obj(p.list, p.list);
5910 }
5911 return (*func)(obj, arg, TRUE);
5912 }
5913 else {
5914 enum ruby_tag_type state;
5915
5916 p.func = func;
5917
5918 if (outermost) {
5919 recursive_push(p.list, ID2SYM(recursive_key), 0);
5920 recursive_push(p.list, p.obj, p.pairid);
5921 result = rb_catch_protect(p.list, exec_recursive_i, (VALUE)&p, &state);
5922 if (!recursive_pop(p.list, p.obj, p.pairid)) goto invalid;
5923 if (!recursive_pop(p.list, ID2SYM(recursive_key), 0)) goto invalid;
5924 if (state != TAG_NONE) EC_JUMP_TAG(GET_EC(), state);
5925 if (result == p.list) {
5926 result = (*func)(obj, arg, TRUE);
5927 }
5928 }
5929 else {
5930 volatile VALUE ret = Qundef;
5931 recursive_push(p.list, p.obj, p.pairid);
5932 EC_PUSH_TAG(GET_EC());
5933 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
5934 ret = (*func)(obj, arg, FALSE);
5935 }
5936 EC_POP_TAG();
5937 if (!recursive_pop(p.list, p.obj, p.pairid)) {
5938 goto invalid;
5939 }
5940 if (state != TAG_NONE) EC_JUMP_TAG(GET_EC(), state);
5941 result = ret;
5942 }
5943 }
5944 *(volatile struct exec_recursive_params *)&p;
5945 return result;
5946
5947 invalid:
5948 rb_raise(rb_eTypeError, "invalid inspect_tbl pair_list "
5949 "for %+"PRIsVALUE" in %+"PRIsVALUE,
5950 sym, rb_thread_current());
5952}
5953
5954/*
5955 * Calls func(obj, arg, recursive), where recursive is non-zero if the
5956 * current method is called recursively on obj
5957 */
5958
5959VALUE
5960rb_exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg)
5961{
5962 return exec_recursive(func, obj, 0, arg, 0, rb_frame_last_func());
5963}
5964
5965/*
5966 * Calls func(obj, arg, recursive), where recursive is non-zero if the
5967 * current method is called recursively on the ordered pair <obj, paired_obj>
5968 */
5969
5970VALUE
5971rb_exec_recursive_paired(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE paired_obj, VALUE arg)
5972{
5973 return exec_recursive(func, obj, rb_memory_id(paired_obj), arg, 0, rb_frame_last_func());
5974}
5975
5976/*
5977 * If recursion is detected on the current method and obj, the outermost
5978 * func will be called with (obj, arg, true). All inner func will be
5979 * short-circuited using throw.
5980 */
5981
5982VALUE
5983rb_exec_recursive_outer(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg)
5984{
5985 return exec_recursive(func, obj, 0, arg, 1, rb_frame_last_func());
5986}
5987
5988VALUE
5989rb_exec_recursive_outer_mid(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg, ID mid)
5990{
5991 return exec_recursive(func, obj, 0, arg, 1, mid);
5992}
5993
5994/*
5995 * If recursion is detected on the current method, obj and paired_obj,
5996 * the outermost func will be called with (obj, arg, true). All inner
5997 * func will be short-circuited using throw.
5998 */
5999
6000VALUE
6001rb_exec_recursive_paired_outer(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE paired_obj, VALUE arg)
6002{
6003 return exec_recursive(func, obj, rb_memory_id(paired_obj), arg, 1, rb_frame_last_func());
6004}
6005
6006/*
6007 * call-seq:
6008 * thread.backtrace -> array or nil
6009 *
6010 * Returns the current backtrace of the target thread.
6011 *
6012 */
6013
6014static VALUE
6015rb_thread_backtrace_m(int argc, VALUE *argv, VALUE thval)
6016{
6017 return rb_vm_thread_backtrace(argc, argv, thval);
6018}
6019
6020/* call-seq:
6021 * thread.backtrace_locations(*args) -> array or nil
6022 *
6023 * Returns the execution stack for the target thread---an array containing
6024 * backtrace location objects.
6025 *
6026 * See Thread::Backtrace::Location for more information.
6027 *
6028 * This method behaves similarly to Kernel#caller_locations except it applies
6029 * to a specific thread.
6030 */
6031static VALUE
6032rb_thread_backtrace_locations_m(int argc, VALUE *argv, VALUE thval)
6033{
6034 return rb_vm_thread_backtrace_locations(argc, argv, thval);
6035}
6036
6037void
6038Init_Thread_Mutex(void)
6039{
6040 rb_thread_t *th = GET_THREAD();
6041
6042 rb_native_mutex_initialize(&th->vm->once_lock);
6043 rb_native_cond_initialize(&th->vm->once_cond);
6044 rb_native_mutex_initialize(&th->interrupt_lock);
6045}
6046
6047/*
6048 * Document-class: ThreadError
6049 *
6050 * Raised when an invalid operation is attempted on a thread.
6051 *
6052 * For example, when no other thread has been started:
6053 *
6054 * Thread.stop
6055 *
6056 * This will raises the following exception:
6057 *
6058 * ThreadError: stopping only thread
6059 * note: use sleep to stop forever
6060 */
6061
6062void
6063Init_Thread(void)
6064{
6065 rb_thread_t *th = GET_THREAD();
6066
6067 sym_never = ID2SYM(rb_intern_const("never"));
6068 sym_immediate = ID2SYM(rb_intern_const("immediate"));
6069 sym_on_blocking = ID2SYM(rb_intern_const("on_blocking"));
6070
6071 rb_define_singleton_method(rb_cThread, "new", thread_s_new, -1);
6072 rb_define_singleton_method(rb_cThread, "start", thread_start, -2);
6073 rb_define_singleton_method(rb_cThread, "fork", thread_start, -2);
6074 rb_define_singleton_method(rb_cThread, "main", rb_thread_s_main, 0);
6075 rb_define_singleton_method(rb_cThread, "current", thread_s_current, 0);
6076 rb_define_singleton_method(rb_cThread, "stop", thread_stop, 0);
6077 rb_define_singleton_method(rb_cThread, "kill", rb_thread_s_kill, 1);
6078 rb_define_singleton_method(rb_cThread, "exit", rb_thread_exit, 0);
6079 rb_define_singleton_method(rb_cThread, "pass", thread_s_pass, 0);
6080 rb_define_singleton_method(rb_cThread, "list", thread_list, 0);
6081 rb_define_singleton_method(rb_cThread, "abort_on_exception", rb_thread_s_abort_exc, 0);
6082 rb_define_singleton_method(rb_cThread, "abort_on_exception=", rb_thread_s_abort_exc_set, 1);
6083 rb_define_singleton_method(rb_cThread, "report_on_exception", rb_thread_s_report_exc, 0);
6084 rb_define_singleton_method(rb_cThread, "report_on_exception=", rb_thread_s_report_exc_set, 1);
6085 rb_define_singleton_method(rb_cThread, "ignore_deadlock", rb_thread_s_ignore_deadlock, 0);
6086 rb_define_singleton_method(rb_cThread, "ignore_deadlock=", rb_thread_s_ignore_deadlock_set, 1);
6087 rb_define_singleton_method(rb_cThread, "handle_interrupt", rb_thread_s_handle_interrupt, 1);
6088 rb_define_singleton_method(rb_cThread, "pending_interrupt?", rb_thread_s_pending_interrupt_p, -1);
6089 rb_define_method(rb_cThread, "pending_interrupt?", rb_thread_pending_interrupt_p, -1);
6090
6091 rb_define_method(rb_cThread, "initialize", thread_initialize, -2);
6092 rb_define_method(rb_cThread, "raise", thread_raise_m, -1);
6093 rb_define_method(rb_cThread, "join", thread_join_m, -1);
6094 rb_define_method(rb_cThread, "value", thread_value, 0);
6095 rb_define_method(rb_cThread, "kill", rb_thread_kill, 0);
6096 rb_define_method(rb_cThread, "terminate", rb_thread_kill, 0);
6097 rb_define_method(rb_cThread, "exit", rb_thread_kill, 0);
6098 rb_define_method(rb_cThread, "run", rb_thread_run, 0);
6099 rb_define_method(rb_cThread, "wakeup", rb_thread_wakeup, 0);
6100 rb_define_method(rb_cThread, "[]", rb_thread_aref, 1);
6101 rb_define_method(rb_cThread, "[]=", rb_thread_aset, 2);
6102 rb_define_method(rb_cThread, "fetch", rb_thread_fetch, -1);
6103 rb_define_method(rb_cThread, "key?", rb_thread_key_p, 1);
6104 rb_define_method(rb_cThread, "keys", rb_thread_keys, 0);
6105 rb_define_method(rb_cThread, "priority", rb_thread_priority, 0);
6106 rb_define_method(rb_cThread, "priority=", rb_thread_priority_set, 1);
6107 rb_define_method(rb_cThread, "status", rb_thread_status, 0);
6108 rb_define_method(rb_cThread, "thread_variable_get", rb_thread_variable_get, 1);
6109 rb_define_method(rb_cThread, "thread_variable_set", rb_thread_variable_set, 2);
6110 rb_define_method(rb_cThread, "thread_variables", rb_thread_variables, 0);
6111 rb_define_method(rb_cThread, "thread_variable?", rb_thread_variable_p, 1);
6112 rb_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0);
6113 rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0);
6114 rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0);
6115 rb_define_method(rb_cThread, "abort_on_exception=", rb_thread_abort_exc_set, 1);
6116 rb_define_method(rb_cThread, "report_on_exception", rb_thread_report_exc, 0);
6117 rb_define_method(rb_cThread, "report_on_exception=", rb_thread_report_exc_set, 1);
6118 rb_define_method(rb_cThread, "group", rb_thread_group, 0);
6119 rb_define_method(rb_cThread, "backtrace", rb_thread_backtrace_m, -1);
6120 rb_define_method(rb_cThread, "backtrace_locations", rb_thread_backtrace_locations_m, -1);
6121
6122 rb_define_method(rb_cThread, "name", rb_thread_getname, 0);
6123 rb_define_method(rb_cThread, "name=", rb_thread_setname, 1);
6124 rb_define_method(rb_cThread, "native_thread_id", rb_thread_native_thread_id, 0);
6125 rb_define_method(rb_cThread, "to_s", rb_thread_to_s, 0);
6126 rb_define_alias(rb_cThread, "inspect", "to_s");
6127
6128 rb_vm_register_special_exception(ruby_error_stream_closed, rb_eIOError,
6129 "stream closed in another thread");
6130
6131 cThGroup = rb_define_class("ThreadGroup", rb_cObject);
6132 rb_define_alloc_func(cThGroup, thgroup_s_alloc);
6133 rb_define_method(cThGroup, "list", thgroup_list, 0);
6134 rb_define_method(cThGroup, "enclose", thgroup_enclose, 0);
6135 rb_define_method(cThGroup, "enclosed?", thgroup_enclosed_p, 0);
6136 rb_define_method(cThGroup, "add", thgroup_add, 1);
6137
6138 const char * ptr = getenv("RUBY_THREAD_TIMESLICE");
6139
6140 if (ptr) {
6141 long quantum = strtol(ptr, NULL, 0);
6142 if (quantum > 0 && !(SIZEOF_LONG > 4 && quantum > UINT32_MAX)) {
6143 thread_default_quantum_ms = (uint32_t)quantum;
6144 }
6145 else if (0) {
6146 fprintf(stderr, "Ignored RUBY_THREAD_TIMESLICE=%s\n", ptr);
6147 }
6148 }
6149
6150 {
6151 th->thgroup = th->ractor->thgroup_default = rb_obj_alloc(cThGroup);
6152 rb_define_const(cThGroup, "Default", th->thgroup);
6153 }
6154
6155 rb_eThreadError = rb_define_class("ThreadError", rb_eStandardError);
6156
6157 /* init thread core */
6158 {
6159 /* main thread setting */
6160 {
6161 /* acquire global vm lock */
6162#ifdef HAVE_PTHREAD_NP_H
6163 VM_ASSERT(TH_SCHED(th)->running == th);
6164#endif
6165 // thread_sched_to_running() should not be called because
6166 // it assumes blocked by thread_sched_to_waiting().
6167 // thread_sched_to_running(sched, th);
6168
6169 th->pending_interrupt_queue = rb_ary_hidden_new(0);
6170 th->pending_interrupt_queue_checked = 0;
6171 th->pending_interrupt_mask_stack = rb_ary_hidden_new(0);
6172 }
6173 }
6174
6175 rb_thread_create_timer_thread();
6176
6177 Init_thread_sync();
6178
6179 // TODO: Suppress unused function warning for now
6180 // if (0) rb_thread_sched_destroy(NULL);
6181}
6182
6185{
6186 rb_thread_t *th = ruby_thread_from_native();
6187
6188 return th != 0;
6189}
6190
6191#ifdef NON_SCALAR_THREAD_ID
6192 #define thread_id_str(th) (NULL)
6193#else
6194 #define thread_id_str(th) ((void *)(uintptr_t)(th)->nt->thread_id)
6195#endif
6196
6197static void
6198debug_deadlock_check(rb_ractor_t *r, VALUE msg)
6199{
6200 rb_thread_t *th = 0;
6201 VALUE sep = rb_str_new_cstr("\n ");
6202
6203 rb_str_catf(msg, "\n%d threads, %d sleeps current:%p main thread:%p\n",
6204 rb_ractor_living_thread_num(r), rb_ractor_sleeper_thread_num(r),
6205 (void *)GET_THREAD(), (void *)r->threads.main);
6206
6207 ccan_list_for_each(&r->threads.set, th, lt_node) {
6208 rb_str_catf(msg, "* %+"PRIsVALUE"\n rb_thread_t:%p "
6209 "native:%p int:%u",
6210 th->self, (void *)th, th->nt ? thread_id_str(th) : "N/A", th->ec->interrupt_flag);
6211
6212 if (th->locking_mutex) {
6213 rb_mutex_t *mutex = mutex_ptr(th->locking_mutex);
6214 rb_str_catf(msg, " mutex:%llu cond:%"PRIuSIZE,
6215 (unsigned long long)mutex->ec_serial, rb_mutex_num_waiting(mutex));
6216 }
6217
6218 {
6219 struct rb_waiting_list *list = th->join_list;
6220 while (list) {
6221 rb_str_catf(msg, "\n depended by: tb_thread_id:%p", (void *)list->thread);
6222 list = list->next;
6223 }
6224 }
6225 rb_str_catf(msg, "\n ");
6226 rb_str_concat(msg, rb_ary_join(rb_ec_backtrace_str_ary(th->ec, RUBY_BACKTRACE_START, RUBY_ALL_BACKTRACE_LINES), sep));
6227 rb_str_catf(msg, "\n");
6228 }
6229}
6230
6231static void
6232rb_check_deadlock(rb_ractor_t *r)
6233{
6234 if (GET_THREAD()->vm->thread_ignore_deadlock) return;
6235
6236 if (r->threads.sched.readyq_cnt > 0) return;
6237
6238 int sleeper_num = rb_ractor_sleeper_thread_num(r);
6239 int ltnum = rb_ractor_living_thread_num(r);
6240
6241 if (ltnum > sleeper_num) return;
6242 if (ltnum < sleeper_num) rb_bug("sleeper must not be more than vm_living_thread_num(vm)");
6243
6244 int found = 0;
6245 rb_thread_t *th = NULL;
6246
6247 ccan_list_for_each(&r->threads.set, th, lt_node) {
6248 if (th->status != THREAD_STOPPED_FOREVER || RUBY_VM_INTERRUPTED(th->ec)) {
6249 found = 1;
6250 }
6251 else if (th->locking_mutex) {
6252 rb_mutex_t *mutex = mutex_ptr(th->locking_mutex);
6253 if (mutex->ec_serial == rb_ec_serial(th->ec) || (!mutex->ec_serial && !ccan_list_empty(&mutex->waitq))) {
6254 found = 1;
6255 }
6256 }
6257 if (found)
6258 break;
6259 }
6260
6261 if (!found) {
6262 VALUE argv[2];
6263 argv[0] = rb_eFatal;
6264 argv[1] = rb_str_new2("No live threads left. Deadlock?");
6265 debug_deadlock_check(r, argv[1]);
6266 rb_ractor_sleeper_threads_dec(GET_RACTOR());
6267 rb_threadptr_raise(r->threads.main, 2, argv);
6268 }
6269}
6270
6271static void
6272update_line_coverage(VALUE data, const rb_trace_arg_t *trace_arg)
6273{
6274 const rb_control_frame_t *cfp = GET_EC()->cfp;
6275 VALUE coverage = rb_iseq_coverage(CFP_ISEQ(cfp));
6276 if (RB_TYPE_P(coverage, T_ARRAY) && !RBASIC_CLASS(coverage)) {
6277 VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
6278 if (lines) {
6279 long line = rb_sourceline() - 1;
6280 VM_ASSERT(line >= 0);
6281 long count;
6282 VALUE num;
6283 void rb_iseq_clear_event_flags(const rb_iseq_t *iseq, size_t pos, rb_event_flag_t reset);
6284 if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) {
6285 rb_iseq_clear_event_flags(CFP_ISEQ(cfp), CFP_PC(cfp) - ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded - 1, RUBY_EVENT_COVERAGE_LINE);
6286 rb_ary_push(lines, LONG2FIX(line + 1));
6287 return;
6288 }
6289 if (line >= RARRAY_LEN(lines)) { /* no longer tracked */
6290 return;
6291 }
6292 num = RARRAY_AREF(lines, line);
6293 if (!FIXNUM_P(num)) return;
6294 count = FIX2LONG(num) + 1;
6295 if (POSFIXABLE(count)) {
6296 RARRAY_ASET(lines, line, LONG2FIX(count));
6297 }
6298 }
6299 }
6300}
6301
6302static void
6303update_branch_coverage(VALUE data, const rb_trace_arg_t *trace_arg)
6304{
6305 const rb_control_frame_t *cfp = GET_EC()->cfp;
6306 VALUE coverage = rb_iseq_coverage(CFP_ISEQ(cfp));
6307 if (RB_TYPE_P(coverage, T_ARRAY) && !RBASIC_CLASS(coverage)) {
6308 VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES);
6309 if (branches) {
6310 long pc = CFP_PC(cfp) - ISEQ_BODY(CFP_ISEQ(cfp))->iseq_encoded - 1;
6311 long idx = FIX2INT(RARRAY_AREF(ISEQ_PC2BRANCHINDEX(CFP_ISEQ(cfp)), pc)), count;
6312 VALUE counters = RARRAY_AREF(branches, 1);
6313 VALUE num = RARRAY_AREF(counters, idx);
6314 count = FIX2LONG(num) + 1;
6315 if (POSFIXABLE(count)) {
6316 RARRAY_ASET(counters, idx, LONG2FIX(count));
6317 }
6318 }
6319 }
6320}
6321
6322const rb_method_entry_t *
6323rb_resolve_me_location(const rb_method_entry_t *me, VALUE resolved_location[5])
6324{
6325 VALUE path, beg_pos_lineno, beg_pos_column, end_pos_lineno, end_pos_column;
6326
6327 if (!me->def) return NULL; // negative cme
6328
6329 retry:
6330 switch (me->def->type) {
6331 case VM_METHOD_TYPE_ISEQ: {
6332 const rb_iseq_t *iseq = me->def->body.iseq.iseqptr;
6333 rb_iseq_location_t *loc = &ISEQ_BODY(iseq)->location;
6334 path = rb_iseq_path(iseq);
6335 beg_pos_lineno = INT2FIX(loc->code_location.beg_pos.lineno);
6336 beg_pos_column = INT2FIX(loc->code_location.beg_pos.column);
6337 end_pos_lineno = INT2FIX(loc->code_location.end_pos.lineno);
6338 end_pos_column = INT2FIX(loc->code_location.end_pos.column);
6339 break;
6340 }
6341 case VM_METHOD_TYPE_BMETHOD: {
6342 const rb_iseq_t *iseq = rb_proc_get_iseq(me->def->body.bmethod.proc, 0);
6343 if (iseq) {
6344 rb_iseq_location_t *loc;
6345 rb_iseq_check(iseq);
6346 path = rb_iseq_path(iseq);
6347 loc = &ISEQ_BODY(iseq)->location;
6348 beg_pos_lineno = INT2FIX(loc->code_location.beg_pos.lineno);
6349 beg_pos_column = INT2FIX(loc->code_location.beg_pos.column);
6350 end_pos_lineno = INT2FIX(loc->code_location.end_pos.lineno);
6351 end_pos_column = INT2FIX(loc->code_location.end_pos.column);
6352 break;
6353 }
6354 return NULL;
6355 }
6356 case VM_METHOD_TYPE_ALIAS:
6357 me = me->def->body.alias.original_me;
6358 goto retry;
6359 case VM_METHOD_TYPE_REFINED:
6360 me = me->def->body.refined.orig_me;
6361 if (!me) return NULL;
6362 goto retry;
6363 default:
6364 return NULL;
6365 }
6366
6367 /* found */
6368 if (RB_TYPE_P(path, T_ARRAY)) {
6369 path = rb_ary_entry(path, 1);
6370 if (!RB_TYPE_P(path, T_STRING)) return NULL; /* just for the case... */
6371 }
6372 if (resolved_location) {
6373 resolved_location[0] = path;
6374 resolved_location[1] = beg_pos_lineno;
6375 resolved_location[2] = beg_pos_column;
6376 resolved_location[3] = end_pos_lineno;
6377 resolved_location[4] = end_pos_column;
6378 }
6379 return me;
6380}
6382struct method_coverage_arg {
6383 rb_coverage_method_callback *callback;
6384 void *data;
6385};
6386
6387/* Fills *out for the method entry `me_v` and returns true, or returns false
6388 * if the method entry is not a subject of method coverage (aliases,
6389 * complemented entries, and methods without a source location). */
6390bool
6391rb_coverage_method_data_of(VALUE me_v, VALUE count, struct rb_coverage_method_data *out)
6392{
6393 const rb_method_entry_t *me = (const rb_method_entry_t *)me_v;
6394 VALUE location[5];
6395 const rb_method_entry_t *resolved_me = rb_resolve_me_location(me, location);
6396
6397 if (me != resolved_me || RB_TYPE_P(me->owner, T_ICLASS) ||
6398 FIX2LONG(location[1]) <= 0) return false;
6399
6400 out->owner = me->owner;
6401 out->method_id = ID2SYM(me->def->original_id);
6402 out->path = location[0];
6403 out->first_lineno = location[1];
6404 out->first_column = location[2];
6405 out->last_lineno = location[3];
6406 out->last_column = location[4];
6407 out->count = count;
6408 return true;
6409}
6410
6411static void
6412method_coverage_call(const rb_method_entry_t *me, VALUE count,
6413 struct method_coverage_arg *arg)
6414{
6415 struct rb_coverage_method_data method;
6416 if (rb_coverage_method_data_of((VALUE)me, count, &method)) {
6417 arg->callback(&method, arg->data);
6418 }
6419}
6420
6421static int
6422method_coverage_me_i(VALUE me, VALUE value, VALUE data)
6423{
6424 method_coverage_call((const rb_method_entry_t *)me, INT2FIX(0),
6425 (struct method_coverage_arg *)data);
6426 return ST_CONTINUE;
6427}
6428
6429static int
6430method_coverage_count_i(VALUE me, VALUE count, VALUE data)
6431{
6432 if (!FIXNUM_P(count)) count = INT2FIX(0);
6433 method_coverage_call((const rb_method_entry_t *)me, count,
6434 (struct method_coverage_arg *)data);
6435 return ST_CONTINUE;
6436}
6437
6438void
6439rb_coverage_each_method(rb_coverage_method_callback callback, void *data)
6440{
6441 struct method_coverage_arg arg = {callback, data};
6442 VALUE me_set = GET_VM()->me_set;
6443 VALUE cme2counter = GET_VM()->cme2counter;
6444
6445 if (RTEST(me_set)) {
6446 rb_hash_foreach(me_set, method_coverage_me_i, (VALUE)&arg);
6447 }
6448 if (RTEST(cme2counter)) {
6449 rb_hash_foreach(cme2counter, method_coverage_count_i, (VALUE)&arg);
6450 }
6451}
6452
6453static void
6454update_method_coverage(VALUE cme2counter, rb_trace_arg_t *trace_arg)
6455{
6456 const rb_control_frame_t *cfp = GET_EC()->cfp;
6457 const rb_callable_method_entry_t *cme = rb_vm_frame_method_entry(cfp);
6458 const rb_method_entry_t *me = (const rb_method_entry_t *)cme;
6459 VALUE rcount;
6460 long count;
6461
6462 me = rb_resolve_me_location(me, 0);
6463 if (!me) return;
6464
6465 rcount = rb_hash_aref(cme2counter, (VALUE) me);
6466 count = FIXNUM_P(rcount) ? FIX2LONG(rcount) + 1 : 1;
6467 if (POSFIXABLE(count)) {
6468 rb_hash_aset(cme2counter, (VALUE) me, LONG2FIX(count));
6469 }
6470}
6471
6472/* [Bug #22179] Record every method entry as it is defined (via method_added)
6473 * into me_set, so that method coverage no longer needs to reconstruct the set
6474 * of defined methods by walking the heap. This keeps shadowed/removed method
6475 * entries discoverable (me_set holds them as keys, so GC cannot reclaim them)
6476 * and makes the result independent of GC timing. Only entries that resolve to
6477 * themselves (i.e. methods defined by `def` or Module#define_method) are
6478 * recorded. */
6479void
6480rb_vm_coverage_record_me(const rb_method_entry_t *me)
6481{
6482 if (!RTEST(GET_VM()->coverages)) return;
6483 if (!(GET_VM()->coverage_mode & COVERAGE_TARGET_METHODS)) return;
6484
6485 VALUE me_set = GET_VM()->me_set;
6486 if (!RTEST(me_set)) return;
6487
6488 if (rb_resolve_me_location(me, 0) == me) {
6489 rb_hash_aset(me_set, (VALUE)me, Qtrue);
6490 }
6491}
6492
6493VALUE
6494rb_get_coverages(void)
6495{
6496 return GET_VM()->coverages;
6497}
6498
6499int
6500rb_get_coverage_mode(void)
6501{
6502 return GET_VM()->coverage_mode;
6503}
6504
6505void
6506rb_set_coverages(VALUE coverages, int mode, VALUE cme2counter, VALUE me_set)
6507{
6508 GET_VM()->coverages = coverages;
6509 GET_VM()->cme2counter = cme2counter;
6510 GET_VM()->me_set = me_set;
6511 GET_VM()->coverage_mode = mode;
6512}
6513
6514void
6515rb_resume_coverages(void)
6516{
6517 int mode = GET_VM()->coverage_mode;
6518 VALUE cme2counter = GET_VM()->cme2counter;
6519 rb_add_event_hook2((rb_event_hook_func_t) update_line_coverage, RUBY_EVENT_COVERAGE_LINE, Qnil, RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG);
6520 if (mode & COVERAGE_TARGET_BRANCHES) {
6521 rb_add_event_hook2((rb_event_hook_func_t) update_branch_coverage, RUBY_EVENT_COVERAGE_BRANCH, Qnil, RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG);
6522 }
6523 if (mode & COVERAGE_TARGET_METHODS) {
6524 rb_add_event_hook2((rb_event_hook_func_t) update_method_coverage, RUBY_EVENT_CALL, cme2counter, RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG);
6525 }
6526}
6527
6528void
6529rb_suspend_coverages(void)
6530{
6531 rb_remove_event_hook((rb_event_hook_func_t) update_line_coverage);
6532 if (GET_VM()->coverage_mode & COVERAGE_TARGET_BRANCHES) {
6533 rb_remove_event_hook((rb_event_hook_func_t) update_branch_coverage);
6534 }
6535 if (GET_VM()->coverage_mode & COVERAGE_TARGET_METHODS) {
6536 rb_remove_event_hook((rb_event_hook_func_t) update_method_coverage);
6537 }
6538}
6539
6540/* Make coverage arrays empty so old covered files are no longer tracked. */
6541void
6542rb_reset_coverages(void)
6543{
6544 rb_clear_coverages();
6545 rb_iseq_remove_coverage_all();
6546 GET_VM()->coverages = Qfalse;
6547 GET_VM()->cme2counter = Qnil;
6548 GET_VM()->me_set = Qnil;
6549}
6550
6551VALUE
6552rb_default_coverage(int n)
6553{
6554 VALUE coverage = rb_ary_hidden_new_fill(3);
6555 VALUE lines = Qfalse, branches = Qfalse;
6556 int mode = GET_VM()->coverage_mode;
6557
6558 if (mode & COVERAGE_TARGET_LINES) {
6559 lines = n > 0 ? rb_ary_hidden_new_fill(n) : rb_ary_hidden_new(0);
6560 }
6561 RARRAY_ASET(coverage, COVERAGE_INDEX_LINES, lines);
6562
6563 if (mode & COVERAGE_TARGET_BRANCHES) {
6564 branches = rb_ary_hidden_new_fill(2);
6565 /* internal data structures for branch coverage:
6566 *
6567 * { branch base key (see decl_branch_base) =>
6568 * [base_type, base_first_lineno, base_first_column, base_last_lineno, base_last_column, {
6569 * branch target id =>
6570 * [target_type, target_first_lineno, target_first_column, target_last_lineno, target_last_column, target_counter_index],
6571 * ...
6572 * }],
6573 * ...
6574 * }
6575 *
6576 * Example:
6577 * { [source_hash, node_id, lineno] =>
6578 * [1, 0, 4, 3, {
6579 * 0 => [2, 8, 2, 9, 0],
6580 * 1 => [3, 8, 3, 9, 1],
6581 * ...
6582 * }],
6583 * ...
6584 * }
6585 */
6586 VALUE structure = rb_hash_new();
6587 rb_obj_hide(structure);
6588 RARRAY_ASET(branches, 0, structure);
6589 /* branch execution counters */
6590 RARRAY_ASET(branches, 1, rb_ary_hidden_new(0));
6591 }
6592 RARRAY_ASET(coverage, COVERAGE_INDEX_BRANCHES, branches);
6593
6594 return coverage;
6595}
6596
6597static VALUE
6598uninterruptible_exit(VALUE v)
6599{
6600 rb_thread_t *cur_th = GET_THREAD();
6601 rb_ary_pop(cur_th->pending_interrupt_mask_stack);
6602
6603 cur_th->pending_interrupt_queue_checked = 0;
6604 if (!rb_threadptr_pending_interrupt_empty_p(cur_th)) {
6605 RUBY_VM_SET_INTERRUPT(cur_th->ec);
6606 }
6607 return Qnil;
6608}
6609
6610VALUE
6611rb_uninterruptible(VALUE (*b_proc)(VALUE), VALUE data)
6612{
6613 VALUE interrupt_mask = rb_ident_hash_new();
6614 rb_thread_t *cur_th = GET_THREAD();
6615
6616 rb_hash_aset(interrupt_mask, rb_cObject, sym_never);
6617 OBJ_FREEZE(interrupt_mask);
6618 rb_ary_push(cur_th->pending_interrupt_mask_stack, interrupt_mask);
6619
6620 VALUE ret = rb_ensure(b_proc, data, uninterruptible_exit, Qnil);
6621
6622 RUBY_VM_CHECK_INTS(cur_th->ec);
6623 return ret;
6624}
6625
6626static void
6627thread_specific_storage_alloc(rb_thread_t *th)
6628{
6629 VM_ASSERT(th->specific_storage == NULL);
6630
6631 if (UNLIKELY(specific_key_count > 0)) {
6632 th->specific_storage = ZALLOC_N(void *, RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX);
6633 }
6634}
6635
6636rb_internal_thread_specific_key_t
6638{
6639 rb_vm_t *vm = GET_VM();
6640
6641 if (specific_key_count == 0 && vm->ractor.cnt > 1) {
6642 rb_raise(rb_eThreadError, "The first rb_internal_thread_specific_key_create() is called with multiple ractors");
6643 }
6644 else if (specific_key_count > RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX) {
6645 rb_raise(rb_eThreadError, "rb_internal_thread_specific_key_create() is called more than %d times", RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX);
6646 }
6647 else {
6648 rb_internal_thread_specific_key_t key = specific_key_count++;
6649
6650 if (key == 0) {
6651 // allocate
6652 rb_ractor_t *cr = GET_RACTOR();
6653 rb_thread_t *th;
6654
6655 ccan_list_for_each(&cr->threads.set, th, lt_node) {
6656 thread_specific_storage_alloc(th);
6657 }
6658 }
6659 return key;
6660 }
6661}
6662
6663// async and native thread safe.
6664void *
6665rb_internal_thread_specific_get(VALUE thread_val, rb_internal_thread_specific_key_t key)
6666{
6667 rb_thread_t *th = DATA_PTR(thread_val);
6668
6669 VM_ASSERT(rb_thread_ptr(thread_val) == th);
6670 VM_ASSERT(key < RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX);
6671 VM_ASSERT(th->specific_storage);
6672
6673 return th->specific_storage[key];
6674}
6675
6676// async and native thread safe.
6677void
6678rb_internal_thread_specific_set(VALUE thread_val, rb_internal_thread_specific_key_t key, void *data)
6679{
6680 rb_thread_t *th = DATA_PTR(thread_val);
6681
6682 VM_ASSERT(rb_thread_ptr(thread_val) == th);
6683 VM_ASSERT(key < RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX);
6684 VM_ASSERT(th->specific_storage);
6685
6686 th->specific_storage[key] = data;
6687}
6688
6689// interrupt_exec
6692 struct ccan_list_node node;
6693
6694 rb_interrupt_exec_func_t *func;
6695 void *data;
6696 enum rb_interrupt_exec_flag flags;
6697};
6698
6699void
6700rb_threadptr_interrupt_exec_task_mark(rb_thread_t *th)
6701{
6702 struct rb_interrupt_exec_task *task;
6703
6704 ccan_list_for_each(&th->interrupt_exec_tasks, task, node) {
6705 if (task->flags & rb_interrupt_exec_flag_value_data) {
6706 rb_gc_mark((VALUE)task->data);
6707 }
6708 }
6709}
6710
6711// native thread safe
6712// th should be available
6713void
6714rb_threadptr_interrupt_exec(rb_thread_t *th, rb_interrupt_exec_func_t *func, void *data, enum rb_interrupt_exec_flag flags)
6715{
6716 // should not use ALLOC
6718 *task = (struct rb_interrupt_exec_task) {
6719 .flags = flags,
6720 .func = func,
6721 .data = data,
6722 };
6723
6724 rb_native_mutex_lock(&th->interrupt_lock);
6725 {
6726 ccan_list_add_tail(&th->interrupt_exec_tasks, &task->node);
6727 threadptr_set_interrupt_locked(th, true);
6728 }
6729 rb_native_mutex_unlock(&th->interrupt_lock);
6730}
6731
6732static void
6733threadptr_interrupt_exec_exec(rb_thread_t *th)
6734{
6735 while (1) {
6736 struct rb_interrupt_exec_task *task;
6737
6738 rb_native_mutex_lock(&th->interrupt_lock);
6739 {
6740 task = ccan_list_pop(&th->interrupt_exec_tasks, struct rb_interrupt_exec_task, node);
6741 }
6742 rb_native_mutex_unlock(&th->interrupt_lock);
6743
6744 RUBY_DEBUG_LOG("task:%p", task);
6745
6746 if (task) {
6747 if (task->flags & rb_interrupt_exec_flag_new_thread) {
6748 rb_thread_create(task->func, task->data);
6749 }
6750 else {
6751 (*task->func)(task->data);
6752 }
6753 SIZED_FREE(task);
6754 }
6755 else {
6756 break;
6757 }
6758 }
6759}
6760
6761static void
6762threadptr_interrupt_exec_cleanup(rb_thread_t *th)
6763{
6764 rb_native_mutex_lock(&th->interrupt_lock);
6765 {
6766 struct rb_interrupt_exec_task *task;
6767
6768 while ((task = ccan_list_pop(&th->interrupt_exec_tasks, struct rb_interrupt_exec_task, node)) != NULL) {
6769 SIZED_FREE(task);
6770 }
6771 }
6772 rb_native_mutex_unlock(&th->interrupt_lock);
6773}
6774
6775// native thread safe
6776// func/data should be native thread safe
6777void
6778rb_ractor_interrupt_exec(struct rb_ractor_struct *target_r,
6779 rb_interrupt_exec_func_t *func, void *data, enum rb_interrupt_exec_flag flags)
6780{
6781 RUBY_DEBUG_LOG("flags:%d", (int)flags);
6782
6783 rb_thread_t *main_th = target_r->threads.main;
6784 rb_threadptr_interrupt_exec(main_th, func, data, flags | rb_interrupt_exec_flag_new_thread);
6785}
#define RUBY_ASSERT_ALWAYS(expr,...)
A variant of RUBY_ASSERT that does not interface with RUBY_DEBUG.
Definition assert.h:199
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
#define RUBY_INTERNAL_EVENT_SWITCH
Thread switched.
Definition event.h:90
int rb_remove_event_hook(rb_event_hook_func_t func)
Removes the passed function from the list of event hooks.
Definition vm_trace.c:454
#define RUBY_EVENT_THREAD_BEGIN
Encountered a new thread.
Definition event.h:57
void(* rb_event_hook_func_t)(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass)
Type of event hooks.
Definition event.h:120
uint32_t rb_event_flag_t
Represents event(s).
Definition event.h:108
#define RUBY_EVENT_CALL
A method, written in Ruby, is called.
Definition event.h:41
#define RUBY_EVENT_THREAD_END
Encountered an end of a thread.
Definition event.h:58
static void RB_FL_SET_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_SET().
Definition fl_type.h:544
void rb_define_alias(VALUE klass, const char *name1, const char *name2)
Defines an alias of a method.
Definition class.c:3060
ID rb_frame_last_func(void)
Returns the ID of the last method in the call stack.
Definition eval.c:1269
int rb_keyword_given_p(void)
Determines if the current method is given a keyword argument.
Definition eval.c:1048
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1035
#define rb_str_new2
Old name of rb_str_new_cstr.
Definition string.h:1700
#define ALLOC
Old name of RB_ALLOC.
Definition memory.h:400
#define T_STRING
Old name of RUBY_T_STRING.
Definition value_type.h:78
#define Qundef
Old name of RUBY_Qundef.
#define INT2FIX
Old name of RB_INT2FIX.
Definition long.h:48
#define OBJ_FROZEN
Old name of RB_OBJ_FROZEN.
Definition fl_type.h:133
#define UNREACHABLE
Old name of RBIMPL_UNREACHABLE.
Definition assume.h:28
#define ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#define OBJ_FREEZE
Old name of RB_OBJ_FREEZE.
Definition fl_type.h:131
#define UNREACHABLE_RETURN
Old name of RBIMPL_UNREACHABLE_RETURN.
Definition assume.h:29
#define CLASS_OF
Old name of rb_class_of.
Definition globals.h:205
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
#define LONG2FIX
Old name of RB_INT2FIX.
Definition long.h:49
#define FIX2INT
Old name of RB_FIX2INT.
Definition int.h:41
#define ZALLOC_N
Old name of RB_ZALLOC_N.
Definition memory.h:401
#define T_ICLASS
Old name of RUBY_T_ICLASS.
Definition value_type.h:66
#define T_HASH
Old name of RUBY_T_HASH.
Definition value_type.h:65
#define Qtrue
Old name of RUBY_Qtrue.
#define NUM2INT
Old name of RB_NUM2INT.
Definition int.h:44
#define INT2NUM
Old name of RB_INT2NUM.
Definition int.h:43
#define Qnil
Old name of RUBY_Qnil.
#define Qfalse
Old name of RUBY_Qfalse.
#define FIX2LONG
Old name of RB_FIX2LONG.
Definition long.h:46
#define T_ARRAY
Old name of RUBY_T_ARRAY.
Definition value_type.h:56
#define T_OBJECT
Old name of RUBY_T_OBJECT.
Definition value_type.h:75
#define NIL_P
Old name of RB_NIL_P.
#define POSFIXABLE
Old name of RB_POSFIXABLE.
Definition fixnum.h:29
#define BUILTIN_TYPE
Old name of RB_BUILTIN_TYPE.
Definition value_type.h:85
#define FIXNUM_P
Old name of RB_FIXNUM_P.
#define SYMBOL_P
Old name of RB_SYMBOL_P.
Definition value_type.h:88
void ruby_stop(int ex)
Calls ruby_cleanup() and exits the process.
Definition eval.c:307
#define ruby_debug
This variable controls whether the interpreter is in debug mode.
Definition error.h:487
void rb_exc_raise(VALUE mesg)
Raises an exception in the current thread.
Definition eval.c:678
VALUE rb_eSystemExit
SystemExit exception.
Definition error.c:1450
VALUE rb_eIOError
IOError exception.
Definition io.c:180
VALUE rb_eStandardError
StandardError exception.
Definition error.c:1454
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1457
void rb_frozen_error_raise(VALUE frozen_obj, const char *fmt,...)
Raises an instance of rb_eFrozenError.
Definition error.c:4265
VALUE rb_eFatal
fatal exception.
Definition error.c:1453
VALUE rb_eInterrupt
Interrupt exception.
Definition error.c:1451
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1455
void rb_warn(const char *fmt,...)
Identical to rb_warning(), except it reports unless $VERBOSE is nil.
Definition error.c:464
VALUE rb_eException
Mother of all exceptions.
Definition error.c:1449
VALUE rb_exc_new(VALUE etype, const char *ptr, rb_len_t len)
Creates an instance of the passed exception class.
Definition error.c:1495
VALUE rb_eThreadError
ThreadError exception.
Definition eval.c:1053
void rb_exit(int status)
Terminates the current execution context.
Definition process.c:4319
VALUE rb_eSignal
SignalException exception.
Definition error.c:1452
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_obj_alloc(VALUE klass)
Allocates an instance of the given class.
Definition object.c:2274
VALUE rb_cInteger
Module class.
Definition numeric.c:202
VALUE rb_obj_hide(VALUE obj)
Make the object invisible from Ruby code.
Definition object.c:94
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:240
VALUE rb_cThread
Thread class.
Definition vm.c:719
VALUE rb_cModule
Module class.
Definition object.c:61
double rb_num2dbl(VALUE num)
Converts an instance of rb_cNumeric into C's double.
Definition object.c:3855
VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass)
Queries if the given object is an instance (of possibly descendants) of the given class.
Definition object.c:912
Scheduler APIs.
VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void *(*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *data2, int flags, struct rb_fiber_scheduler_blocking_operation_state *state)
Defer the execution of the passed function to the scheduler.
Definition scheduler.c:1243
VALUE rb_fiber_scheduler_current(void)
Identical to rb_fiber_scheduler_get(), except it also returns RUBY_Qnil in case of a blocking fiber.
Definition scheduler.c:581
VALUE rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout)
Non-blocking wait for the passed "blocker", which is for instance Thread.join or Mutex....
Definition scheduler.c:770
VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE target, VALUE exception)
Interrupt a target by raising an exception.
Definition scheduler.c:1290
VALUE rb_fiber_scheduler_yield(VALUE scheduler)
Yield to the scheduler, to be resumed on the next scheduling cycle.
Definition scheduler.c:671
VALUE rb_fiber_scheduler_set(VALUE scheduler)
Destructively assigns the passed scheduler to that of the current thread that is calling this functio...
Definition scheduler.c:543
VALUE rb_fiber_scheduler_current_for_threadptr(struct rb_thread_struct *thread)
Identical to rb_fiber_scheduler_current_for_thread(), except it expects a threadptr instead of a thre...
Definition scheduler.c:594
VALUE rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber)
Wakes up a fiber previously blocked using rb_fiber_scheduler_block().
Definition scheduler.c:789
VALUE rb_ary_shift(VALUE ary)
Destructively deletes an element from the beginning of the passed array and returns what was deleted.
VALUE rb_ary_entry(VALUE ary, rb_len_t off)
Queries an element of an array.
VALUE rb_ary_dup(VALUE ary)
Duplicates an array.
VALUE rb_ary_delete_at(VALUE ary, rb_len_t pos)
Destructively removes an element which resides at the specific index of the passed array.
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_pop(VALUE ary)
Destructively deletes an element from the end of the passed array and returns what was deleted.
VALUE rb_ary_clear(VALUE ary)
Destructively removes everything form an array.
VALUE rb_ary_hidden_new(rb_len_t capa)
Allocates a hidden (no class) empty array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
VALUE rb_ary_join(VALUE ary, VALUE sep)
Recursively stringises the elements of the passed array, flattens that result, then joins the sequenc...
static int rb_check_arity(int argc, int min, int max)
Ensures that the passed integer is in the passed range.
Definition error.h:284
VALUE rb_block_proc(void)
Constructs a Proc object from implicitly passed components.
Definition proc.c:1580
void rb_reset_random_seed(void)
Resets the RNG behind rb_genrand_int32()/rb_genrand_real().
Definition random.c:1757
VALUE rb_str_new_frozen(VALUE str)
Creates a frozen copy of the string, if necessary.
Definition string.c:1569
VALUE rb_str_concat(VALUE dst, VALUE src)
Identical to rb_str_append(), except it also accepts an integer as a codepoint.
Definition string.c:4174
#define rb_str_cat_cstr(buf, str)
Identical to rb_str_cat(), except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1681
#define rb_str_new_cstr(str)
Identical to rb_str_new, except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1539
int rb_thread_interrupted(VALUE thval)
Checks if the thread's execution was recently interrupted.
Definition thread.c:1663
VALUE rb_thread_local_aref(VALUE thread, ID key)
This badly named function reads from a Fiber local storage.
Definition thread.c:4082
VALUE rb_mutex_new(void)
Creates a mutex.
VALUE rb_thread_kill(VALUE thread)
Terminates the given thread.
Definition thread.c:3274
#define RUBY_UBF_IO
A special UBF for blocking IO operations.
Definition thread.h:382
VALUE rb_thread_main(void)
Obtains the "main" thread.
Definition thread.c:3513
VALUE rb_exec_recursive(VALUE(*f)(VALUE g, VALUE h, int r), VALUE g, VALUE h)
"Recursion" API entry point.
void rb_thread_sleep_forever(void)
Blocks indefinitely.
Definition thread.c:1592
void rb_thread_fd_close(int fd)
This function is now a no-op.
Definition thread.c:3210
void rb_thread_wait_for(struct timeval time)
Identical to rb_thread_sleep(), except it takes struct timeval instead.
Definition thread.c:1625
VALUE rb_mutex_synchronize(VALUE mutex, VALUE(*func)(VALUE arg), VALUE arg)
Obtains the lock, runs the passed function, and releases the lock when it completes.
VALUE rb_thread_stop(void)
Stops the current thread.
Definition thread.c:3425
VALUE rb_mutex_sleep(VALUE self, VALUE timeout)
Releases the lock held in the mutex and waits for the period of time; reacquires the lock on wakeup.
VALUE rb_exec_recursive_paired(VALUE(*f)(VALUE g, VALUE h, int r), VALUE g, VALUE p, VALUE h)
Identical to rb_exec_recursive(), except it checks for the recursion on the ordered pair of { g,...
void rb_unblock_function_t(void *)
This is the type of UBFs.
Definition thread.h:336
void rb_thread_atfork_before_exec(void)
:FIXME: situation of this function is unclear.
Definition thread.c:5434
void rb_thread_check_ints(void)
Checks for interrupts.
Definition thread.c:1646
VALUE rb_thread_run(VALUE thread)
This is a rb_thread_wakeup() + rb_thread_schedule() combo.
Definition thread.c:3416
VALUE rb_thread_wakeup(VALUE thread)
Marks a given thread as eligible for scheduling.
Definition thread.c:3369
VALUE rb_mutex_unlock(VALUE mutex)
Releases the mutex.
VALUE rb_exec_recursive_paired_outer(VALUE(*f)(VALUE g, VALUE h, int r), VALUE g, VALUE p, VALUE h)
Identical to rb_exec_recursive_outer(), except it checks for the recursion on the ordered pair of { g...
void rb_thread_sleep_deadly(void)
Identical to rb_thread_sleep_forever(), except the thread calling this function is considered "dead" ...
Definition thread.c:1599
void rb_thread_atfork(void)
A pthread_atfork(3posix)-like API.
Definition thread.c:5429
VALUE rb_thread_current(void)
Obtains the "current" thread.
Definition thread.c:3492
int rb_thread_alone(void)
Checks if the thread this function is running is the only thread that is currently alive.
Definition thread.c:4370
VALUE rb_thread_local_aset(VALUE thread, ID key, VALUE val)
This badly named function writes to a Fiber local storage.
Definition thread.c:4230
void rb_thread_schedule(void)
Tries to switch to another thread.
Definition thread.c:1694
#define RUBY_UBF_PROCESS
A special UBF for blocking process operations.
Definition thread.h:389
VALUE rb_exec_recursive_outer(VALUE(*f)(VALUE g, VALUE h, int r), VALUE g, VALUE h)
Identical to rb_exec_recursive(), except it calls f for outermost recursion only.
VALUE rb_thread_wakeup_alive(VALUE thread)
Identical to rb_thread_wakeup(), except it doesn't raise on an already killed thread.
Definition thread.c:3378
VALUE rb_mutex_lock(VALUE mutex)
Attempts to lock the mutex.
void rb_thread_sleep(int sec)
Blocks for the given period of time.
Definition thread.c:1669
void rb_timespec_now(struct timespec *ts)
Fills the current time into the given struct.
Definition time.c:2034
struct timeval rb_time_timeval(VALUE time)
Converts an instance of rb_cTime to a struct timeval that represents the identical point of time.
Definition time.c:2990
VALUE rb_ivar_set(VALUE obj, ID name, VALUE val)
Identical to rb_iv_set(), except it accepts the name as an ID instead of a C string.
Definition variable.c:2150
VALUE rb_ivar_get(VALUE obj, ID name)
Identical to rb_iv_get(), except it accepts the name as an ID instead of a C string.
Definition variable.c:1645
VALUE rb_class_path(VALUE mod)
Identical to rb_mod_name(), except it returns #<Class: ...> style inspection for anonymous modules.
Definition variable.c:396
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
int rb_sourceline(void)
Resembles __LINE__.
Definition vm.c:2204
static ID rb_intern_const(const char *str)
This is a "tiny optimisation" over rb_intern().
Definition symbol.h:285
ID rb_check_id(volatile VALUE *namep)
Detects if the given name is already interned or not.
Definition symbol.c:1289
VALUE rb_to_symbol(VALUE name)
Identical to rb_intern_str(), except it generates a dynamic symbol if necessary.
Definition string.c:14235
ID rb_to_id(VALUE str)
Identical to rb_intern_str(), except it tries to convert the parameter object to an instance of rb_cS...
Definition string.c:14225
int capa
Designed capacity of the buffer.
Definition io.h:11
#define RB_IO_POINTER(obj, fp)
Queries the underlying IO pointer.
Definition io.h:436
VALUE rb_eIOTimeoutError
Indicates that a timeout has occurred while performing an IO operation.
Definition io.c:181
#define RB_NOGVL_UBF_ASYNC_SAFE
Passing this flag to rb_nogvl() indicates that the passed UBF is async-signal-safe.
Definition thread.h:71
void * rb_internal_thread_specific_get(VALUE thread_val, rb_internal_thread_specific_key_t key)
Get thread and tool specific data.
Definition thread.c:6664
#define RB_NOGVL_INTR_FAIL
Passing this flag to rb_nogvl() prevents it from processing interrupts after the given function retur...
Definition thread.h:50
void rb_internal_thread_specific_set(VALUE thread_val, rb_internal_thread_specific_key_t key, void *data)
Set thread and tool specific data.
Definition thread.c:6677
rb_internal_thread_specific_key_t rb_internal_thread_specific_key_create(void)
Create a key to store thread specific data.
Definition thread.c:6636
#define RB_NOGVL_PENDING_INTR_FAIL
Passing this flag to rb_nogvl() prevents it from entering the blocking region if the current thread h...
Definition thread.h:59
void * rb_nogvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2, int flags)
Identical to rb_thread_call_without_gvl(), except it additionally takes "flags" that change the behav...
Definition thread.c:1774
void * rb_thread_call_with_gvl(void *(*func)(void *), void *data1)
(Re-)acquires the GVL.
Definition thread.c:2323
#define RB_NOGVL_OFFLOAD_SAFE
Passing this flag to rb_nogvl() indicates that the passed function is safe to offload to a background...
Definition thread.h:84
void * rb_thread_call_without_gvl2(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2)
Identical to rb_thread_call_without_gvl(), except it does not interface with signals etc.
Definition thread.c:1925
void * rb_thread_call_without_gvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2)
Allows the passed function to run in parallel with other Ruby threads.
#define RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, callback_arg)
Shim for block function parameters.
Definition iterator.h:58
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1379
void rb_throw_obj(VALUE tag, VALUE val)
Identical to rb_throw(), except it allows arbitrary Ruby object to become a tag.
Definition vm_eval.c:2604
static int rb_fd_max(const rb_fdset_t *f)
It seems this function has no use.
Definition largesize.h:209
void rb_fd_copy(rb_fdset_t *dst, const fd_set *src, int max)
Destructively overwrites an fdset with another.
void rb_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src)
Identical to rb_fd_copy(), except it copies unlimited number of file descriptors.
void rb_fd_term(rb_fdset_t *f)
Destroys the rb_fdset_t, releasing any memory and resources it used.
static fd_set * rb_fd_ptr(const rb_fdset_t *f)
Raw pointer to fd_set.
Definition largesize.h:195
#define MEMCPY(p1, p2, type, n)
Handy macro to call memcpy.
Definition memory.h:372
#define ALLOCA_N(type, n)
Definition memory.h:292
#define MEMZERO(p, type, n)
Handy macro to erase a region of memory.
Definition memory.h:360
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
VALUE rb_thread_create(type *q, void *w)
Creates a rb_cThread instance.
void rb_hash_foreach(VALUE q, int_type *w, VALUE e)
Iteration over the given hash.
VALUE rb_ensure(type *q, VALUE w, type *e, VALUE r)
An equivalent of ensure clause.
#define rb_fd_isset
Queries if the given fd is in the rb_fdset_t.
Definition posix.h:60
#define rb_fd_select
Waits for multiple file descriptors at once.
Definition posix.h:66
#define rb_fd_init
Initialises the :given :rb_fdset_t.
Definition posix.h:63
#define rb_fd_set
Sets the given fd to the rb_fdset_t.
Definition posix.h:54
#define rb_fd_zero
Clears the given rb_fdset_t.
Definition posix.h:51
#define rb_fd_clr
Unsets the given fd from the rb_fdset_t.
Definition posix.h:57
#define RARRAY_LEN
Just another name of rb_array_len.
Definition rarray.h:50
static int RARRAY_LENINT(VALUE ary)
Identical to rb_array_len(), except it differs for the return type.
Definition rarray.h:280
#define RARRAY_AREF(a, i)
References an object in an array.
Definition rarray.h:421
static void RARRAY_ASET(VALUE ary, rb_len_t i, VALUE v)
Assigns an object in an array.
Definition rarray.h:385
#define RARRAY_CONST_PTR
Just another name of rb_array_const_ptr.
Definition rarray.h:51
static VALUE RBASIC_CLASS(VALUE obj)
Queries the class of an object.
Definition rbasic.h:166
#define RBASIC(obj)
Convenient casting macro.
Definition rbasic.h:40
#define RCLASS_SUPER
Just another name of rb_class_get_superclass.
Definition rclass.h:44
#define RHASH_EMPTY_P(h)
Checks if the hash is empty.
Definition rhash.h:67
#define StringValueCStr(v)
Identical to StringValuePtr, except it additionally checks for the contents for viability as a C stri...
Definition rstring.h:89
#define RUBY_TYPED_DEFAULT_FREE
This is a value you can set to rb_data_type_struct::dfree.
Definition rtypeddata.h:81
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:773
#define DATA_PTR(obj)
Convenient casting macro for backward compatibility.
Definition rtypeddata.h:439
#define TypedData_Wrap_Struct(klass, data_type, sval)
Converts sval, a pointer to your struct, into a Ruby object.
Definition rtypeddata.h:557
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:604
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
int ruby_native_thread_p(void)
Queries if the thread which calls this function is a ruby's thread.
Definition thread.c:6183
int ruby_snprintf(char *str, size_t n, char const *fmt,...)
Our own locale-insensitive version of snprintf(3).
Definition sprintf.c:1081
#define RB_PASS_CALLED_KEYWORDS
Pass keywords if current method is called with keywords, useful for argument delegation.
Definition scan_args.h:78
int rb_thread_fd_select(int nfds, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, struct timeval *timeout)
Waits for multiple file descriptors at once.
Definition thread.c:4868
#define rb_fd_resize(n, f)
Does nothing (defined for compatibility).
Definition select.h:43
static bool RB_TEST(VALUE obj)
Emulates Ruby's "if" statement.
#define RTEST
This is an old name of RB_TEST.
#define _(args)
This was a transition path from K&R to ANSI.
Definition stdarg.h:35
Definition method.h:63
CREF (Class REFerence)
Definition method.h:45
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:242
The data structure which wraps the fd_set bitmap used by select(2).
Definition largesize.h:71
int maxfd
Maximum allowed number of FDs.
Definition largesize.h:72
fd_set * fdset
File descriptors buffer.
Definition largesize.h:73
int capa
Maximum allowed number of FDs.
Definition win32.h:50
Ruby's IO, metadata and buffers.
Definition io.h:295
VALUE self
The IO's Ruby level counterpart.
Definition io.h:298
int fd
file descriptor.
Definition io.h:306
struct ccan_list_head blocking_operations
Threads that are performing a blocking operation without the GVL using this IO.
Definition io.h:134
Definition method.h:55
const rb_iseq_t * iseqptr
iseq pointer, should be separated from iseqval
Definition method.h:143
void rb_nativethread_lock_lock(rb_nativethread_lock_t *lock)
Blocks until the current thread obtains a lock.
Definition thread.c:319
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.
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_nativethread_lock_unlock(rb_nativethread_lock_t *lock)
Releases a lock.
Definition thread.c:325
void rb_native_mutex_destroy(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_destroy.
void rb_nativethread_lock_initialize(rb_nativethread_lock_t *lock)
Fills the passed lock with an initial value.
Definition thread.c:307
void rb_nativethread_lock_destroy(rb_nativethread_lock_t *lock)
Destroys the passed mutex.
Definition thread.c:313
long rb_len_t
A signed integer type for lengths and indices of objects such as String and Array.
Definition value.h:142
uintptr_t ID
Type that represents a Ruby identifier such as a variable name.
Definition value.h:52
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376