Ruby 4.1.0dev (2026-09-07 revision b57404b461ba8bf34e802d86b0db78388216e182)
ractor.c (b57404b461ba8bf34e802d86b0db78388216e182)
1// Ractor implementation
2
3#include "ruby/ruby.h"
4#include "ruby/thread.h"
5#include "ruby/ractor.h"
6#include "ruby/re.h"
8#include "vm_core.h"
9#include "vm_sync.h"
10#include "ractor_core.h"
11#include "internal/array.h"
12#include "internal/complex.h"
13#include "internal/cont.h"
14#include "internal/error.h"
15#include "internal/gc.h"
16#include "internal/hash.h"
17#include "internal/object.h"
18#include "internal/array.h"
19#include "internal/string.h"
20#include "internal/variable.h"
21#include "eval_intern.h"
22#include "internal/io.h"
23#include "internal/ractor.h"
24#include "internal/rational.h"
25#include "internal/re.h"
26#include "internal/struct.h"
27#include "internal/st.h"
28#include "internal/thread.h"
29#include "internal/vm.h"
30#include "ruby/encoding.h"
31#include "variable.h"
32#include "shape.h"
33#include "yjit.h"
34#include "zjit.h"
35
37static VALUE rb_cRactorSelector;
38
39VALUE rb_eRactorUnsafeError;
40VALUE rb_eRactorIsolationError;
41static VALUE rb_eRactorError;
42static VALUE rb_eRactorRemoteError;
43static VALUE rb_eRactorMovedError;
44static VALUE rb_eRactorClosedError;
45static VALUE rb_cRactorMovedObject;
46
47static void vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line);
48
49
50#if RACTOR_CHECK_MODE > 0
51bool rb_ractor_ignore_belonging_flag = false;
52#endif
53
54// Ractor locking
55
56static void
57ASSERT_ractor_unlocking(rb_ractor_t *r)
58{
59#if RACTOR_CHECK_MODE > 0
60 const rb_execution_context_t *ec = rb_current_ec_noinline();
61 if (ec != NULL && r->sync.locked_by == rb_ractor_self(rb_ec_ractor_ptr(ec))) {
62 rb_bug("recursive ractor locking");
63 }
64#endif
65}
66
67static void
68ASSERT_ractor_locking(rb_ractor_t *r)
69{
70#if RACTOR_CHECK_MODE > 0
71 const rb_execution_context_t *ec = rb_current_ec_noinline();
72 if (ec != NULL && r->sync.locked_by != rb_ractor_self(rb_ec_ractor_ptr(ec))) {
73 rp(r->sync.locked_by);
74 rb_bug("ractor lock is not acquired.");
75 }
76#endif
77}
78
79static void
80ractor_lock(rb_ractor_t *r, const char *file, int line)
81{
82 RUBY_DEBUG_LOG2(file, line, "locking r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
83
84 ASSERT_ractor_unlocking(r);
85 rb_native_mutex_lock(&r->sync.lock);
86
87 const rb_execution_context_t *ec = rb_current_ec_noinline();
88 if (ec) {
89 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
90 VM_ASSERT(!cr->malloc_gc_disabled);
91 cr->malloc_gc_disabled = true;
92 }
93
94#if RACTOR_CHECK_MODE > 0
95 if (ec != NULL) {
96 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
97 r->sync.locked_by = rb_ractor_self(cr);
98 }
99#endif
100
101 RUBY_DEBUG_LOG2(file, line, "locked r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
102}
103
104static void
105ractor_lock_self(rb_ractor_t *cr, const char *file, int line)
106{
107 VM_ASSERT(cr == rb_ec_ractor_ptr(rb_current_ec_noinline()));
108#if RACTOR_CHECK_MODE > 0
109 VM_ASSERT(cr->sync.locked_by != cr->pub.self);
110#endif
111 ractor_lock(cr, file, line);
112}
113
114static void
115ractor_unlock(rb_ractor_t *r, const char *file, int line)
116{
117 ASSERT_ractor_locking(r);
118#if RACTOR_CHECK_MODE > 0
119 r->sync.locked_by = Qnil;
120#endif
121
122 const rb_execution_context_t *ec = rb_current_ec_noinline();
123 if (ec) {
124 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
125 VM_ASSERT(cr->malloc_gc_disabled);
126 cr->malloc_gc_disabled = false;
127 }
128
129 rb_native_mutex_unlock(&r->sync.lock);
130
131 RUBY_DEBUG_LOG2(file, line, "r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
132}
133
134static void
135ractor_unlock_self(rb_ractor_t *cr, const char *file, int line)
136{
137 VM_ASSERT(cr == rb_ec_ractor_ptr(rb_current_ec_noinline()));
138#if RACTOR_CHECK_MODE > 0
139 VM_ASSERT(cr->sync.locked_by == cr->pub.self);
140#endif
141 ractor_unlock(cr, file, line);
142}
143
144#define RACTOR_LOCK(r) ractor_lock(r, __FILE__, __LINE__)
145#define RACTOR_UNLOCK(r) ractor_unlock(r, __FILE__, __LINE__)
146#define RACTOR_LOCK_SELF(r) ractor_lock_self(r, __FILE__, __LINE__)
147#define RACTOR_UNLOCK_SELF(r) ractor_unlock_self(r, __FILE__, __LINE__)
148
149void
150rb_ractor_lock_self(rb_ractor_t *r)
151{
152 RACTOR_LOCK_SELF(r);
153}
154
155void
156rb_ractor_unlock_self(rb_ractor_t *r)
157{
158 RACTOR_UNLOCK_SELF(r);
159}
160
161// Ractor status
162
163static const char *
164ractor_status_str(enum ractor_status status)
165{
166 switch (status) {
167 case ractor_created: return "created";
168 case ractor_running: return "running";
169 case ractor_blocking: return "blocking";
170 case ractor_terminated: return "terminated";
171 }
172 rb_bug("unreachable");
173}
174
175static void
176ractor_status_set(rb_ractor_t *r, enum ractor_status status)
177{
178 RUBY_DEBUG_LOG("r:%u [%s]->[%s]", r->pub.id, ractor_status_str(r->status_), ractor_status_str(status));
179
180 // check 1
181 if (r->status_ != ractor_created) {
182 VM_ASSERT(r == GET_RACTOR()); // only self-modification is allowed.
183 ASSERT_vm_locking();
184 }
185
186 // check2: transition check. assume it will be vanished on non-debug build.
187 switch (r->status_) {
188 case ractor_created:
189 VM_ASSERT(status == ractor_blocking);
190 break;
191 case ractor_running:
192 VM_ASSERT(status == ractor_blocking||
193 status == ractor_terminated);
194 break;
195 case ractor_blocking:
196 VM_ASSERT(status == ractor_running);
197 break;
198 case ractor_terminated:
199 rb_bug("unreachable");
200 break;
201 }
202
203 r->status_ = status;
204}
205
206static bool
207ractor_status_p(rb_ractor_t *r, enum ractor_status status)
208{
209 return rb_ractor_status_p(r, status);
210}
211
212// Ractor data/mark/free
213
214static void ractor_local_storage_mark(rb_ractor_t *r);
215static void ractor_local_storage_free(rb_ractor_t *r);
216
217static void ractor_sync_mark(rb_ractor_t *r);
218static void ractor_sync_free(rb_ractor_t *r);
219static size_t ractor_sync_memsize(const rb_ractor_t *r);
220static void ractor_sync_init(rb_ractor_t *r);
221
222static int
223mark_targeted_hook_list(st_data_t key, st_data_t value, st_data_t _arg)
224{
225 rb_hook_list_t *hook_list = (rb_hook_list_t*)value;
226
227 if (hook_list->type == hook_list_type_targeted_iseq) {
228 rb_gc_mark((VALUE)key);
229 }
230 else {
232 RUBY_ASSERT(hook_list->type == hook_list_type_targeted_def);
233 rb_gc_mark(def->body.bmethod.proc);
234 }
235 rb_hook_list_mark(hook_list);
236
237 return ST_CONTINUE;
238}
239
240static void
241ractor_mark_thread(rb_thread_t *th)
242{
243 rb_gc_mark(th->self);
244
245 /* A thread's ec lives inside the root fiber struct and is freed with that
246 * fiber's wrapper object, so keep the fiber wrappers alive from here too. */
247 if (th->root_fiber) {
248 VALUE root_fiber_self = rb_fiberptr_self(th->root_fiber);
249 if (root_fiber_self) rb_gc_mark(root_fiber_self);
250 }
251 /* The ec sits inside its fiber, so marking that fiber's wrapper scans the ec
252 * as well. Only when there is no wrapper yet (mid-creation, teardown) does
253 * the ec need marking of its own. */
254 VALUE ec_fiber_self = (th->ec && th->ec->fiber_ptr) ? rb_fiberptr_self(th->ec->fiber_ptr) : 0;
255 if (ec_fiber_self) {
256 rb_gc_mark(ec_fiber_self);
257 }
258 else if (th->ec) {
259 rb_execution_context_mark(th->ec);
260 }
261
262 /* Root the thread's remaining possessions directly as well; thgroup in
263 * particular has no other root. */
264 rb_thread_mark_owned_roots(th);
265}
266
267static void
268ractor_mark_unshareable_parts(rb_ractor_t *r)
269{
270 /* A single VALUE slot written by the owner in one word, so any GC reads it safely.
271 * Its target belongs to another Ractor, so containment makes a foreign marker skip
272 * it. */
273 rb_gc_mark(r->r_stdin);
274 rb_gc_mark(r->r_stdout);
275 rb_gc_mark(r->r_stderr);
276 rb_gc_mark(r->verbose);
277 rb_gc_mark(r->debug);
278
279 // mark the received messages (the structures the owner mutates guard themselves)
280 ractor_sync_mark(r);
281
282 /* Structures the owner mutates while running follow. */
283
284 rb_hook_list_mark(&r->pub.hooks);
285 if (r->pub.targeted_hooks.num_entries) {
286 st_foreach(&r->pub.targeted_hooks, mark_targeted_hook_list, 0);
287 }
288
289 if (r->threads.cnt > 0) {
290 rb_thread_t *th = 0;
291 ccan_list_for_each(&r->threads.set, th, lt_node) {
292 VM_ASSERT(th != NULL);
293 ractor_mark_thread(th);
294 }
295 }
296
297 /* A thread in the MN termination epilogue has left the set but is still
298 * running on its coroutine stack; it stays a root until its last use.
299 * Read once: the epilogue clears the slot concurrently. The thread is
300 * past rb_fiber_close/thread_cleanup_func by then -- the same state
301 * thread_mark walks whenever a terminated Thread's wrapper is still
302 * referenced, and ractor_mark_thread performs the same marks. */
303 rb_thread_t *dying_th = RUBY_ATOMIC_PTR_LOAD(r->threads.dying_th);
304 if (dying_th) ractor_mark_thread(dying_th);
305
306 ractor_local_storage_mark(r);
307}
308
309static void
310ractor_mark(void *ptr)
311{
312 rb_ractor_t *r = (rb_ractor_t *)ptr;
313
314 /* Only the wrapper's direct references: following an unshareable object from the
315 * shareable wrapper would break the shref rule. Unshareable roots are marked by the
316 * root scan (rb_ractor_mark_local_roots); zombie_objspaces covers the terminated. */
317 rb_gc_mark(r->loc);
318 rb_gc_mark(r->name);
319 /* The default port is shareable, so following it breaks no rule. Other Ractors
320 * still send/value through it after termination, and once a terminated Ractor left
321 * both the set and zombie_objspaces (orphan-merged) this marker is its only cover. */
322 rb_gc_mark(r->sync.default_port_value);
323 /* A single-objspace impl (mmtk) has no zombie_objspaces and no pin/shref bits, so
324 * the root scan cannot reach a terminated Ractor's queue, in-flight payloads or
325 * join value; and no shref rule forbids following them from the wrapper. */
326 if (!rb_gc_multi_objspace_p()) {
327 ractor_mark_unshareable_parts(r);
328 rb_ractor_mark_terminated_join_value(r);
329 }
330 else if (rb_gc_during_global_gc_p()) {
331 /* The join value is only of use to whoever can still call Ractor#value, which
332 * means holding this wrapper, so mark it as the wrapper's child rather than as a
333 * root. A global GC stops the world and marks every objspace together, which is
334 * what lets the shareable wrapper reach an unshareable value at all. */
335 rb_ractor_mark_terminated_join_value(r);
336 }
337}
338
339/* Mark the GC roots reachable from Ractor r's C structs. A local GC cannot rely on the
340 * heap Ractor and Thread wrapper objects, which may live in another objspace, so this
341 * Ractor's own possessions are rooted directly from here. */
342void
343rb_ractor_mark_local_roots(rb_ractor_t *r)
344{
345 if (r->postmortem) {
346 /* The final self collection: everything else -- the Thread and Fiber
347 * wrappers, stdio, stack leftovers -- is what it exists to reclaim.
348 * Skipping the walk below cannot drop dying_th: postmortem runs on the
349 * Ractor's last thread, which can only run after any predecessor's
350 * epilogue cleared the slot (under the same scheduler lock). */
351 VM_ASSERT(RUBY_ATOMIC_PTR_LOAD(r->threads.dying_th) == NULL);
352 rb_ractor_mark_terminated_join_value(r);
353 rb_gc_mark_vm_stack_values((long)r->registered_marks_cnt, r->registered_marks);
354 return;
355 }
356
357 rb_gc_mark(r->loc);
358 rb_gc_mark(r->name);
359 /* Only the root scan calls this: a local GC for itself, a global GC for the whole
360 * set under the barrier. A terminated Ractor has left the set; zombie_objspaces
361 * covers it instead. */
362 VM_ASSERT(r == rb_current_ractor_raw(false) || rb_gc_during_global_gc_p());
363 VM_ASSERT(!rb_ractor_status_p(r, ractor_terminated));
364 ractor_mark_unshareable_parts(r);
365
366 /* This Ractor's rb_gc_register_mark_object pins, treated conservatively: a local GC
367 * marks only its own residents and leaves foreign or shareable entries to their
368 * owner or to the global GC. */
369 rb_gc_mark_vm_stack_values((long)r->registered_marks_cnt, r->registered_marks);
370
371}
372
373/* Mark and pin a terminated, unfreed Ractor's return value (legacy); the global GC
374 * calls this via zombie_objspaces. Pinned because compaction does not update C-struct
375 * slots. The default port is covered by the mutual wrapper/port marking instead. */
376void
377rb_ractor_mark_terminated_join_value(rb_ractor_t *r)
378{
379 VALUE slots[] = {
380 r->sync.legacy,
381 };
382 rb_gc_mark_vm_stack_values((long)numberof(slots), slots);
383}
384
385/* Move src's rb_gc_register_mark_object pins to dst. Called before merging src's
386 * objspace into dst, so a pinned object never loses its root in between. An absorb can
387 * run during a GC sweep, so plain realloc keeps it from re-entering GC. */
388void
389rb_ractor_absorb_registered_marks(rb_ractor_t *dst, rb_ractor_t *src)
390{
391 if (src->registered_marks_cnt == 0) return;
392 size_t need = dst->registered_marks_cnt + src->registered_marks_cnt;
393 if (need > dst->registered_marks_capa) {
394 size_t nc = dst->registered_marks_capa ? dst->registered_marks_capa : 64;
395 while (nc < need) nc *= 2;
396 VALUE *p = realloc(dst->registered_marks, nc * sizeof(VALUE));
397 if (!p) rb_bug("rb_ractor_absorb_registered_marks: out of memory");
398 dst->registered_marks = p;
399 dst->registered_marks_capa = nc;
400 }
401 MEMCPY(dst->registered_marks + dst->registered_marks_cnt,
402 src->registered_marks, VALUE, src->registered_marks_cnt);
403 dst->registered_marks_cnt = need;
404 src->registered_marks_cnt = 0;
405}
406
407static int
408free_targeted_hook_lists(st_data_t key, st_data_t val, st_data_t _arg)
409{
410 rb_hook_list_t *hook_list = (rb_hook_list_t*)val;
411 rb_hook_list_free(hook_list);
412 return ST_DELETE;
413}
414
415static void
416free_targeted_hooks(st_table *hooks_tbl)
417{
418 st_foreach(hooks_tbl, free_targeted_hook_lists, 0);
419}
420
421void rb_thread_sched_destroy(struct rb_thread_sched *);
422
423static void
424ractor_free(void *ptr)
425{
426 rb_ractor_t *r = (rb_ractor_t *)ptr;
427 RUBY_DEBUG_LOG("free r:%d", rb_ractor_id(r));
428
429 free_targeted_hooks(&r->pub.targeted_hooks);
430 rb_thread_sched_destroy(&r->threads.sched);
431 rb_native_mutex_destroy(&r->sync.lock);
432 ractor_local_storage_free(r);
433 rb_hook_list_free(&r->pub.hooks);
434 rb_st_free_embedded_table(&r->pub.targeted_hooks);
435
436 if (r->newobj_cache) {
437 RUBY_ASSERT(r == ruby_single_main_ractor);
438
439 rb_gc_ractor_cache_free(r->newobj_cache);
440 r->newobj_cache = NULL;
441 }
442
443 /* Died unjoined and the handle is collected: nobody can inherit it. We are in a
444 * sweep under the global GC barrier, so disown the zombie_objspaces entry and post
445 * the merge to main. main itself only gets here in the free-at-exit walk: leave it
446 * and its objspace to VM destruct. */
447 if (r->objspace && !r->main_ractor) {
448 rb_gc_objspace_disown(r->objspace);
449 r->objspace = NULL;
450 }
451
452 ractor_sync_free(r);
453
454 if (r->in_terminated_set) {
455 rb_native_mutex_lock(&GET_VM()->gc.registered_globals.lock);
456 ccan_list_del(&r->vmlr_node);
457 r->in_terminated_set = false;
458 rb_native_mutex_unlock(&GET_VM()->gc.registered_globals.lock);
459 }
460
461 /* An orphan (unjoined) Ractor hands its rb_gc_register_mark_object pins to main
462 * before its objspace is absorbed; the join path does the same for the joiner.
463 * Both happen before the objspace merge, so no window has unmoved registrations. */
464 if (!r->main_ractor) {
465 rb_ractor_absorb_registered_marks(GET_VM()->ractor.main_ractor, r);
466 }
467 free(r->registered_marks);
468 r->registered_marks = NULL;
469 r->registered_marks_cnt = r->registered_marks_capa = 0;
470
471 if (!r->main_ractor) {
472 SIZED_FREE(r);
473 }
474}
475
476static size_t
477ractor_memsize(const void *ptr)
478{
479 rb_ractor_t *r = (rb_ractor_t *)ptr;
480
481 // TODO: more correct?
482 return sizeof(rb_ractor_t) + ractor_sync_memsize(r);
483}
484
485static void
486ractor_update_references(void *ptr)
487{
488 /* registered_marks are pinned (marked by rb_gc_mark_vm_stack_values), so
489 * compaction does not need to update them. */
490}
491
492static const rb_data_type_t ractor_data_type = {
493 "ractor",
494 {
495 ractor_mark,
496 ractor_free,
497 ractor_memsize,
498 ractor_update_references,
499 },
500 0, 0, RUBY_TYPED_FREE_IMMEDIATELY /* | RUBY_TYPED_WB_PROTECTED */
501};
502
503bool
504rb_ractor_p(VALUE gv)
505{
506 if (rb_typeddata_is_kind_of(gv, &ractor_data_type)) {
507 return true;
508 }
509 else {
510 return false;
511 }
512}
513
514static inline rb_ractor_t *
515RACTOR_PTR(VALUE self)
516{
517 VM_ASSERT(rb_ractor_p(self));
518 rb_ractor_t *r = DATA_PTR(self);
519 return r;
520}
521
522#define MAIN_RACTOR_ID 1
523static rb_atomic_t ractor_last_id = MAIN_RACTOR_ID;
524
525#include "ractor_sync.c"
526
527// creation/termination
528
529static uint32_t
530ractor_next_id(void)
531{
532 uint32_t id;
533
534 id = (uint32_t)(RUBY_ATOMIC_FETCH_ADD(ractor_last_id, 1) + 1);
535
536 return id;
537}
538
539static void
540vm_insert_ractor0(rb_vm_t *vm, rb_ractor_t *r, bool single_ractor_mode)
541{
542 RUBY_DEBUG_LOG("r:%u ractor.cnt:%u++", r->pub.id, vm->ractor.cnt);
543 VM_ASSERT(single_ractor_mode || RB_VM_LOCKED_P());
544
545 /* Incremental marking only runs in a single-objspace world, and nothing later can
546 * finish another objspace's partial mark, so end any cycle in progress before a
547 * second Ractor becomes visible. */
548 if (vm->ractor.cnt == 1) {
549 rb_gc_rest();
550 }
551
552 ccan_list_add_tail(&vm->ractor.set, &r->vmlr_node);
553 vm->ractor.cnt++;
554
555 if (r->newobj_cache) {
556 VM_ASSERT(r == ruby_single_main_ractor);
557 }
558 else {
559 r->newobj_cache = rb_gc_ractor_cache_alloc(r);
560 }
561}
562
563static void
564cancel_single_ractor_mode(void)
565{
566 // enable multi-ractor mode
567 RUBY_DEBUG_LOG("enable multi-ractor mode");
568
569 ruby_single_main_ractor = NULL;
570 rb_yjit_invalidate_single_ractor();
571 rb_zjit_invalidate_single_ractor();
572
573 rb_funcall(rb_cRactor, rb_intern("_activated"), 0);
574}
575
576static void
577vm_insert_ractor(rb_vm_t *vm, rb_ractor_t *r)
578{
579 VM_ASSERT(ractor_status_p(r, ractor_created));
580
581 if (rb_multi_ractor_p()) {
582 RB_VM_LOCK();
583 {
584 vm_insert_ractor0(vm, r, false);
585 vm_ractor_blocking_cnt_inc(vm, r, __FILE__, __LINE__);
586 /* The child is in the set and enumerated on its own now, so drop the cover
587 * through its creator and avoid enumerating it twice. Cleared under the
588 * same VM lock that added it, so no whole-VM walk sees both. */
589 rb_ractor_t *cur = rb_current_ractor_raw(false);
590 if (cur && cur->creating_child_objspace == r->objspace) {
591 cur->creating_child_objspace = NULL;
592 }
593 }
594 RB_VM_UNLOCK();
595 }
596 else {
597 if (vm->ractor.cnt == 0) {
598 // main ractor
599 vm_insert_ractor0(vm, r, true);
600 ractor_status_set(r, ractor_blocking);
601 ractor_status_set(r, ractor_running);
602 }
603 else {
604 cancel_single_ractor_mode();
605 vm_insert_ractor0(vm, r, true);
606 vm_ractor_blocking_cnt_inc(vm, r, __FILE__, __LINE__);
607 /* As in the multi-Ractor branch: the child joined the set, so drop the
608 * creator's cover, or a global GC enumerates the child's objspace twice and
609 * sweeps its live main Thread and root Fiber. */
610 rb_ractor_t *cur = rb_current_ractor_raw(false);
611 if (cur && cur->creating_child_objspace == r->objspace) {
612 cur->creating_child_objspace = NULL;
613 }
614 }
615 }
616}
617
618static void
619vm_remove_ractor(rb_vm_t *vm, rb_ractor_t *cr)
620{
621 VM_ASSERT(ractor_status_p(cr, ractor_running));
622 VM_ASSERT(vm->ractor.cnt > 1);
623 VM_ASSERT(cr->threads.cnt == 1);
624
625 RB_VM_LOCK();
626 {
627 RUBY_DEBUG_LOG("ractor.cnt:%u-- terminate_waiting:%d",
628 vm->ractor.cnt, vm->ractor.sync.terminate_waiting);
629
630 VM_ASSERT(vm->ractor.cnt > 0);
631 ccan_list_del(&cr->vmlr_node);
632
633 /* A single-objspace impl has no zombie_objspaces, so nothing roots the
634 * registered_marks of a Ractor that left the set; track it in a separate list
635 * until ractor_free. */
636 if (!rb_gc_multi_objspace_p()) {
637 rb_native_mutex_lock(&vm->gc.registered_globals.lock);
638 ccan_list_add(&vm->ractor.terminated_set, &cr->vmlr_node);
639 cr->in_terminated_set = true;
640 rb_native_mutex_unlock(&vm->gc.registered_globals.lock);
641 }
642
643 if (vm->ractor.cnt <= 2 && vm->ractor.sync.terminate_waiting) {
644 rb_native_cond_signal(&vm->ractor.sync.terminate_cond);
645 }
646
647 rb_gc_ractor_cache_free(cr->newobj_cache);
648 cr->newobj_cache = NULL;
649
650 /* The objspace loses its owning thread: keep it enumerable until inheritance
651 * merges it. Register in zombie_objspaces BEFORE decrementing cnt: other
652 * Ractors read rb_gc_single_objspace_p lock-free, and the other order opens a
653 * cnt==1-no-zombie window where a GC skips shareable pinning and collects
654 * objects (a cc, say) reachable only through this objspace. */
655 if (cr->objspace) {
656 /* The final self collection already ran (ractor_postmortem_collect),
657 * so the entry measures exactly the pages the joiner will inherit. */
658 rb_gc_objspace_retire(&cr->objspace);
659 }
660 vm->ractor.cnt--;
661
662 ractor_status_set(cr, ractor_terminated);
663 }
664 RB_VM_UNLOCK();
665}
666
667/* The dying thread's final collection of its own objspace, and the capture of what it
668 * must free itself. Called with the GVL still held: a concurrent global GC waits for
669 * this thread's safepoint, so the collection is lock-free like any local GC. */
670static void
671ractor_postmortem_collect(rb_thread_t *th, struct rb_ractor_postmortem_frees *pf)
672{
673 rb_ractor_t *const cr = th->ractor;
674 VM_ASSERT(cr != GET_VM()->ractor.main_ractor);
675 VM_ASSERT(th->ec != NULL);
676
677 struct rb_fiber_struct *const fiber = th->ec->fiber_ptr;
678 const bool fiber_wrapped = fiber && rb_fiberptr_self(fiber) != 0;
679
680 /* With a thread-event hook registered the callbacks may retain any Ractor's Thread
681 * object (rb_internal_thread_event_hook_t), and such references are invisible to
682 * the reduced roots: keep the ordinary retire roots, wrappers survive to absorb. */
683 cr->postmortem = rb_gc_multi_objspace_p() && !rb_thread_event_hooks_registered_p();
684 rb_gc_objspace_postmortem_self();
685
686 /* Only what this collection provably swept (wrapper gone, struct deferred with an
687 * in-band mark) may be touched after vm_remove_ractor unlinks the Ractor. */
688 pf->th = (th->self == 0) ? th : NULL;
689 pf->fiber = (fiber_wrapped && rb_fiberptr_self(fiber) == 0) ? fiber : NULL;
690}
691
692void
693rb_ractor_postmortem_free(const struct rb_ractor_postmortem_frees *pf)
694{
695 if (pf->fiber == NULL && pf->th == NULL) return;
696
697 /* The frees below resolve their objspace through the TLS ec, which sits inside
698 * pf->fiber and leads to the retired Ractor: cut the resolution off so they fall
699 * back to the main objspace. (The MN epilogue has already done this.) */
700#ifdef RB_THREAD_LOCAL_SPECIFIER
701 rb_current_ec_set(NULL);
702#else
703 native_tls_set(ruby_current_ec_key, NULL);
704#endif
705
706 /* the fiber struct embeds the thread's final ec, so free it first */
707 if (pf->fiber) rb_fiber_free_body(pf->fiber);
708 if (pf->th) rb_thread_free_body(pf->th);
709}
710
711static VALUE
712ractor_alloc(VALUE klass)
713{
714 rb_ractor_t *r;
715 VALUE rv = TypedData_Make_Struct(klass, rb_ractor_t, &ractor_data_type, r);
717 rb_gc_obj_became_shareable(rv);
718 r->pub.self = rv;
719 r->next_ec_serial = 1;
720 VM_ASSERT(ractor_status_p(r, ractor_created));
721 return rv;
722}
723
724static rb_ractor_t _main_ractor = {
725 .loc = Qnil,
726 .name = Qnil,
727 .pub.id = MAIN_RACTOR_ID,
728 .pub.self = Qnil,
729 .next_ec_serial = 1,
730 .main_ractor = true,
731};
732
734rb_ractor_main_alloc(void)
735{
736 rb_ractor_t *r = &_main_ractor;
737 /* The main Ractor is allocated before its objspace exists, so its newobj cache is
738 * created later in Init_BareVM, once rb_gc_init_objspaces has set r->objspace. */
739 ruby_single_main_ractor = r;
740
741 return r;
742}
743
744#if defined(HAVE_WORKING_FORK)
745// Set up the main Ractor for the VM after fork.
746// Puts us in "single Ractor mode"
747void
748rb_ractor_atfork(rb_vm_t *vm, rb_thread_t *th)
749{
750 // initialize as a main ractor
751 vm->ractor.cnt = 0;
752 vm->ractor.blocking_cnt = 0;
753 /* Only main survives a fork: the holds of dead Ractors and of critical sections are
754 * gone, leaving main's own disable. */
755 rb_gc_disable_holders_atfork();
756 /* Only the main Ractor survives a fork, so drop the creation cover. The set was
757 * just emptied by rb_vm_living_threads_init, and zombie_objspaces still holds the
758 * non-main objspaces that terminate_atfork parked there for the orphan merge. */
759 th->ractor->creating_child_objspace = NULL;
760 ruby_single_main_ractor = th->ractor;
761 th->ractor->status_ = ractor_created;
762
763 rb_ractor_living_threads_init(th->ractor);
764 rb_ractor_living_threads_insert(th->ractor, th);
765
766 VM_ASSERT(vm->ractor.blocking_cnt == 0);
767 VM_ASSERT(vm->ractor.cnt == 1);
768}
769
770void
771rb_ractor_terminate_atfork(rb_vm_t *vm, rb_ractor_t *r)
772{
773 rb_gc_ractor_cache_free(r->newobj_cache);
774 r->newobj_cache = NULL;
775 r->status_ = ractor_terminated;
776 // a termination epilogue in the parent did not survive the fork
777 r->threads.dying_th = NULL;
778 /* In a forked child every other Ractor is terminated-unjoined, so keep its objspace
779 * enumerable until a join or a global GC merges it. */
780 if (r->objspace) {
781 rb_gc_objspace_retire(&r->objspace);
782 }
783 ractor_sync_terminate_atfork(vm, r);
784}
785#endif
786
787void rb_thread_sched_init(struct rb_thread_sched *, bool atfork);
788
789void
790rb_ractor_living_threads_init(rb_ractor_t *r)
791{
792 ccan_list_head_init(&r->threads.set);
793 r->threads.cnt = 0;
794 r->threads.blocking_cnt = 0;
795 r->threads.terminating = false;
796 // atfork: a sibling's termination epilogue did not survive the fork
797 r->threads.dying_th = NULL;
798}
799
800static void
801ractor_init(rb_ractor_t *r, VALUE name, VALUE loc)
802{
803 ractor_sync_init(r);
804 st_init_existing_numtable_with_size(&r->pub.targeted_hooks, 0);
805 r->pub.hooks.type = hook_list_type_ractor_local;
806
807 // thread management
808 rb_thread_sched_init(&r->threads.sched, false);
809 rb_ractor_living_threads_init(r);
810
811 // naming
812 if (!NIL_P(name)) {
813 rb_encoding *enc;
814 StringValueCStr(name);
815 enc = rb_enc_get(name);
816 if (!rb_enc_asciicompat(enc)) {
817 rb_raise(rb_eArgError, "ASCII incompatible encoding (%s)",
818 rb_enc_name(enc));
819 }
820 name = RB_OBJ_SET_SHAREABLE(rb_str_new_frozen(name));
821 }
822
823 if (!SPECIAL_CONST_P(loc)) RB_OBJ_SET_SHAREABLE(loc);
824 r->loc = loc;
825 r->name = name;
826}
827
828void
829rb_ractor_main_setup(rb_vm_t *vm, rb_ractor_t *r, rb_thread_t *th)
830{
831 VALUE rv = r->pub.self = TypedData_Wrap_Struct(rb_cRactor, &ractor_data_type, r);
832 FL_SET_RAW(r->pub.self, RUBY_FL_SHAREABLE);
833 rb_gc_obj_became_shareable(r->pub.self);
834 ractor_init(r, Qnil, Qnil);
835 r->threads.main = th;
836 rb_ractor_living_threads_insert(r, th);
837 rb_ractor_setup_default_port(r);
838
839 RB_GC_GUARD(rv);
840}
841
842static VALUE
843ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VALUE args, VALUE block)
844{
845 VALUE rv = ractor_alloc(self);
846 rb_ractor_t *r = RACTOR_PTR(rv);
847 ractor_init(r, name, loc);
848
849 r->pub.id = ractor_next_id();
850 RUBY_DEBUG_LOG("r:%u", r->pub.id);
851
852 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
853 r->verbose = cr->verbose;
854 r->debug = cr->debug;
855
856 /* Every Ractor has an objspace, and it must exist before its thread runs: the
857 * first allocation goes there through rb_gc_get_objspace. */
858 r->objspace = rb_gc_objspace_alloc();
859
860 rb_thread_create_ractor(r, args, block);
861
862 RB_GC_GUARD(rv);
863 return rv;
864}
865
866#if 0
867static VALUE
868ractor_create_func(VALUE klass, VALUE loc, VALUE name, VALUE args, rb_block_call_func_t func)
869{
870 VALUE block = rb_proc_new(func, Qnil);
871 return ractor_create(rb_current_ec_noinline(), klass, loc, name, args, block);
872}
873#endif
874
875static void
876ractor_atexit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE result, bool exc)
877{
878 ractor_notify_exit(ec, cr, result, exc);
879}
880
881/* The dying thread's last work inside its Ractor. The order is the point: a joiner woken
882 * before the collection spins in ractor_value for the whole of it. */
883void
884rb_ractor_postmortem(rb_thread_t *th, struct rb_ractor_postmortem_frees *pf)
885{
886 ractor_postmortem_collect(th, pf);
887 ractor_send_exit_tokens(th->ec, th->ractor);
888}
889
890void
891rb_ractor_atexit(rb_execution_context_t *ec, VALUE result)
892{
893 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
894 ractor_atexit(ec, cr, result, false);
895}
896
897void
898rb_ractor_atexit_exception(rb_execution_context_t *ec)
899{
900 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
901 ractor_atexit(ec, cr, ec->errinfo, true);
902}
903
904void
905rb_ractor_teardown(rb_execution_context_t *ec)
906{
907 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
908
909 // sync with rb_ractor_terminate_interrupt_main_thread()
910 RB_VM_LOCKING() {
911 VM_ASSERT(cr->threads.main != NULL);
912 cr->threads.main = NULL;
913 }
914}
915
916void
917rb_ractor_receive_parameters(rb_execution_context_t *ec, rb_ractor_t *r, int len, VALUE *ptr)
918{
919 for (int i=0; i<len; i++) {
920 ptr[i] = ractor_receive(ec, ractor_default_port(r), NULL);
921 }
922}
923
924void
925rb_ractor_send_parameters(rb_execution_context_t *ec, rb_ractor_t *r, VALUE args)
926{
927 int len = RARRAY_LENINT(args);
928 for (int i=0; i<len; i++) {
929 ractor_send(ec, ractor_default_port(r), RARRAY_AREF(args, i), false);
930 }
931}
932
933bool
934rb_ractor_main_p_(void)
935{
936 VM_ASSERT(rb_multi_ractor_p());
937 rb_execution_context_t *ec = GET_EC();
938 return rb_ec_ractor_ptr(ec) == rb_ec_vm_ptr(ec)->ractor.main_ractor;
939}
940
941int
942rb_ractor_living_thread_num(const rb_ractor_t *r)
943{
944 return r->threads.cnt;
945}
946
947// only for current ractor
948VALUE
949rb_ractor_thread_list(void)
950{
951 rb_ractor_t *r = GET_RACTOR();
952 rb_thread_t *th = 0;
953 VALUE ary = rb_ary_new();
954
955 ccan_list_for_each(&r->threads.set, th, lt_node) {
956 switch (th->status) {
957 case THREAD_RUNNABLE:
958 case THREAD_STOPPED:
959 case THREAD_STOPPED_FOREVER:
960 rb_ary_push(ary, th->self);
961 default:
962 break;
963 }
964 }
965
966 return ary;
967}
968
969void
970rb_ractor_living_threads_insert(rb_ractor_t *r, rb_thread_t *th)
971{
972 VM_ASSERT(th != NULL);
973
974 RACTOR_LOCK(r);
975 {
976 RUBY_DEBUG_LOG("r(%d)->threads.cnt:%d++", r->pub.id, r->threads.cnt);
977 ccan_list_add_tail(&r->threads.set, &th->lt_node);
978 r->threads.cnt++;
979 }
980 RACTOR_UNLOCK(r);
981
982 // first thread for a ractor
983 if (r->threads.cnt == 1) {
984 VM_ASSERT(ractor_status_p(r, ractor_created));
985 vm_insert_ractor(th->vm, r);
986 }
987}
988
989static void
990vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line)
991{
992 ractor_status_set(r, ractor_blocking);
993
994 RUBY_DEBUG_LOG2(file, line, "vm->ractor.blocking_cnt:%d++", vm->ractor.blocking_cnt);
995 vm->ractor.blocking_cnt++;
996 VM_ASSERT(vm->ractor.blocking_cnt <= vm->ractor.cnt);
997}
998
999void
1000rb_vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
1001{
1002 ASSERT_vm_locking();
1003 VM_ASSERT(GET_RACTOR() == cr);
1004 vm_ractor_blocking_cnt_inc(vm, cr, file, line);
1005}
1006
1007void
1008rb_vm_ractor_blocking_cnt_dec(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
1009{
1010 ASSERT_vm_locking();
1011 VM_ASSERT(GET_RACTOR() == cr);
1012
1013 RUBY_DEBUG_LOG2(file, line, "vm->ractor.blocking_cnt:%d--", vm->ractor.blocking_cnt);
1014 VM_ASSERT(vm->ractor.blocking_cnt > 0);
1015 vm->ractor.blocking_cnt--;
1016
1017 ractor_status_set(cr, ractor_running);
1018}
1019
1020/* Remove a child that never started (send_parameters failed during creation). The
1021 * creator calls this (rb_ractor_living_threads_remove assumes the current Ractor);
1022 * leaving the set and disowning the objspace share one VM-lock section, no window. */
1023void
1024rb_ractor_cancel_creation(rb_ractor_t *r, rb_thread_t *th)
1025{
1026 RACTOR_LOCK(r);
1027 {
1028 ccan_list_del(&th->lt_node);
1029 r->threads.cnt--;
1030 }
1031 RACTOR_UNLOCK(r);
1032
1033 RB_VM_LOCK();
1034 {
1035 rb_vm_t *vm = th->vm;
1036 VM_ASSERT(vm->ractor.cnt > 1);
1037 ccan_list_del(&r->vmlr_node);
1038 vm->ractor.cnt--;
1039 /* Give back the blocking count vm_insert_ractor took at insert time. A child
1040 * that never ran has no chance to decrement it, and without this the
1041 * blocking_cnt <= cnt invariant breaks on the next insert. */
1042 VM_ASSERT(r->status_ == ractor_blocking);
1043 VM_ASSERT(vm->ractor.blocking_cnt > 0);
1044 vm->ractor.blocking_cnt--;
1045
1046 rb_gc_ractor_cache_free(r->newobj_cache);
1047 r->newobj_cache = NULL;
1048
1049 if (r->objspace) {
1050 rb_gc_objspace_disown(r->objspace);
1051 r->objspace = NULL;
1052 }
1053 r->status_ = ractor_terminated;
1054 }
1055 RB_VM_UNLOCK();
1056}
1057
1058void
1059rb_ractor_living_threads_remove(rb_ractor_t *cr, rb_thread_t *th)
1060{
1061 VM_ASSERT(cr == GET_RACTOR());
1062 RUBY_DEBUG_LOG("r->threads.cnt:%d--", cr->threads.cnt);
1063
1064 if (cr->threads.cnt == 1) {
1065 vm_remove_ractor(th->vm, cr);
1066 }
1067 else {
1068 RACTOR_LOCK(cr);
1069 {
1070 ccan_list_del(&th->lt_node);
1071 cr->threads.cnt--;
1072 }
1073 RACTOR_UNLOCK(cr);
1074 }
1075}
1076
1077void
1078rb_ractor_blocking_threads_inc(rb_ractor_t *cr, const char *file, int line)
1079{
1080 RUBY_DEBUG_LOG2(file, line, "cr->threads.blocking_cnt:%d++", cr->threads.blocking_cnt);
1081
1082 VM_ASSERT(cr->threads.cnt > 0);
1083 VM_ASSERT(cr == GET_RACTOR());
1084
1085 cr->threads.blocking_cnt++;
1086}
1087
1088void
1089rb_ractor_blocking_threads_dec(rb_ractor_t *cr, const char *file, int line)
1090{
1091 RUBY_DEBUG_LOG2(file, line,
1092 "r->threads.blocking_cnt:%d--, r->threads.cnt:%u",
1093 cr->threads.blocking_cnt, cr->threads.cnt);
1094
1095 VM_ASSERT(cr == GET_RACTOR());
1096
1097 cr->threads.blocking_cnt--;
1098}
1099
1100void
1101rb_ractor_vm_barrier_interrupt_running_thread(rb_ractor_t *r)
1102{
1103 VM_ASSERT(r != GET_RACTOR());
1104 ASSERT_ractor_unlocking(r);
1105 ASSERT_vm_locking();
1106
1107 RACTOR_LOCK(r);
1108 {
1109 if (ractor_status_p(r, ractor_running)) {
1110 rb_execution_context_t *ec = r->threads.running_ec;
1111 if (ec) {
1112 RUBY_VM_SET_VM_BARRIER_INTERRUPT(ec);
1113 }
1114 }
1115 }
1116 RACTOR_UNLOCK(r);
1117}
1118
1119void
1120rb_ractor_terminate_interrupt_main_thread(rb_ractor_t *r)
1121{
1122 VM_ASSERT(r != GET_RACTOR());
1123 ASSERT_ractor_unlocking(r);
1124 ASSERT_vm_locking();
1125
1126 rb_thread_t *main_th = r->threads.main;
1127 if (main_th) {
1128 if (main_th->status != THREAD_KILLED) {
1129 RUBY_VM_SET_TERMINATE_INTERRUPT(main_th->ec);
1130 rb_threadptr_interrupt(main_th);
1131 }
1132 else {
1133 RUBY_DEBUG_LOG("killed (%p)", (void *)main_th);
1134 }
1135 }
1136}
1137
1138void rb_thread_terminate_all(rb_thread_t *th); // thread.c
1139
1140static void
1141ractor_terminal_interrupt_all(rb_vm_t *vm)
1142{
1143 if (vm->ractor.cnt > 1) {
1144 // send terminate notification to all ractors
1145 rb_ractor_t *r = 0;
1146 ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
1147 if (r != vm->ractor.main_ractor) {
1148 RUBY_DEBUG_LOG("r:%d", rb_ractor_id(r));
1149 rb_ractor_terminate_interrupt_main_thread(r);
1150 }
1151 }
1152 }
1153}
1154
1155void rb_add_running_thread(rb_thread_t *th);
1156void rb_del_running_thread(rb_thread_t *th);
1157
1158void
1159rb_ractor_terminate_all(void)
1160{
1161 rb_vm_t *vm = GET_VM();
1162 rb_ractor_t *cr = vm->ractor.main_ractor;
1163
1164 RUBY_DEBUG_LOG("ractor.cnt:%d", (int)vm->ractor.cnt);
1165
1166 VM_ASSERT(cr == GET_RACTOR()); // only main-ractor's main-thread should kick it.
1167
1168 RB_VM_LOCK();
1169 {
1170 ractor_terminal_interrupt_all(vm); // kill all ractors
1171 }
1172 RB_VM_UNLOCK();
1173 rb_thread_terminate_all(GET_THREAD()); // kill other threads in main-ractor and wait
1174
1175 RB_VM_LOCK();
1176 {
1177 while (vm->ractor.cnt > 1) {
1178 RUBY_DEBUG_LOG("terminate_waiting:%d", vm->ractor.sync.terminate_waiting);
1179 vm->ractor.sync.terminate_waiting = true;
1180
1181 // wait for 1sec
1182 rb_vm_ractor_blocking_cnt_inc(vm, cr, __FILE__, __LINE__);
1183 rb_del_running_thread(rb_ec_thread_ptr(cr->threads.running_ec));
1184 rb_vm_cond_timedwait(vm, &vm->ractor.sync.terminate_cond, 1000 /* ms */);
1185 while (vm->ractor.sched.barrier_is_waiting) {
1186 // A barrier is waiting. Threads relinquish the VM lock before joining the barrier and
1187 // since we just acquired the VM lock back, we're blocking other threads from joining it.
1188 // We loop until the barrier is over. We can't join this barrier because our thread isn't added to
1189 // running_threads until the call below to `rb_add_running_thread`.
1190 RB_VM_UNLOCK();
1191 unsigned int lev;
1192 RB_VM_LOCK_ENTER_LEV_NB(&lev);
1193 }
1194 rb_add_running_thread(rb_ec_thread_ptr(cr->threads.running_ec));
1195 rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__);
1196
1197 ractor_terminal_interrupt_all(vm);
1198 }
1199 }
1200 RB_VM_UNLOCK();
1201
1202 /* Every other Ractor is dead. main inherits all uninherited objspaces, so the
1203 * remaining at-exit work (finalizers, IO flush, free-at-exit) sees every object. */
1204 rb_gc_objspace_absorb_all_zombies();
1205}
1206
1208rb_vm_main_ractor_ec(rb_vm_t *vm)
1209{
1210 /* This code needs to carefully work around two bugs:
1211 * - Bug #20016: When M:N threading is enabled, running_ec is NULL if no thread is
1212 * actually currently running (as opposed to without M:N threading, when
1213 * running_ec will still point to the _last_ thread which ran)
1214 * - Bug #20197: If the main thread is sleeping, setting its postponed job
1215 * interrupt flag is pointless; it won't look at the flag until it stops sleeping
1216 * for some reason. It would be better to set the flag on the running ec, which
1217 * will presumably look at it soon.
1218 *
1219 * Solution: use running_ec if it's set, otherwise fall back to the main thread ec.
1220 * This is still susceptible to some rare race conditions (what if the last thread
1221 * to run just entered a long-running sleep?), but seems like the best balance of
1222 * robustness and complexity.
1223 */
1224 rb_execution_context_t *running_ec = vm->ractor.main_ractor->threads.running_ec;
1225 if (running_ec) { return running_ec; }
1226 return vm->ractor.main_thread->ec;
1227}
1228
1229static VALUE
1230ractor_moved_missing(int argc, VALUE *argv, VALUE self)
1231{
1232 rb_raise(rb_eRactorMovedError, "can not send any methods to a moved object");
1233}
1234
1235/*
1236 * Document-class: Ractor::Error
1237 *
1238 * The parent class of Ractor-related error classes.
1239 */
1240
1241/*
1242 * Document-class: Ractor::ClosedError
1243 *
1244 * Raised when an attempt is made to send a message to a closed port,
1245 * or to retrieve a message from a closed and empty port.
1246 * Ports may be closed explicitly with Ractor::Port#close
1247 * and are closed implicitly when a Ractor terminates.
1248 *
1249 * port = Ractor::Port.new
1250 * port.close
1251 * port << "test" # Ractor::ClosedError
1252 * port.receive # Ractor::ClosedError
1253 *
1254 * ClosedError is a descendant of StopIteration, so the closing of a port will break
1255 * out of loops without propagating the error.
1256 */
1257
1258/*
1259 * Document-class: Ractor::IsolationError
1260 *
1261 * Raised on attempt to make a Ractor-unshareable object
1262 * Ractor-shareable.
1263 */
1264
1265/*
1266 * Document-class: Ractor::RemoteError
1267 *
1268 * Raised on Ractor#join or Ractor#value if there was an uncaught exception in the Ractor.
1269 * Its +cause+ will contain the original exception, and +ractor+ is the original ractor
1270 * it was raised in.
1271 *
1272 * r = Ractor.new { raise "Something weird happened" }
1273 *
1274 * begin
1275 * r.value
1276 * rescue => e
1277 * p e # => #<Ractor::RemoteError: thrown by remote Ractor.>
1278 * p e.ractor == r # => true
1279 * p e.cause # => #<RuntimeError: Something weird happened>
1280 * end
1281 *
1282 */
1283
1284/*
1285 * Document-class: Ractor::MovedError
1286 *
1287 * Raised on an attempt to access an object which was moved in Ractor#send or Ractor::Port#send.
1288 *
1289 * r = Ractor.new { sleep }
1290 *
1291 * ary = [1, 2, 3]
1292 * r.send(ary, move: true)
1293 * ary.inspect
1294 * # Ractor::MovedError (can not send any methods to a moved object)
1295 *
1296 */
1297
1298/*
1299 * Document-class: Ractor::MovedObject
1300 *
1301 * A special object which replaces any value that was moved to another ractor in Ractor#send
1302 * or Ractor::Port#send. Any attempt to access the object results in Ractor::MovedError.
1303 *
1304 * r = Ractor.new { receive }
1305 *
1306 * ary = [1, 2, 3]
1307 * r.send(ary, move: true)
1308 * p Ractor::MovedObject === ary
1309 * # => true
1310 * ary.inspect
1311 * # Ractor::MovedError (can not send any methods to a moved object)
1312 */
1313
1314/*
1315 * Document-class: Ractor::UnsafeError
1316 *
1317 * Raised when Ractor-unsafe C-methods is invoked by a non-main Ractor.
1318 */
1319
1320// Main docs are in ractor.rb, but without this clause there are weird artifacts
1321// in their rendering.
1322/*
1323 * Document-class: Ractor
1324 *
1325 */
1326
1327void
1328Init_Ractor(void)
1329{
1330 rb_cRactor = rb_define_class("Ractor", rb_cObject);
1332
1333 rb_eRactorError = rb_define_class_under(rb_cRactor, "Error", rb_eRuntimeError);
1334 rb_eRactorIsolationError = rb_define_class_under(rb_cRactor, "IsolationError", rb_eRactorError);
1335 rb_eRactorRemoteError = rb_define_class_under(rb_cRactor, "RemoteError", rb_eRactorError);
1336 rb_eRactorMovedError = rb_define_class_under(rb_cRactor, "MovedError", rb_eRactorError);
1337 rb_eRactorClosedError = rb_define_class_under(rb_cRactor, "ClosedError", rb_eStopIteration);
1338 rb_eRactorUnsafeError = rb_define_class_under(rb_cRactor, "UnsafeError", rb_eRactorError);
1339
1340 rb_cRactorMovedObject = rb_define_class_under(rb_cRactor, "MovedObject", rb_cBasicObject);
1341 rb_undef_alloc_func(rb_cRactorMovedObject);
1342 rb_define_method(rb_cRactorMovedObject, "method_missing", ractor_moved_missing, -1);
1343
1344 // override methods defined in BasicObject
1345 rb_define_method(rb_cRactorMovedObject, "__send__", ractor_moved_missing, -1);
1346 rb_define_method(rb_cRactorMovedObject, "!", ractor_moved_missing, -1);
1347 rb_define_method(rb_cRactorMovedObject, "==", ractor_moved_missing, -1);
1348 rb_define_method(rb_cRactorMovedObject, "!=", ractor_moved_missing, -1);
1349 rb_define_method(rb_cRactorMovedObject, "__id__", ractor_moved_missing, -1);
1350 rb_define_method(rb_cRactorMovedObject, "equal?", ractor_moved_missing, -1);
1351 rb_define_method(rb_cRactorMovedObject, "instance_eval", ractor_moved_missing, -1);
1352 rb_define_method(rb_cRactorMovedObject, "instance_exec", ractor_moved_missing, -1);
1353
1354 Init_RactorPort();
1355}
1356
1357void
1358rb_ractor_dump(void)
1359{
1360 rb_vm_t *vm = GET_VM();
1361 rb_ractor_t *r = 0;
1362
1363 ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
1364 if (r != vm->ractor.main_ractor) {
1365 fprintf(stderr, "r:%u (%s)\n", r->pub.id, ractor_status_str(r->status_));
1366 }
1367 }
1368}
1369
1370VALUE
1372{
1373 if (rb_ractor_main_p()) {
1374 return rb_stdin;
1375 }
1376 else {
1377 rb_ractor_t *cr = GET_RACTOR();
1378 if (UNLIKELY(cr->r_stdin == 0)) {
1379 cr->r_stdin = rb_io_prep_stdin();
1380 }
1381 return cr->r_stdin;
1382 }
1383}
1384
1385VALUE
1386rb_ractor_stdout(void)
1387{
1388 if (rb_ractor_main_p()) {
1389 return rb_stdout;
1390 }
1391 else {
1392 rb_ractor_t *cr = GET_RACTOR();
1393 if (UNLIKELY(cr->r_stdout == 0)) {
1394 cr->r_stdout = rb_io_prep_stdout();
1395 }
1396 return cr->r_stdout;
1397 }
1398}
1399
1400VALUE
1401rb_ractor_stderr(void)
1402{
1403 if (rb_ractor_main_p()) {
1404 return rb_stderr;
1405 }
1406 else {
1407 rb_ractor_t *cr = GET_RACTOR();
1408 if (UNLIKELY(cr->r_stderr == 0)) {
1409 cr->r_stderr = rb_io_prep_stderr();
1410 }
1411 return cr->r_stderr;
1412 }
1413}
1414
1415void
1417{
1418 if (rb_ractor_main_p()) {
1419 rb_stdin = in;
1420 }
1421 else {
1422 rb_ractor_t *cr = GET_RACTOR();
1423 RB_OBJ_WRITE(cr->pub.self, &cr->r_stdin, in);
1424 }
1425}
1426
1427void
1429{
1430 if (rb_ractor_main_p()) {
1431 rb_stdout = out;
1432 }
1433 else {
1434 rb_ractor_t *cr = GET_RACTOR();
1435 RB_OBJ_WRITE(cr->pub.self, &cr->r_stdout, out);
1436 }
1437}
1438
1439void
1441{
1442 if (rb_ractor_main_p()) {
1443 rb_stderr = err;
1444 }
1445 else {
1446 rb_ractor_t *cr = GET_RACTOR();
1447 RB_OBJ_WRITE(cr->pub.self, &cr->r_stderr, err);
1448 }
1449}
1450
1452rb_ractor_hooks(rb_ractor_t *cr)
1453{
1454 return &cr->pub.hooks;
1455}
1456
1457st_table *
1458rb_ractor_targeted_hooks(rb_ractor_t *cr)
1459{
1460 return &cr->pub.targeted_hooks;
1461}
1462
1463static void
1464rb_obj_set_shareable_no_assert(VALUE obj)
1465{
1466 /* make_shareable_check_shareable refuses an IO, because the traversal cannot reach
1467 * the VALUE members inside its fptr. */
1468 VM_ASSERT(!RB_TYPE_P(obj, T_FILE));
1469
1471 rb_gc_obj_became_shareable(obj);
1472
1473 /* A T_OBJECT can have a fields imemo too (too_complex and friends), and an imemo
1474 * born while its owner was unshareable stays unshareable
1475 * (imemo_fields_complex_from_obj), so align it here. */
1476 if (rb_obj_gen_fields_p(obj) || BUILTIN_TYPE(obj) == T_OBJECT) {
1477 /* obj is shareable already, so rb_obj_fields_no_ractor_check finds the right
1478 * table. Make the fields imemo itself shareable and record shrefs for the
1479 * hidden field values the traversal never reaches. */
1480 VALUE fields = rb_obj_fields_no_ractor_check(obj);
1481 if (imemo_type_p(fields, imemo_fields)) {
1482 // no recursive mark
1483 FL_SET_RAW(fields, FL_SHAREABLE);
1484 rb_gc_obj_became_shareable(fields);
1485 // Field values the traversal never reaches (hidden internal ivars, say)
1486 // can stay unshareable, so record their shrefs to keep the shareable
1487 // fields imemo's edges correct.
1488 rb_imemo_fields_record_shrefs(fields);
1489 }
1490 }
1491}
1492
1493#ifndef STRICT_VERIFY_SHAREABLE
1494#define STRICT_VERIFY_SHAREABLE 0
1495#endif
1496
1497bool
1498rb_ractor_verify_shareable(VALUE obj)
1499{
1500#if STRICT_VERIFY_SHAREABLE
1501 rb_gc_verify_shareable(obj);
1502#endif
1503 return true;
1504}
1505
1506VALUE
1507rb_obj_set_shareable(VALUE obj)
1508{
1510
1511 rb_obj_set_shareable_no_assert(obj);
1512 RUBY_ASSERT(rb_ractor_verify_shareable(obj));
1513
1514 return obj;
1515}
1516
1518
1519// 2: stop search
1520// 1: skip child
1521// 0: continue
1522
1523enum obj_traverse_iterator_result {
1524 traverse_cont,
1525 traverse_skip,
1526 traverse_stop,
1527};
1528
1529typedef enum obj_traverse_iterator_result (*rb_obj_traverse_enter_func)(VALUE obj);
1530typedef enum obj_traverse_iterator_result (*rb_obj_traverse_leave_func)(VALUE obj);
1531typedef enum obj_traverse_iterator_result (*rb_obj_traverse_final_func)(VALUE obj);
1532
1533static enum obj_traverse_iterator_result null_leave(VALUE obj);
1534
1536 rb_obj_traverse_enter_func enter_func;
1537 rb_obj_traverse_leave_func leave_func;
1538
1539 st_table *rec;
1540 VALUE rec_hash;
1541};
1542
1543
1545 bool stop;
1546 struct obj_traverse_data *data;
1547};
1548
1549static int obj_traverse_i(VALUE obj, struct obj_traverse_data *data);
1550
1551static int
1552obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
1553{
1555
1556 if (obj_traverse_i(key, d->data)) {
1557 d->stop = true;
1558 return ST_STOP;
1559 }
1560
1561 if (obj_traverse_i(val, d->data)) {
1562 d->stop = true;
1563 return ST_STOP;
1564 }
1565
1566 return ST_CONTINUE;
1567}
1568
1569static void
1570obj_traverse_reachable_i(VALUE obj, void *ptr)
1571{
1573
1574 if (obj_traverse_i(obj, d->data)) {
1575 d->stop = true;
1576 }
1577}
1578
1579// Traverse obj's children via its GC mark function. Returns 1 to stop.
1580static int
1581obj_traverse_reachable(VALUE obj, struct obj_traverse_data *data)
1582{
1583 struct obj_traverse_callback_data d = {
1584 .stop = false,
1585 .data = data,
1586 };
1587 RB_VM_LOCKING_NO_BARRIER() {
1588 rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d);
1589 }
1590 return d.stop;
1591}
1592
1593static struct st_table *
1594obj_traverse_rec(struct obj_traverse_data *data)
1595{
1596 if (UNLIKELY(!data->rec)) {
1597 data->rec_hash = rb_ident_hash_new();
1598 rb_obj_hide(data->rec_hash);
1599 data->rec = RHASH_ST_TABLE(data->rec_hash);
1600 }
1601 return data->rec;
1602}
1603
1604static int
1605obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
1606{
1608
1609 if (obj_traverse_i(val, d->data)) {
1610 d->stop = true;
1611 return ST_STOP;
1612 }
1613
1614 return ST_CONTINUE;
1615}
1616
1617static int
1618obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
1619{
1620 if (RB_SPECIAL_CONST_P(obj)) return 0;
1621
1622 switch (data->enter_func(obj)) {
1623 case traverse_cont: break;
1624 case traverse_skip: return 0; // skip children
1625 case traverse_stop: return 1; // stop search
1626 }
1627
1628 if (UNLIKELY(st_insert(obj_traverse_rec(data), obj, 1))) {
1629 // already traversed
1630 return 0;
1631 }
1632 RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj);
1633
1634 if (rb_obj_shape_has_ivars(obj)) {
1635 struct obj_traverse_callback_data d = {
1636 .stop = false,
1637 .data = data,
1638 };
1639 rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1640 if (d.stop) return 1;
1641 }
1642
1643 switch (BUILTIN_TYPE(obj)) {
1644 // no child node
1645 case T_STRING:
1646 case T_FLOAT:
1647 case T_BIGNUM:
1648 case T_REGEXP:
1649 case T_SYMBOL:
1650 break;
1651
1652 case T_OBJECT:
1653 /* Instance variables already traversed. */
1654 break;
1655
1656 case T_ARRAY:
1657 {
1658 rb_ary_cancel_sharing(obj);
1659
1660 for (int i = 0; i < RARRAY_LENINT(obj); i++) {
1661 VALUE e = RARRAY_AREF(obj, i);
1662 if (obj_traverse_i(e, data)) return 1;
1663 }
1664 }
1665 break;
1666
1667 case T_HASH:
1668 {
1669 if (obj_traverse_i(RHASH_IFNONE(obj), data)) return 1;
1670
1671 struct obj_traverse_callback_data d = {
1672 .stop = false,
1673 .data = data,
1674 };
1675 rb_hash_foreach(obj, obj_hash_traverse_i, (VALUE)&d);
1676 if (d.stop) return 1;
1677 }
1678 break;
1679
1680 case T_STRUCT:
1681 {
1682 long len = RSTRUCT_LEN_RAW(obj);
1683 const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
1684
1685 for (long i=0; i<len; i++) {
1686 if (obj_traverse_i(ptr[i], data)) return 1;
1687 }
1688 }
1689 break;
1690
1691 case T_MATCH:
1692 if (obj_traverse_i(RMATCH(obj)->str, data)) return 1;
1693 break;
1694
1695 case T_RATIONAL:
1696 if (obj_traverse_i(RRATIONAL(obj)->num, data)) return 1;
1697 if (obj_traverse_i(RRATIONAL(obj)->den, data)) return 1;
1698 break;
1699 case T_COMPLEX:
1700 if (obj_traverse_i(RCOMPLEX(obj)->real, data)) return 1;
1701 if (obj_traverse_i(RCOMPLEX(obj)->imag, data)) return 1;
1702 break;
1703
1704 case T_DATA:
1705 {
1706 void *const ptr = RTYPEDDATA_GET_DATA(obj);
1707 const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
1708
1709 if (!ptr || !type->function.dmark) {
1710 // no references (the class and ivars are handled elsewhere)
1711 }
1712 else if (type->flags & RUBY_TYPED_DECL_MARKING) {
1713 const size_t *offsets = (const size_t *)(uintptr_t)type->function.dmark;
1714 for (; *offsets != RUBY_REF_END; offsets++) {
1715 VALUE ref = *(VALUE *)((char *)ptr + *offsets);
1716 if (obj_traverse_i(ref, data)) return 1;
1717 }
1718 }
1719 else {
1720 if (obj_traverse_reachable(obj, data)) return 1;
1721 }
1722 }
1723 break;
1724
1725 case T_IMEMO:
1726 // TODO: Not sure this can actually happen; traverse rather than crash.
1727 if (obj_traverse_reachable(obj, data)) return 1;
1728 break;
1729
1730 // unreachable
1731 case T_CLASS:
1732 case T_MODULE:
1733 case T_ICLASS:
1734 default:
1735 rp(obj);
1736 rb_bug("unreachable");
1737 }
1738
1739 if (data->leave_func(obj) == traverse_stop) {
1740 return 1;
1741 }
1742 else {
1743 return 0;
1744 }
1745}
1746
1748 rb_obj_traverse_final_func final_func;
1749 int stopped;
1750};
1751
1752static int
1753obj_traverse_final_i(st_data_t key, st_data_t val, st_data_t arg)
1754{
1755 struct rb_obj_traverse_final_data *data = (void *)arg;
1756 if (data->final_func(key)) {
1757 data->stopped = 1;
1758 return ST_STOP;
1759 }
1760 return ST_CONTINUE;
1761}
1762
1763// 0: traverse all
1764// 1: stopped
1765static int
1766rb_obj_traverse(VALUE obj,
1767 rb_obj_traverse_enter_func enter_func,
1768 rb_obj_traverse_leave_func leave_func,
1769 rb_obj_traverse_final_func final_func)
1770{
1771 struct obj_traverse_data data = {
1772 .enter_func = enter_func,
1773 .leave_func = leave_func,
1774 .rec = NULL,
1775 };
1776
1777 if (obj_traverse_i(obj, &data)) return 1;
1778 if (final_func && data.rec) {
1779 struct rb_obj_traverse_final_data f = {final_func, 0};
1780 st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
1781 return f.stopped;
1782 }
1783 return 0;
1784}
1785
1786static int
1787allow_frozen_shareable_p(VALUE obj)
1788{
1789 if (RB_TYPE_P(obj, T_FILE)) {
1790 return false;
1791 }
1792 else if (!RB_TYPE_P(obj, T_DATA)) {
1793 return true;
1794 }
1795 else {
1796 const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
1797 if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE) {
1798 return true;
1799 }
1800 }
1801
1802 return false;
1803}
1804
1805static void
1806make_shareable_freeze(VALUE obj)
1807{
1808 VALUE klass = RBASIC_CLASS(obj);
1809 if (klass == rb_cString && BASIC_OP_UNREDEFINED_P(BOP_FREEZE, STRING_REDEFINED_OP_FLAG)) {
1810 rb_str_freeze(obj);
1811 }
1812 else if (klass == rb_cArray && BASIC_OP_UNREDEFINED_P(BOP_FREEZE, ARRAY_REDEFINED_OP_FLAG)) {
1813 rb_ary_freeze(obj);
1814 }
1815 else if (klass == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_FREEZE, HASH_REDEFINED_OP_FLAG)) {
1816 rb_hash_freeze(obj);
1817 }
1818 else {
1819 rb_funcall(obj, idFreeze, 0);
1820 }
1821}
1822
1823static enum obj_traverse_iterator_result
1824make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result)
1825{
1826 if (!RB_OBJ_FROZEN_RAW(obj)) {
1827 make_shareable_freeze(obj);
1828
1829 if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
1830 rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
1831 }
1832
1833 if (RB_OBJ_SHAREABLE_P(obj)) {
1834 return traverse_skip;
1835 }
1836 }
1837
1838 return result;
1839}
1840
1841static int obj_refer_only_shareables_p(VALUE obj);
1842
1843static enum obj_traverse_iterator_result
1844make_shareable_check_shareable(VALUE obj)
1845{
1846 VM_ASSERT(!SPECIAL_CONST_P(obj));
1847
1848 if (rb_ractor_shareable_p(obj)) {
1849 return traverse_skip;
1850 }
1851 else if (!allow_frozen_shareable_p(obj)) {
1852 if (!RB_TYPE_P(obj, T_DATA)) {
1853 rb_raise(rb_eRactorError,
1854 "can not make shareable object for %+"PRIsVALUE, obj);
1855 }
1856 else if (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
1857 if (obj_refer_only_shareables_p(obj)) {
1858 make_shareable_check_shareable_freeze(obj, traverse_skip);
1859 RB_OBJ_SET_SHAREABLE(obj);
1860 return traverse_skip;
1861 }
1862 else {
1863 rb_raise(rb_eRactorError,
1864 "can not make shareable object for %+"PRIsVALUE" because it refers unshareable objects", obj);
1865 }
1866 }
1867 else if (rb_obj_is_proc(obj)) {
1868 rb_proc_ractor_make_shareable(obj, Qundef);
1869 return traverse_cont;
1870 }
1871 else {
1872 rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1873 }
1874 }
1875
1876 switch (TYPE(obj)) {
1877 case T_IMEMO:
1878 return traverse_skip;
1879 case T_OBJECT:
1880 {
1881 // If a T_OBJECT is shared and has no free capacity, we can't safely store the object_id inline,
1882 // as it would require to move the object content into an external buffer.
1883 // This is only a problem for T_OBJECT, given other types have external fields and can do RCU.
1884 // To avoid this issue, we proactively create the object_id.
1885 shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
1886 attr_index_t capacity = RSHAPE_CAPACITY(shape_id);
1887 attr_index_t free_capacity = capacity - RSHAPE_LEN(shape_id);
1888 if (!rb_shape_has_object_id(shape_id) && capacity && !free_capacity) {
1889 rb_obj_id(obj);
1890 }
1891 }
1892 break;
1893 default:
1894 break;
1895 }
1896
1897 return make_shareable_check_shareable_freeze(obj, traverse_cont);
1898}
1899
1900static enum obj_traverse_iterator_result
1901mark_shareable(VALUE obj)
1902{
1903 if (RB_BUILTIN_TYPE(obj) == T_STRING) {
1904 rb_str_make_independent(obj);
1905 }
1906
1907 rb_obj_set_shareable_no_assert(obj);
1908 return traverse_cont;
1909}
1910
1911VALUE
1913{
1914 rb_obj_traverse(obj,
1915 make_shareable_check_shareable,
1916 null_leave, mark_shareable);
1917 return obj;
1918}
1919
1920static VALUE ractor_copy(VALUE obj); // defined below
1921
1922VALUE
1924{
1925 VALUE copy = ractor_copy(obj);
1926 return rb_ractor_make_shareable(copy);
1927}
1928
1929VALUE
1930rb_ractor_ensure_shareable(VALUE obj, VALUE name)
1931{
1932 if (!rb_ractor_shareable_p(obj)) {
1933 VALUE message = rb_sprintf("cannot assign unshareable object to %"PRIsVALUE,
1934 name);
1935 rb_exc_raise(rb_exc_new_str(rb_eRactorIsolationError, message));
1936 }
1937 return obj;
1938}
1939
1940void
1941rb_ractor_ensure_main_ractor(const char *msg)
1942{
1943 if (!rb_ractor_main_p()) {
1944 rb_raise(rb_eRactorIsolationError, "%s", msg);
1945 }
1946}
1947
1948static enum obj_traverse_iterator_result
1949shareable_p_enter(VALUE obj)
1950{
1951 if (RB_OBJ_SHAREABLE_P(obj)) {
1952 return traverse_skip;
1953 }
1954 else if (RB_TYPE_P(obj, T_CLASS) ||
1955 RB_TYPE_P(obj, T_MODULE) ||
1956 RB_TYPE_P(obj, T_ICLASS)) {
1957 // TODO: remove it
1958 mark_shareable(obj);
1959 return traverse_skip;
1960 }
1961 else if (RB_OBJ_FROZEN_RAW(obj) &&
1962 allow_frozen_shareable_p(obj)) {
1963 return traverse_cont;
1964 }
1965 else if (RB_OBJ_FROZEN_RAW(obj) &&
1966 RB_TYPE_P(obj, T_DATA) &&
1967 (RTYPEDDATA_TYPE(obj)->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC)) {
1968 // Similar to RUBY_TYPED_FROZEN_SHAREABLE, but the object is only
1969 // shareable if all reachable objects are already shareable (they
1970 // are not made shareable recursively).
1971 if (obj_refer_only_shareables_p(obj)) {
1972 mark_shareable(obj);
1973 return traverse_skip;
1974 }
1975 }
1976
1977 return traverse_stop; // fail
1978}
1979
1980bool
1981rb_ractor_shareable_p_continue(VALUE obj)
1982{
1983 if (rb_obj_traverse(obj,
1984 shareable_p_enter, null_leave,
1985 mark_shareable)) {
1986 return false;
1987 }
1988 else {
1989 return true;
1990 }
1991}
1992
1993static enum obj_traverse_iterator_result
1994null_leave(VALUE obj)
1995{
1996 return traverse_cont;
1997}
1998
1999
2001
2002// 2: stop search
2003// 1: skip child
2004// 0: continue
2005
2007static int obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data);
2008typedef enum obj_traverse_iterator_result (*rb_obj_traverse_replace_enter_func)(VALUE obj, struct obj_traverse_replace_data *data);
2009typedef enum obj_traverse_iterator_result (*rb_obj_traverse_replace_leave_func)(VALUE obj, struct obj_traverse_replace_data *data);
2010
2012 rb_obj_traverse_replace_enter_func enter_func;
2013 rb_obj_traverse_replace_leave_func leave_func;
2014
2015 /* old -> new map, a plain st_table: an OLD key may live in another Ractor's
2016 * objspace and must not become a GC edge here (marking a freed foreign key is a
2017 * UAF). Keys compare by address; replacements stay alive via rec_keepalive. */
2018 st_table *rec;
2019 VALUE rec_keepalive;
2020
2021 VALUE replacement;
2022 bool move;
2023};
2024
2026 bool stop;
2027 VALUE src;
2028 struct obj_traverse_replace_data *data;
2029};
2030
2031static int
2032obj_hash_traverse_replace_foreach_i(st_data_t key, st_data_t value, st_data_t argp, int error)
2033{
2034 return ST_REPLACE;
2035}
2036
2037static int
2038obj_hash_traverse_replace_i(st_data_t *key, st_data_t *val, st_data_t ptr, int exists)
2039{
2041 struct obj_traverse_replace_data *data = d->data;
2042
2043 if (obj_traverse_replace_i(*key, data)) {
2044 d->stop = true;
2045 return ST_STOP;
2046 }
2047 else if (*key != data->replacement) {
2048 VALUE v = *key = data->replacement;
2049 RB_OBJ_WRITTEN(d->src, Qundef, v);
2050 }
2051
2052 if (obj_traverse_replace_i(*val, data)) {
2053 d->stop = true;
2054 return ST_STOP;
2055 }
2056 else if (*val != data->replacement) {
2057 VALUE v = *val = data->replacement;
2058 RB_OBJ_WRITTEN(d->src, Qundef, v);
2059 }
2060
2061 return ST_CONTINUE;
2062}
2063
2064static int
2065obj_iv_hash_traverse_replace_foreach_i(st_data_t _key, st_data_t _val, st_data_t _data, int _x)
2066{
2067 return ST_REPLACE;
2068}
2069
2070static int
2071obj_iv_hash_traverse_replace_i(st_data_t * _key, st_data_t * val, st_data_t ptr, int exists)
2072{
2074 struct obj_traverse_replace_data *data = d->data;
2075
2076 if (obj_traverse_replace_i(*(VALUE *)val, data)) {
2077 d->stop = true;
2078 return ST_STOP;
2079 }
2080 else if (*(VALUE *)val != data->replacement) {
2081 VALUE v = *(VALUE *)val = data->replacement;
2082 RB_OBJ_WRITTEN(d->src, Qundef, v);
2083 }
2084
2085 return ST_CONTINUE;
2086}
2087
2088static struct st_table *
2089obj_traverse_replace_rec(struct obj_traverse_replace_data *data)
2090{
2091 if (UNLIKELY(!data->rec)) {
2092 data->rec = st_init_numtable();
2093 data->rec_keepalive = rb_ary_hidden_new(0);
2094 }
2095 return data->rec;
2096}
2097
2098static void
2099obj_refer_only_shareables_p_i(VALUE obj, void *ptr)
2100{
2101 int *pcnt = (int *)ptr;
2102
2103 if (!rb_ractor_shareable_p(obj)) {
2104 ++*pcnt;
2105 }
2106}
2107
2108static int
2109obj_refer_only_shareables_p(VALUE obj)
2110{
2111 int cnt = 0;
2112 RB_VM_LOCKING_NO_BARRIER() {
2113 rb_objspace_reachable_objects_from(obj, obj_refer_only_shareables_p_i, &cnt);
2114 }
2115 return cnt == 0;
2116}
2117
2118static int
2119obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data)
2120{
2121 st_data_t replacement;
2122
2123 if (RB_SPECIAL_CONST_P(obj)) {
2124 data->replacement = obj;
2125 return 0;
2126 }
2127
2128 /* Dedup before enter_func, so a revisited shared/cyclic node reuses its recorded
2129 * replacement; otherwise the copy path would build a wasteful temporary holding a
2130 * containment-breaking cross-objspace edge. */
2131 if (UNLIKELY(st_lookup(obj_traverse_replace_rec(data), (st_data_t)obj, &replacement))) {
2132 data->replacement = (VALUE)replacement;
2133 return 0;
2134 }
2135
2136 switch (data->enter_func(obj, data)) {
2137 case traverse_cont: break;
2138 case traverse_skip: return 0; // skip children
2139 case traverse_stop: return 1; // stop search
2140 }
2141
2142 replacement = (st_data_t)data->replacement;
2143 st_insert(obj_traverse_replace_rec(data), (st_data_t)obj, replacement);
2144 if (!RB_SPECIAL_CONST_P((VALUE)replacement)) {
2145 rb_ary_push(data->rec_keepalive, (VALUE)replacement);
2146 }
2147
2148 if (!data->move) {
2149 obj = replacement;
2150 }
2151
2152#define CHECK_AND_REPLACE(parent_obj, v) do { \
2153 VALUE _val = (v); \
2154 if (obj_traverse_replace_i(_val, data)) { return 1; } \
2155 else if (data->replacement != _val) { RB_OBJ_WRITE(parent_obj, &v, data->replacement); } \
2156} while (0)
2157
2158 if (UNLIKELY(rb_obj_gen_fields_p(obj))) {
2159 VALUE fields_obj = rb_obj_fields_no_ractor_check(obj);
2160
2161 if (UNLIKELY(rb_obj_shape_complex_p(obj))) {
2163 .stop = false,
2164 .data = data,
2165 .src = fields_obj,
2166 };
2167 rb_st_foreach_with_replace(
2168 rb_imemo_fields_complex_tbl(fields_obj),
2169 obj_iv_hash_traverse_replace_foreach_i,
2170 obj_iv_hash_traverse_replace_i,
2171 (st_data_t)&d
2172 );
2173 if (d.stop) return 1;
2174 }
2175 else {
2176 uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj));
2177 VALUE *fields = rb_imemo_fields_ptr(fields_obj);
2178 for (uint32_t i = 0; i < fields_count; i++) {
2179 CHECK_AND_REPLACE(fields_obj, fields[i]);
2180 }
2181 }
2182 }
2183
2184 switch (BUILTIN_TYPE(obj)) {
2185 // no child node
2186 case T_FLOAT:
2187 case T_BIGNUM:
2188 case T_REGEXP:
2189 case T_FILE:
2190 case T_SYMBOL:
2191 break;
2192 case T_STRING:
2193 rb_str_make_independent(obj);
2194 break;
2195
2196 case T_OBJECT:
2197 {
2198 VALUE fields_obj = ROBJECT_FIELDS_OBJ(obj);
2199 shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj);
2200 if (rb_shape_complex_p(shape_id)) {
2202 .stop = false,
2203 .data = data,
2204 .src = obj,
2205 };
2206 rb_st_foreach_with_replace(
2207 rb_imemo_fields_complex_tbl(fields_obj),
2208 obj_iv_hash_traverse_replace_foreach_i,
2209 obj_iv_hash_traverse_replace_i,
2210 (st_data_t)&d
2211 );
2212 if (d.stop) return 1;
2213 }
2214 else {
2215 attr_index_t len = RSHAPE_LEN(shape_id);
2216 VALUE *ptr = rb_imemo_fields_ptr(fields_obj);
2217
2218 for (attr_index_t i = 0; i < len; i++) {
2219 CHECK_AND_REPLACE(obj, ptr[i]);
2220 }
2221 }
2222 }
2223 break;
2224
2225 case T_ARRAY:
2226 {
2227 rb_ary_cancel_sharing(obj);
2228
2229 for (int i = 0; i < RARRAY_LENINT(obj); i++) {
2230 VALUE e = RARRAY_AREF(obj, i);
2231
2232 if (obj_traverse_replace_i(e, data)) {
2233 return 1;
2234 }
2235 else if (e != data->replacement) {
2236 RARRAY_ASET(obj, i, data->replacement);
2237 }
2238 }
2239 RB_GC_GUARD(obj);
2240 }
2241 break;
2242 case T_HASH:
2243 {
2245 .stop = false,
2246 .data = data,
2247 .src = obj,
2248 };
2249 rb_hash_stlike_foreach_with_replace(obj,
2250 obj_hash_traverse_replace_foreach_i,
2251 obj_hash_traverse_replace_i,
2252 (VALUE)&d);
2253 if (d.stop) return 1;
2254 // TODO: rehash here?
2255
2256 VALUE ifnone = RHASH_IFNONE(obj);
2257 if (obj_traverse_replace_i(ifnone, data)) {
2258 return 1;
2259 }
2260 else if (ifnone != data->replacement) {
2261 RHASH_SET_IFNONE(obj, data->replacement);
2262 }
2263 }
2264 break;
2265
2266 case T_STRUCT:
2267 {
2268 long len = RSTRUCT_LEN_RAW(obj);
2269 const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
2270
2271 for (long i=0; i<len; i++) {
2272 CHECK_AND_REPLACE(obj, ptr[i]);
2273 }
2274 }
2275 break;
2276
2277 case T_MATCH:
2278 CHECK_AND_REPLACE(obj, RMATCH(obj)->str);
2279 break;
2280
2281 case T_RATIONAL:
2282 CHECK_AND_REPLACE(obj, RRATIONAL(obj)->num);
2283 CHECK_AND_REPLACE(obj, RRATIONAL(obj)->den);
2284 break;
2285 case T_COMPLEX:
2286 CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->real);
2287 CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->imag);
2288 break;
2289
2290 case T_DATA:
2291 if (!data->move && obj_refer_only_shareables_p(obj)) {
2292 break;
2293 }
2294 else {
2295 rb_raise(rb_eRactorError, "can not %s %"PRIsVALUE" object.",
2296 data->move ? "move" : "copy", rb_class_of(obj));
2297 }
2298
2299 case T_IMEMO:
2300 // not supported yet
2301 return 1;
2302
2303 // unreachable
2304 case T_CLASS:
2305 case T_MODULE:
2306 case T_ICLASS:
2307 default:
2308 rp(obj);
2309 rb_bug("unreachable");
2310 }
2311
2312 data->replacement = (VALUE)replacement;
2313
2314 if (data->leave_func(obj, data) == traverse_stop) {
2315 return 1;
2316 }
2317 else {
2318 return 0;
2319 }
2320}
2321
2322// 0: traverse all
2323// 1: stopped
2324static VALUE
2325rb_obj_traverse_replace(VALUE obj,
2326 rb_obj_traverse_replace_enter_func enter_func,
2327 rb_obj_traverse_replace_leave_func leave_func,
2328 bool move)
2329{
2330 struct obj_traverse_replace_data data = {
2331 .enter_func = enter_func,
2332 .leave_func = leave_func,
2333 .rec = NULL,
2334 .rec_keepalive = Qfalse,
2335 .replacement = Qundef,
2336 .move = move,
2337 };
2338
2339 int stopped = obj_traverse_replace_i(obj, &data);
2340
2341 /* The enter and leave functions report failure with traverse_stop rather than by
2342 * raising, so this is the only place the table is freed. */
2343 if (data.rec) st_free_table(data.rec);
2344 RB_GC_GUARD(data.rec_keepalive);
2345
2346 if (stopped) {
2347 return Qundef;
2348 }
2349 else {
2350 return data.replacement;
2351 }
2352}
2353
2354/* Courier: serializes a Ractor message payload -- copied or moved -- into an xmalloc'd
2355 * structure that belongs to no objspace, so no sender GC can mark, sweep, compact or
2356 * race with it. A node array with id references handles sharing and cycles, and the
2357 * receiver rebuilds it in its own objspace in two passes. Copy and move differ only in
2358 * whether the source is read or taken apart: see courier_build.copy. */
2359
2360enum courier_node_kind {
2361 COURIER_KIND_REF, /* an immediate or a shareable object: carried by value */
2362 COURIER_KIND_BACKTRACE, /* an exception's backtrace: frames copied into an off-heap blob */
2363 COURIER_KIND_STRING,
2364 COURIER_KIND_ARRAY,
2365 COURIER_KIND_HASH,
2366 COURIER_KIND_OBJECT,
2367 COURIER_KIND_STRUCT,
2368 COURIER_KIND_MATCH,
2369 COURIER_KIND_IO,
2370};
2371
2373 enum courier_node_kind kind;
2374 bool frozen;
2375 /* The instance and generic ivars every non-REF node can have (a String or Array
2376 * can hold generic ivars too) */
2377 uint32_t niv;
2378 ID *iv_ids; /* owned by the courier */
2379 uint32_t *iv_vals; /* owned by the courier; node ids */
2380 union {
2381 VALUE ref;
2382 struct { char *ptr; long len, capa; int encidx; VALUE klass; } str; /* the courier owns ptr */
2383 struct { long len; uint32_t *elems; VALUE klass; } ary; /* the courier owns elems */
2384 struct { long size; uint32_t *kv; uint32_t ifnone_id; bool compare_by_id; bool proc_default; VALUE klass; } hash; /* owns kv (2*size) */
2385 struct { VALUE klass; } obj;
2386 struct { long len; uint32_t *elems; VALUE klass; } strct; /* owns elems */
2387 struct { uint32_t regexp_id, str_id; int num_regs; void *regs; VALUE klass; } match; /* owns regs */
2388 struct { void *blob; int size; } bt; /* the courier owns blob */
2389 struct {
2390 struct rb_io *fptr; /* carried by pointer (it owns the fd) */
2391 VALUE klass;
2392 /* The sender-side VALUE members of fptr travel as ordinary child nodes:
2393 * capture detaches them from fptr (see the T_FILE case) and rebuild writes
2394 * them back into the receiving shell with RB_OBJ_WRITE. */
2395 uint32_t pathv_id, ecopts_id, wc_pre_ecopts_id, wc_asciicompat_id, timeout_id;
2396 } io;
2397 } u;
2398};
2399
2400/* A child slot holds a node id, or -- with this bit set -- an index into c->refs.
2401 * The courier is in-process, so a shareable payload can travel as the VALUE itself
2402 * instead of costing a whole courier_node; the basket holding the courier marks c->refs. */
2403#define COURIER_ID_REF_BIT 0x80000000u
2404
2406 struct courier_node *nodes;
2407 uint32_t count;
2408 uint32_t capa;
2409 VALUE *refs; /* shareable payloads, embedded by value */
2410 uint32_t refs_count;
2411 uint32_t refs_capa;
2412 uint32_t root;
2413};
2414
2416 struct rb_ractor_courier *c;
2417 st_table *seen; /* src VALUE -> (node id + 1) */
2418 /* Copy mode: read the sources instead of taking them apart. No husk, no buffer
2419 * hand-over, no freeing of the source's internals. */
2420 bool copy;
2421};
2422
2423static uint32_t courier_capture(struct courier_build *b, VALUE obj);
2424
2425/* Off the hot path: the preflight sizes both arrays, so this only runs if its count
2426 * came out short. Swap a fresh array in rather than realloc -- the courier is a GC
2427 * root while it is being built, and a realloc leaves the old pointer live over a
2428 * window where it may already have been freed. */
2429NOINLINE(static void courier_grow_nodes(struct rb_ractor_courier *c));
2430NOINLINE(static void courier_grow_refs(struct rb_ractor_courier *c));
2431
2432static void
2433courier_grow_nodes(struct rb_ractor_courier *c)
2434{
2435 uint32_t capa = c->capa ? c->capa * 2 : 8;
2436 struct courier_node *nodes = ALLOC_N(struct courier_node, capa);
2437 if (c->count > 0) MEMCPY(nodes, c->nodes, struct courier_node, c->count);
2438 struct courier_node *old_nodes = c->nodes;
2439 c->nodes = nodes;
2440 c->capa = capa;
2441 ruby_xfree(old_nodes);
2442}
2443
2444static void
2445courier_grow_refs(struct rb_ractor_courier *c)
2446{
2447 uint32_t capa = c->refs_capa ? c->refs_capa * 2 : 8;
2448 VALUE *refs = ALLOC_N(VALUE, capa);
2449 if (c->refs_count > 0) MEMCPY(refs, c->refs, VALUE, c->refs_count);
2450 VALUE *old_refs = c->refs;
2451 c->refs = refs;
2452 c->refs_capa = capa;
2453 ruby_xfree(old_refs);
2454}
2455
2456static uint32_t
2457courier_alloc_node(struct rb_ractor_courier *c)
2458{
2459 if (RB_UNLIKELY(c->count == c->capa)) courier_grow_nodes(c);
2460 /* Fill the slot with a harmless REF/Qnil and bump the count only after, the way
2461 * courier_alloc_ref does: the courier is a GC root while it is being built, and
2462 * the mark walks nodes[0, count). A captured node overwrites this later. */
2463 struct courier_node *n = &c->nodes[c->count];
2464 n->kind = COURIER_KIND_REF;
2465 n->frozen = false;
2466 n->niv = 0;
2467 n->iv_ids = NULL;
2468 n->iv_vals = NULL;
2469 n->u.ref = Qnil;
2470 return c->count++;
2471}
2472
2473/* Size the arrays from the preflight's count, so capture never grows them. A count
2474 * that turns out short is not a problem: the growth path below still works. */
2475static void
2476courier_reserve(struct rb_ractor_courier *c, uint32_t nodes, uint32_t refs)
2477{
2478 if (nodes > 0) {
2479 c->nodes = ALLOC_N(struct courier_node, nodes);
2480 c->capa = nodes;
2481 }
2482 if (refs > 0) {
2483 c->refs = ALLOC_N(VALUE, refs);
2484 c->refs_capa = refs;
2485 }
2486}
2487
2488/* Embed a shareable payload by value and return its tagged child id. No dedup: a REF
2489 * is the same word however often it appears, and an array of immediates would otherwise
2490 * pay a lookup and an insert per element. */
2491static uint32_t
2492courier_alloc_ref(struct rb_ractor_courier *c, VALUE v)
2493{
2494 /* The count is bumped only after the slot holds a real VALUE: the courier is a GC
2495 * root while it is being built and must never be walkable half-written. */
2496 if (RB_UNLIKELY(c->refs_count == c->refs_capa)) courier_grow_refs(c);
2497 c->refs[c->refs_count] = v;
2498 return COURIER_ID_REF_BIT | c->refs_count++;
2499}
2500
2501/* Resolve a child slot to the object it names. */
2502static VALUE
2503courier_child(const struct rb_ractor_courier *c, VALUE shells, uint32_t id)
2504{
2505 if (id & COURIER_ID_REF_BIT) return c->refs[id & ~COURIER_ID_REF_BIT];
2506 return RARRAY_AREF(shells, id);
2507}
2508
2509/* Turn a moved source into a valid RactorMovedObject without passing through flags==0,
2510 * so a concurrent foreign marker always sees either the original object or the shell. */
2511static void
2512move_neutralize_source(VALUE obj)
2513{
2514 /* The shell stays in the original slot: keep the capacity bits, give it a frozen
2515 * field-less ROBJECT shape (read before the flags are overwritten). The old body is
2516 * then never read as ivars and compaction's slot-size check still holds. */
2517 shape_id_t shape_id = (RBASIC_SHAPE_ID(obj) & SHAPE_ID_CAPACITY_MASK) |
2518 ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_ROBJECT | SHAPE_ID_FL_FROZEN;
2519
2520 /* A non-T_OBJECT host (a String with ivars, say) must drop its generic_fields
2521 * entry: obj stops being a host below and its fields_obj is collected, so a stale
2522 * entry would let the global GC walk a freed value. */
2524
2525 /* A copy-on-write sharer reads its payload straight out of an embedded root's slot
2526 * (String#dup of a frozen string, Array#[] of a frozen array), and it outlives the
2527 * move, so that body has to survive as it is. */
2528 bool wipe_body = true;
2529 switch (BUILTIN_TYPE(obj)) {
2530 case T_STRING:
2531 wipe_body = !rb_str_embedded_shared_root_p(obj);
2532 break;
2533 case T_ARRAY:
2534 wipe_body = !rb_ary_embedded_shared_root_p(obj);
2535 break;
2536 default:
2537 break;
2538 }
2539
2540 VALUE flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_PROMOTED);
2541 /* Read the slot size before the header is rewritten. */
2542 size_t slot_size = rb_gc_obj_slot_size(obj);
2543 RBASIC_SET_CLASS_RAW(obj, rb_cRactorMovedObject);
2544 RBASIC(obj)->flags = flags;
2545 RBASIC_SET_FULL_SHAPE_ID(obj, shape_id);
2546
2547 /* Wipe the old body. The shell has no fields, so nothing reads it as ivars, but
2548 * C code holding the object from before the move still reads it with its old type
2549 * (a running Array iteration, the RMatch capa of a $~ entry): a zeroed body makes
2550 * those reads see an empty object instead of stale internals. */
2551 if (wipe_body) {
2552 MEMZERO((char *)obj + sizeof(struct RBasic), char, slot_size - sizeof(struct RBasic));
2553 }
2554}
2555
2557 struct courier_build *b;
2558 uint32_t *kv;
2559 long i;
2560};
2561
2562static int
2563courier_capture_hash_i(st_data_t key, st_data_t val, st_data_t arg)
2564{
2565 struct courier_hash_ctx *hc = (struct courier_hash_ctx *)arg;
2566 uint32_t kid = courier_capture(hc->b, (VALUE)key);
2567 uint32_t vid = courier_capture(hc->b, (VALUE)val);
2568 hc->kv[hc->i++] = kid;
2569 hc->kv[hc->i++] = vid;
2570 return ST_CONTINUE;
2571}
2572
2574 struct courier_build *b;
2575 ID *ids;
2576 uint32_t *vals;
2577 long n;
2578 long capa;
2579};
2580
2581static int
2582courier_capture_ivar_i(ID name, VALUE val, st_data_t arg)
2583{
2584 struct courier_obj_ctx *oc = (struct courier_obj_ctx *)arg;
2585 if (oc->n == oc->capa) {
2586 oc->capa = oc->capa ? oc->capa * 2 : 4;
2587 REALLOC_N(oc->ids, ID, oc->capa);
2588 REALLOC_N(oc->vals, uint32_t, oc->capa);
2589 }
2590 uint32_t vid = courier_capture(oc->b, val);
2591 oc->ids[oc->n] = name;
2592 oc->vals[oc->n] = vid;
2593 oc->n++;
2594 return ST_CONTINUE;
2595}
2596
2597/* Capture obj's instance and generic ivars as node ids, recursing into the values.
2598 * Handles both a T_OBJECT's inline ivars and the generic ivars of a String, Array and
2599 * so on. */
2600static void
2601courier_capture_ivars(struct courier_build *b, VALUE obj, uint32_t id)
2602{
2603 struct courier_obj_ctx oc = { b, NULL, NULL, 0, 0 };
2604 rb_ivar_foreach_buffered(obj, courier_capture_ivar_i, (st_data_t)&oc);
2605 b->c->nodes[id].niv = (uint32_t)oc.n;
2606 b->c->nodes[id].iv_ids = oc.ids;
2607 b->c->nodes[id].iv_vals = oc.vals;
2608}
2609
2610/* Capture obj into the courier, recurse into its children, return its node id. The id
2611 * is registered before recursing (a cycle back resolves to the same node); node fields
2612 * are written after (recursion can realloc c->nodes); a move neutralizes the source
2613 * exactly once after the switch. */
2614static uint32_t
2615courier_capture(struct courier_build *b, VALUE obj)
2616{
2617 /* An immediate is never in seen (only captured objects are inserted), so it can
2618 * skip the lookup entirely: that is the whole cost of an array of numbers. */
2619 if (RB_SPECIAL_CONST_P(obj)) {
2620 return courier_alloc_ref(b->c, obj);
2621 }
2622
2623 /* Seen first, and only then shareable: move husks each source as it goes, and a
2624 * husk is a frozen field-less object, which rb_ractor_shareable_p answers true for.
2625 * Testing shareable first would embed the husk instead of resolving the second
2626 * occurrence to the node the first one built. */
2627 st_data_t existing;
2628 if (st_lookup(b->seen, (st_data_t)obj, &existing)) {
2629 return (uint32_t)existing - 1;
2630 }
2631
2632 if (rb_ractor_shareable_p(obj)) {
2633 return courier_alloc_ref(b->c, obj);
2634 }
2635
2636 uint32_t id = courier_alloc_node(b->c);
2637 st_insert(b->seen, (st_data_t)obj, (st_data_t)(uintptr_t)(id + 1));
2638
2639 /* Reject an unmovable object before anything is mutated. */
2640 if (BUILTIN_TYPE(obj) == T_FILE && RFILE(obj)->fptr == NULL) {
2641 rb_raise(rb_eRactorError, "can not move an uninitialized IO");
2642 }
2643
2644 bool frozen = OBJ_FROZEN(obj);
2645 b->c->nodes[id].frozen = frozen;
2646 courier_capture_ivars(b, obj, id); /* shared: instance and generic ivars */
2647
2648 switch (BUILTIN_TYPE(obj)) {
2649 case T_STRING: {
2650 /* Give the source its own buffer (drop sharing, copy a static STR_NOFREE one).
2651 * Safe even when frozen: it changes ownership, not content. Afterwards a string
2652 * is embedded, owns a private heap buffer, or is a shared ROOT (a no-op). */
2653 if (!b->copy) rb_str_make_independent(obj);
2654 long len = RSTRING_LEN(obj);
2655 int encidx = ENCODING_GET(obj);
2656 /* The receiver adopts this buffer as a String body, which is freed by size:
2657 * capa has to describe the allocation exactly (capa + terminator bytes). */
2658 const int termlen = rb_enc_mbminlen(rb_enc_from_index(encidx));
2659 char *ptr;
2660 long capa;
2661 if (!b->copy && !STR_EMBED_P(obj) && rb_str_reembeddable_p(obj)) {
2662 /* Owns a private heap buffer: carry the pointer over (zero-copy) and leave
2663 * the source as a shell that does not free it. */
2664 ptr = RSTRING(obj)->as.heap.ptr;
2665 capa = RSTRING(obj)->as.heap.aux.capa;
2666 }
2667 else {
2668 /* Embedded or a shared root: copy the bytes into a courier-owned buffer.
2669 * Taking a root's buffer would dangle its copy-on-write children, so leave
2670 * it (the same reason T_ARRAY excludes ARY_SHARED_ROOT_P below). */
2671 ptr = ALLOC_N(char, len + termlen);
2672 if (len) memcpy(ptr, RSTRING_PTR(obj), len);
2673 memset(ptr + len, 0, termlen);
2674 capa = len;
2675 }
2676 b->c->nodes[id].kind = COURIER_KIND_STRING;
2677 b->c->nodes[id].u.str.klass = RBASIC_CLASS(obj);
2678 b->c->nodes[id].u.str.ptr = ptr;
2679 b->c->nodes[id].u.str.len = len;
2680 b->c->nodes[id].u.str.capa = capa;
2681 b->c->nodes[id].u.str.encidx = encidx;
2682 break;
2683 }
2684
2685 case T_ARRAY: {
2686 long len = RARRAY_LEN(obj);
2687 uint32_t *elems = len ? ALLOC_N(uint32_t, len) : NULL;
2688 for (long i = 0; i < len; i++) {
2689 elems[i] = courier_capture(b, RARRAY_AREF(obj, i));
2690 }
2691 b->c->nodes[id].kind = COURIER_KIND_ARRAY;
2692 b->c->nodes[id].u.ary.klass = RBASIC_CLASS(obj);
2693 b->c->nodes[id].u.ary.len = len;
2694 b->c->nodes[id].u.ary.elems = elems;
2695 /* Free the source's heap buffer now that the children were read, but only when it
2696 * is private: a sharer's belongs to its root, a root's to its sharers -- and a
2697 * frozen array is a root without carrying the flag. */
2698 if (!b->copy && !ARY_EMBED_P(obj) && !ARY_SHARED_P(obj) && !ARY_SHARED_ROOT_P(obj) && !OBJ_FROZEN(obj)) {
2699 ruby_xfree((void *)RARRAY_CONST_PTR(obj));
2700 }
2701 break;
2702 }
2703
2704 case T_HASH: {
2705 uint32_t ifnone_id = courier_capture(b, RHASH_IFNONE(obj));
2706 long size = RHASH_SIZE(obj);
2707 uint32_t *kv = size ? ALLOC_N(uint32_t, size * 2) : NULL;
2708 struct courier_hash_ctx hc = { b, kv, 0 };
2709 rb_hash_stlike_foreach(obj, courier_capture_hash_i, (st_data_t)&hc);
2710 b->c->nodes[id].kind = COURIER_KIND_HASH;
2711 b->c->nodes[id].u.hash.klass = RBASIC_CLASS(obj);
2712 b->c->nodes[id].u.hash.size = size;
2713 b->c->nodes[id].u.hash.kv = kv;
2714 b->c->nodes[id].u.hash.ifnone_id = ifnone_id;
2715 b->c->nodes[id].u.hash.compare_by_id = RTEST(rb_hash_compare_by_id_p(obj));
2716 b->c->nodes[id].u.hash.proc_default = FL_TEST_RAW(obj, RHASH_PROC_DEFAULT) != 0;
2717 /* Free the source's st-table internals (an ar table lives in the slot) */
2718 if (!b->copy) rb_hash_free(obj);
2719 break;
2720 }
2721
2722 case T_OBJECT:
2723 b->c->nodes[id].kind = COURIER_KIND_OBJECT;
2724 /* Keep the real class: even a singleton class is shareable, so a cross-objspace
2725 * reference is safe. rebuild re-attaches it after allocating with a
2726 * non-singleton class. */
2727 b->c->nodes[id].u.obj.klass = RBASIC_CLASS(obj);
2728 break;
2729
2730 case T_STRUCT: {
2731 long len = RSTRUCT_LEN(obj);
2732 uint32_t *elems = len ? ALLOC_N(uint32_t, len) : NULL;
2733 for (long i = 0; i < len; i++) {
2734 elems[i] = courier_capture(b, RSTRUCT_GET(obj, (int)i));
2735 }
2736 b->c->nodes[id].kind = COURIER_KIND_STRUCT;
2737 b->c->nodes[id].u.strct.len = len;
2738 b->c->nodes[id].u.strct.elems = elems;
2739 b->c->nodes[id].u.strct.klass = RBASIC_CLASS(obj);
2740 /* Free the source's private heap buffer (an embedded struct has none) */
2741 if (!b->copy && RSTRUCT_EMBED_LEN(obj) == 0) {
2742 ruby_xfree((void *)RSTRUCT_CONST_PTR(obj));
2743 }
2744 break;
2745 }
2746
2747 case T_MATCH: {
2748 /* The regexp and the matched string travel as ordinary children; re.c dumps the
2749 * registers (freeing the source's onig and char_offset). */
2750 VALUE re, st;
2751 int nregs;
2752 void *regs = rb_match_blob_dump(obj, &re, &st, &nregs, !b->copy);
2753 uint32_t rid = courier_capture(b, re);
2754 uint32_t sid = courier_capture(b, st);
2755 b->c->nodes[id].kind = COURIER_KIND_MATCH;
2756 b->c->nodes[id].u.match.regexp_id = rid;
2757 b->c->nodes[id].u.match.str_id = sid;
2758 b->c->nodes[id].u.match.num_regs = nregs;
2759 b->c->nodes[id].u.match.regs = regs;
2760 b->c->nodes[id].u.match.klass = RBASIC_CLASS(obj);
2761 break;
2762 }
2763
2764 case T_FILE:
2765 {
2766 VM_ASSERT(!b->copy); /* copy_courier_supported_p rejects it */
2767 /* Carry the whole fptr (fd included) by pointer; the source shell does not
2768 * close it. fptr's VALUE members lose their root once the source is T_MOVED,
2769 * so capture them as ordinary child nodes, detached; rebuild writes them back. */
2770 struct rb_io *fptr = RFILE(obj)->fptr;
2771 VM_ASSERT(!RTEST(fptr->tied_io_for_writing) && !RTEST(fptr->wakeup_mutex));
2772 uint32_t pathv_id = courier_capture(b, fptr->pathv);
2773 uint32_t ecopts_id = courier_capture(b, fptr->encs.ecopts);
2774 uint32_t wc_pre_id = courier_capture(b, fptr->writeconv_pre_ecopts);
2775 uint32_t wc_ac_id = courier_capture(b, fptr->writeconv_asciicompat);
2776 uint32_t timeout_id = courier_capture(b, fptr->timeout);
2777 fptr->self = Qnil; /* it points at the moved-from T_MOVED; attach rebuilds it */
2778 fptr->pathv = Qnil;
2779 fptr->encs.ecopts = Qnil;
2780 fptr->writeconv_pre_ecopts = Qnil;
2782 fptr->timeout = Qnil;
2783 fptr->write_lock = Qnil;
2784 fptr->wakeup_mutex = Qnil;
2785 fptr->tied_io_for_writing = 0; /* io.c tests it as a C boolean, so 0 rather than Qnil */
2786 b->c->nodes[id].kind = COURIER_KIND_IO;
2787 b->c->nodes[id].u.io.fptr = fptr;
2788 b->c->nodes[id].u.io.klass = RBASIC_CLASS(obj);
2789 b->c->nodes[id].u.io.pathv_id = pathv_id;
2790 b->c->nodes[id].u.io.ecopts_id = ecopts_id;
2791 b->c->nodes[id].u.io.wc_pre_ecopts_id = wc_pre_id;
2792 b->c->nodes[id].u.io.wc_asciicompat_id = wc_ac_id;
2793 b->c->nodes[id].u.io.timeout_id = timeout_id;
2794 break;
2795 }
2796
2797 case T_DATA:
2798 /* Only an exception's backtrace, and only for a copy: move still refuses every
2799 * T_DATA (its source would have to be taken apart). */
2800 if (b->copy && rb_backtrace_p(obj)) {
2801 int size;
2802 void *blob = rb_backtrace_blob_dump(obj, &size);
2803 b->c->nodes[id].kind = COURIER_KIND_BACKTRACE;
2804 b->c->nodes[id].u.bt.blob = blob;
2805 b->c->nodes[id].u.bt.size = size;
2806 break;
2807 }
2808 /* fall through */
2809 default:
2810 rb_raise(rb_eRactorError, "can not move a %"PRIsVALUE" object",
2812 }
2813
2814 if (!b->copy) move_neutralize_source(obj);
2815 return id;
2816}
2817
2818/* Like the copy walk, this also sizes the courier: see copy_support_ctx. */
2820 st_table *seen;
2821 uint32_t nodes, refs;
2822};
2823
2824static void move_preflight(VALUE obj, struct move_preflight_ctx *ctx);
2825
2826static int
2827move_preflight_ivar_i(ID name, VALUE val, st_data_t arg)
2828{
2829 move_preflight(val, (struct move_preflight_ctx *)arg);
2830 return ST_CONTINUE;
2831}
2832
2833static int
2834move_preflight_hash_i(st_data_t key, st_data_t val, st_data_t arg)
2835{
2836 move_preflight((VALUE)key, (struct move_preflight_ctx *)arg);
2837 move_preflight((VALUE)val, (struct move_preflight_ctx *)arg);
2838 return ST_CONTINUE;
2839}
2840
2841/* A read-only pre-walk of courier_capture's decision tree. Capture turns sources into
2842 * T_MOVED as it goes, so an unmovable object midway would leave the graph broken beyond
2843 * repair; every "can not move" error is raised here, before anything is mutated. */
2844static void
2845move_preflight(VALUE obj, struct move_preflight_ctx *ctx)
2846{
2847 st_table *const seen = ctx->seen;
2848
2849 if (RB_SPECIAL_CONST_P(obj) || rb_ractor_shareable_p(obj)) {
2850 ctx->refs++;
2851 return;
2852 }
2853 if (st_lookup(seen, (st_data_t)obj, NULL)) return; /* cycle */
2854 st_insert(seen, (st_data_t)obj, 0);
2855 ctx->nodes++;
2856
2857 switch (BUILTIN_TYPE(obj)) {
2858 case T_STRING:
2859 case T_OBJECT:
2860 break; /* children are ivars only (below) */
2861 case T_MATCH: {
2862 struct RMatch *rm = RMATCH(obj);
2863 move_preflight(rm->regexp, ctx);
2864 move_preflight(rm->str, ctx);
2865 break;
2866 }
2867 case T_ARRAY:
2868 for (long i = 0; i < RARRAY_LEN(obj); i++) {
2869 move_preflight(RARRAY_AREF(obj, i), ctx);
2870 }
2871 break;
2872 case T_HASH:
2873 rb_hash_stlike_foreach(obj, move_preflight_hash_i, (st_data_t)ctx);
2874 move_preflight(RHASH_IFNONE(obj), ctx);
2875 break;
2876 case T_STRUCT:
2877 for (long i = 0; i < RSTRUCT_LEN(obj); i++) {
2878 move_preflight(RSTRUCT_GET(obj, (int)i), ctx);
2879 }
2880 break;
2881 case T_FILE: {
2882 struct rb_io *fptr = RFILE(obj)->fptr;
2883 if (fptr == NULL) {
2884 rb_raise(rb_eRactorError, "can not move an uninitialized IO");
2885 }
2886 if (RTEST(fptr->tied_io_for_writing)) {
2887 /* A popen("r+") pair: moving one side would dangle the tied writer on the
2888 * sender. */
2889 rb_raise(rb_eRactorError, "can not move an IO tied to a writer IO");
2890 }
2891 if (RTEST(fptr->wakeup_mutex)) {
2892 /* A close is in progress: a thread is blocked on this IO. */
2893 rb_raise(rb_eRactorError, "can not move an IO that is being closed");
2894 }
2895 move_preflight(fptr->pathv, ctx);
2896 move_preflight(fptr->encs.ecopts, ctx);
2897 move_preflight(fptr->writeconv_pre_ecopts, ctx);
2898 move_preflight(fptr->writeconv_asciicompat, ctx);
2899 move_preflight(fptr->timeout, ctx);
2900 break;
2901 }
2902 default:
2903 rb_raise(rb_eRactorError, "can not move a %"PRIsVALUE" object",
2905 }
2906
2907 rb_ivar_foreach(obj, move_preflight_ivar_i, (st_data_t)ctx);
2908}
2909
2910/* The walk also sizes the courier: one node per distinct unshareable object, one ref
2911 * per occurrence of a shareable one -- exactly what courier_capture allocates, so the
2912 * arrays never have to grow while the graph is being captured. */
2914 st_table *seen;
2915 uint32_t nodes, refs;
2916 bool ok;
2917};
2918
2919static bool copy_courier_supported_p(VALUE obj, struct copy_support_ctx *ctx);
2920
2921static int
2922copy_support_val_i(st_data_t val, st_data_t arg)
2923{
2924 struct copy_support_ctx *ctx = (struct copy_support_ctx *)arg;
2925 if (!copy_courier_supported_p((VALUE)val, ctx)) {
2926 ctx->ok = false;
2927 return ST_STOP;
2928 }
2929 return ST_CONTINUE;
2930}
2931
2932static int
2933copy_support_ivar_i(ID name, VALUE val, st_data_t arg)
2934{
2935 return copy_support_val_i((st_data_t)val, arg);
2936}
2937
2938static int
2939copy_support_hash_i(st_data_t key, st_data_t val, st_data_t arg)
2940{
2941 if (copy_support_val_i(key, arg) == ST_STOP) return ST_STOP;
2942 return copy_support_val_i(val, arg);
2943}
2944
2945/* Read-only walk: can the copy courier carry obj's whole graph? Everything it says no
2946 * to (MatchData, IO, any other T_DATA, a singleton class) stays on the older on-heap
2947 * snapshot path, which keeps handling or rejecting it exactly as before. */
2948static bool
2949copy_courier_supported_p(VALUE obj, struct copy_support_ctx *ctx)
2950{
2951 st_table *const seen = ctx->seen;
2952
2953 if (RB_SPECIAL_CONST_P(obj) || rb_ractor_shareable_p(obj)) {
2954 ctx->refs++;
2955 return true;
2956 }
2957 if (st_lookup(seen, (st_data_t)obj, NULL)) return true; /* cycle */
2958 st_insert(seen, (st_data_t)obj, 0);
2959 ctx->nodes++;
2960
2961 /* A singleton class is a send error today (the native copier refuses it and Marshal
2962 * then raises); the courier would happily carry it, so keep it off this path. */
2963 VALUE klass = RBASIC_CLASS(obj);
2964 if (klass == 0 || FL_TEST_RAW(klass, FL_SINGLETON)) return false;
2965
2966 switch (BUILTIN_TYPE(obj)) {
2967 case T_STRING:
2968 case T_OBJECT:
2969 break; /* children are ivars only (below) */
2970 case T_MATCH: {
2971 struct RMatch *rm = RMATCH(obj);
2972 if (!copy_courier_supported_p(rm->regexp, ctx)) return false;
2973 if (!copy_courier_supported_p(rm->str, ctx)) return false;
2974 break;
2975 }
2976 case T_DATA:
2977 /* An exception's backtrace is the one T_DATA the courier carries. */
2978 if (!rb_backtrace_p(obj)) return false;
2979 break;
2980 case T_ARRAY:
2981 for (long i = 0; i < RARRAY_LEN(obj); i++) {
2982 if (!copy_courier_supported_p(RARRAY_AREF(obj, i), ctx)) return false;
2983 }
2984 break;
2985 case T_HASH:
2986 rb_hash_stlike_foreach(obj, copy_support_hash_i, (st_data_t)ctx);
2987 if (!ctx->ok) return false;
2988 if (!copy_courier_supported_p(RHASH_IFNONE(obj), ctx)) return false;
2989 break;
2990 case T_STRUCT:
2991 for (long i = 0; i < RSTRUCT_LEN(obj); i++) {
2992 if (!copy_courier_supported_p(RSTRUCT_GET(obj, (int)i), ctx)) return false;
2993 }
2994 break;
2995 default:
2996 return false;
2997 }
2998
2999 rb_ivar_foreach(obj, copy_support_ivar_i, (st_data_t)ctx);
3000 return ctx->ok;
3001}
3002
3003/* Build a courier holding a copy of obj's graph, leaving the sources untouched.
3004 * Returns NULL when the graph has a type only the on-heap snapshot path handles. */
3005struct rb_ractor_courier *
3006rb_ractor_courier_build_copy(VALUE obj, struct rb_ractor_courier **slot)
3007{
3008 struct copy_support_ctx scan = { st_init_numtable(), 0, 0, true };
3009 {
3010 bool ok = copy_courier_supported_p(obj, &scan);
3011 st_free_table(scan.seen);
3012 if (!ok) return NULL;
3013 }
3014
3015 struct rb_ractor_courier *c = ZALLOC(struct rb_ractor_courier);
3016 courier_reserve(c, scan.nodes, scan.refs);
3017 struct courier_build b = { c, st_init_numtable(), true };
3018
3019 /* Publish it into the caller's basket before capturing anything: from here the
3020 * shareable payloads it collects are rooted by the basket's holder. */
3021 *slot = c;
3022
3023 enum ruby_tag_type state;
3024 rb_execution_context_t *ec = GET_EC();
3025 EC_PUSH_TAG(ec);
3026 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
3027 c->root = courier_capture(&b, obj);
3028 }
3029 EC_POP_TAG();
3030 st_free_table(b.seen);
3031 /* Published above, so the basket owns it even half-built: it frees it. */
3032 if (state != TAG_NONE) EC_JUMP_TAG(ec, state);
3033 return c;
3034}
3035
3036/* Build a courier from obj and turn every captured source into a RactorMovedObject
3037 * (move semantics). Returns the xmalloc'd courier. */
3038struct rb_ractor_courier *
3039rb_ractor_courier_build_move(VALUE obj, struct rb_ractor_courier **slot)
3040{
3041 /* Two phases, preflight then commit, so an unmovable object is raised from the
3042 * read-only walk while the graph is still intact. */
3043 struct move_preflight_ctx scan = { st_init_numtable(), 0, 0 };
3044 {
3045 enum ruby_tag_type state;
3046 rb_execution_context_t *ec = GET_EC();
3047 EC_PUSH_TAG(ec);
3048 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
3049 move_preflight(obj, &scan);
3050 }
3051 EC_POP_TAG();
3052 st_free_table(scan.seen);
3053 if (state != TAG_NONE) EC_JUMP_TAG(ec, state);
3054 }
3055
3056 struct rb_ractor_courier *c = ZALLOC(struct rb_ractor_courier);
3057 courier_reserve(c, scan.nodes, scan.refs);
3058 struct courier_build b = { c, st_init_numtable(), false };
3059
3060 /* Publish it into the caller's basket before the sources become T_MOVED: from here
3061 * the basket's holder roots what the courier carries, and partial nodes are
3062 * initialized mark-safe. */
3063 *slot = c;
3064
3065 enum ruby_tag_type state;
3066 rb_execution_context_t *ec = GET_EC();
3067 EC_PUSH_TAG(ec);
3068 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
3069 c->root = courier_capture(&b, obj);
3070 }
3071 EC_POP_TAG();
3072 st_free_table(b.seen);
3073 if (state != TAG_NONE) {
3074 /* courier_capture raised (an unmovable type, an interrupt). The courier belongs
3075 * to the basket from the publish above, so leave it there and re-raise: the
3076 * basket frees it, once, on the way out. */
3077 EC_JUMP_TAG(ec, state);
3078 }
3079 return c;
3080}
3081
3082/* Shells are created with the base/real class, so re-attach the original subclass or
3083 * singleton class (classes are shareable; the reference is safe). A singleton's
3084 * attached object still points at the sender's source: re-attach it to the shell. */
3085static void
3086courier_apply_klass(VALUE shell, VALUE klass)
3087{
3088 if (klass != RBASIC_CLASS(shell)) {
3089 RBASIC_SET_CLASS(shell, klass);
3090 }
3091 if (RB_UNLIKELY(FL_TEST_RAW(klass, FL_SINGLETON))) {
3092 rb_singleton_class_attached(klass, shell);
3093 }
3094}
3095
3096/* Rebuild the courier's graph in the current Ractor's objspace and return its root.
3097 * Two passes (allocate shells, then fill) break reference cycles. */
3098VALUE
3099rb_ractor_courier_materialize(struct rb_ractor_courier *c)
3100{
3101 /* A hidden Array roots every shell, keeping them alive while the allocations that
3102 * build the rest of the graph (which can start this Ractor's GC) run. */
3103 VALUE shells = rb_ary_hidden_new(c->count);
3104
3105 for (uint32_t i = 0; i < c->count; i++) {
3106 struct courier_node *n = &c->nodes[i];
3107 VALUE shell;
3108 switch (n->kind) {
3109 case COURIER_KIND_REF:
3110 shell = n->u.ref;
3111 break;
3112 case COURIER_KIND_STRING:
3113 /* Hand the courier's buffer to the String instead of copying it again: the
3114 * bytes were already copied (or taken from the source) when the node was
3115 * built. */
3116 shell = rb_str_new_owned(n->u.str.ptr, n->u.str.len, n->u.str.capa, n->u.str.encidx);
3117 n->u.str.ptr = NULL; /* consumed: the new String owns it now */
3118 courier_apply_klass(shell, n->u.str.klass);
3119 break;
3120 case COURIER_KIND_ARRAY:
3121 shell = rb_ary_new_capa(n->u.ary.len);
3122 courier_apply_klass(shell, n->u.ary.klass);
3123 break;
3124 case COURIER_KIND_HASH:
3125 shell = n->u.hash.compare_by_id ? rb_ident_hash_new() : rb_hash_new();
3126 courier_apply_klass(shell, n->u.hash.klass);
3127 break;
3128 case COURIER_KIND_OBJECT:
3129 /* A singleton class cannot allocate, so make an instance of the real class
3130 * and re-attach it afterwards */
3131 shell = rb_obj_alloc(rb_class_real(n->u.obj.klass));
3132 courier_apply_klass(shell, n->u.obj.klass);
3133 break;
3134 case COURIER_KIND_STRUCT:
3135 shell = rb_obj_alloc(rb_class_real(n->u.strct.klass));
3136 courier_apply_klass(shell, n->u.strct.klass);
3137 break;
3138 case COURIER_KIND_MATCH:
3139 shell = rb_match_blob_alloc(rb_class_real(n->u.match.klass), n->u.match.num_regs);
3140 courier_apply_klass(shell, n->u.match.klass);
3141 break;
3142 case COURIER_KIND_BACKTRACE:
3143 shell = rb_backtrace_blob_load(n->u.bt.blob, n->u.bt.size);
3144 break;
3145 case COURIER_KIND_IO:
3146 shell = rb_obj_alloc(rb_class_real(n->u.io.klass));
3147 courier_apply_klass(shell, n->u.io.klass);
3148 RFILE(shell)->fptr = n->u.io.fptr;
3149 n->u.io.fptr->self = shell;
3150 n->u.io.fptr = NULL; /* consumed: the new IO owns it now */
3151 break;
3152 default:
3153 rb_bug("rb_ractor_courier_materialize: bad node kind");
3154 }
3155 rb_ary_push(shells, shell);
3156 }
3157
3158 for (uint32_t i = 0; i < c->count; i++) {
3159 struct courier_node *n = &c->nodes[i];
3160 VALUE shell = RARRAY_AREF(shells, i);
3161 switch (n->kind) {
3162 case COURIER_KIND_ARRAY: {
3163 /* The length is known, so set it once and write the slots, rather than
3164 * pushing each element through the capacity check. */
3165 const long len = n->u.ary.len;
3166 if (len > 0) {
3167 rb_ary_resize(shell, len);
3168 for (long j = 0; j < len; j++) {
3169 RARRAY_ASET(shell, j, courier_child(c, shells, n->u.ary.elems[j]));
3170 }
3171 }
3172 break;
3173 }
3174 case COURIER_KIND_HASH:
3175 /* Entry insertion is deferred to a third pass: insertion calls the key's
3176 * #hash / #eql?, and a content-based #hash would collide on every key while
3177 * the graph is still empty, collapsing entries. */
3178 break;
3179 case COURIER_KIND_STRUCT:
3180 for (long j = 0; j < n->u.strct.len; j++) {
3181 RSTRUCT_SET(shell, (int)j, courier_child(c, shells, n->u.strct.elems[j]));
3182 }
3183 break;
3184 case COURIER_KIND_MATCH:
3185 rb_match_blob_load(shell, courier_child(c, shells, n->u.match.regexp_id),
3186 courier_child(c, shells, n->u.match.str_id),
3187 n->u.match.num_regs, n->u.match.regs);
3188 break;
3189 case COURIER_KIND_IO: {
3190 /* Write the rebuilt VALUE members back into fptr (capture detached them).
3191 * write_lock and wakeup_mutex stay nil; io.c recreates them lazily. */
3192 struct rb_io *fptr = RFILE(shell)->fptr;
3193 RB_OBJ_WRITE(shell, &fptr->pathv, courier_child(c, shells, n->u.io.pathv_id));
3194 RB_OBJ_WRITE(shell, &fptr->encs.ecopts, courier_child(c, shells, n->u.io.ecopts_id));
3195 RB_OBJ_WRITE(shell, &fptr->writeconv_pre_ecopts, courier_child(c, shells, n->u.io.wc_pre_ecopts_id));
3196 RB_OBJ_WRITE(shell, &fptr->writeconv_asciicompat, courier_child(c, shells, n->u.io.wc_asciicompat_id));
3197 RB_OBJ_WRITE(shell, &fptr->timeout, courier_child(c, shells, n->u.io.timeout_id));
3198 break;
3199 }
3200 default:
3201 break;
3202 }
3203 /* Restore instance and generic ivars (any non-REF node can have them) */
3204 for (uint32_t j = 0; j < n->niv; j++) {
3205 rb_ivar_set(shell, n->iv_ids[j], courier_child(c, shells, n->iv_vals[j]));
3206 }
3207 }
3208
3209 /* Insert hash entries only once every shell is filled. Ids are assigned
3210 * depth-first (children larger), so inserting in reverse settles nested hash keys
3211 * inside-out (a #hash cycling through itself is out of scope). */
3212 for (uint32_t i = c->count; i > 0; i--) {
3213 struct courier_node *n = &c->nodes[i - 1];
3214 if (n->kind != COURIER_KIND_HASH) continue;
3215 VALUE shell = RARRAY_AREF(shells, i - 1);
3216 for (long j = 0; j < n->u.hash.size; j++) {
3217 rb_hash_aset(shell, courier_child(c, shells, n->u.hash.kv[2 * j]),
3218 courier_child(c, shells, n->u.hash.kv[2 * j + 1]));
3219 }
3220 /* Restore the default value and default proc (before freezing) */
3221 VALUE ifnone = courier_child(c, shells, n->u.hash.ifnone_id);
3222 if (n->u.hash.proc_default) {
3223 rb_hash_set_default_proc(shell, ifnone);
3224 }
3225 else if (ifnone != Qnil) {
3226 rb_hash_set_default(shell, ifnone);
3227 }
3228 }
3229
3230 /* Freeze after filling, so frozen containers and strings can be built too. */
3231 for (uint32_t i = 0; i < c->count; i++) {
3232 VALUE shell = RARRAY_AREF(shells, i);
3233 if (c->nodes[i].frozen && !RB_SPECIAL_CONST_P(shell)) {
3234 rb_obj_freeze(shell);
3235 }
3236 }
3237
3238 VALUE root = (c->count || c->refs_count) ? courier_child(c, shells, c->root) : Qnil;
3239 RB_GC_GUARD(shells);
3240 return root;
3241}
3242
3243void
3244rb_ractor_courier_free(struct rb_ractor_courier *c)
3245{
3246 for (uint32_t i = 0; i < c->count; i++) {
3247 struct courier_node *n = &c->nodes[i];
3248 ruby_xfree(n->iv_ids);
3249 ruby_xfree(n->iv_vals);
3250 switch (n->kind) {
3251 case COURIER_KIND_STRING:
3252 ruby_xfree(n->u.str.ptr);
3253 break;
3254 case COURIER_KIND_ARRAY:
3255 ruby_xfree(n->u.ary.elems);
3256 break;
3257 case COURIER_KIND_HASH:
3258 ruby_xfree(n->u.hash.kv);
3259 break;
3260 case COURIER_KIND_STRUCT:
3261 ruby_xfree(n->u.strct.elems);
3262 break;
3263 case COURIER_KIND_MATCH:
3264 rb_match_blob_free(n->u.match.regs);
3265 break;
3266 case COURIER_KIND_BACKTRACE:
3267 ruby_xfree(n->u.bt.blob);
3268 break;
3269 case COURIER_KIND_IO:
3270 /* A delivered IO left fptr == NULL (the rebuilt IO owns it). An
3271 * undelivered one still owns the fd and its source is already a
3272 * RactorMovedObject nobody can close: close it here, not leak it. */
3273 if (n->u.io.fptr) {
3274 rb_io_fptr_finalize(n->u.io.fptr);
3275 n->u.io.fptr = NULL;
3276 }
3277 break;
3278 default:
3279 break;
3280 }
3281 }
3282 ruby_xfree(c->nodes);
3283 ruby_xfree(c->refs);
3284 ruby_xfree(c);
3285}
3286
3287/* Mark the only VALUEs a courier holds: shareable objects and immediates (REF) and the
3288 * classes of its objects. All of them are shareable, so marking cannot race, and the
3289 * global GC keeps them reachable through the courier. */
3290void
3291rb_ractor_courier_mark(struct rb_ractor_courier *c)
3292{
3293 if (!c) return;
3294 for (uint32_t i = 0; i < c->refs_count; i++) {
3295 rb_gc_mark(c->refs[i]);
3296 }
3297 for (uint32_t i = 0; i < c->count; i++) {
3298 struct courier_node *n = &c->nodes[i];
3299 if (n->kind == COURIER_KIND_REF) {
3300 rb_gc_mark(n->u.ref);
3301 }
3302 else if (n->kind == COURIER_KIND_OBJECT) {
3303 rb_gc_mark(n->u.obj.klass);
3304 }
3305 else if (n->kind == COURIER_KIND_STRUCT) {
3306 rb_gc_mark(n->u.strct.klass);
3307 }
3308 else if (n->kind == COURIER_KIND_MATCH) {
3309 rb_gc_mark(n->u.match.klass);
3310 }
3311 else if (n->kind == COURIER_KIND_IO) {
3312 rb_gc_mark(n->u.io.klass);
3313 }
3314 else if (n->kind == COURIER_KIND_STRING) {
3315 rb_gc_mark(n->u.str.klass);
3316 }
3317 else if (n->kind == COURIER_KIND_BACKTRACE) {
3318 rb_backtrace_blob_mark(n->u.bt.blob, n->u.bt.size);
3319 }
3320 else if (n->kind == COURIER_KIND_ARRAY) {
3321 rb_gc_mark(n->u.ary.klass);
3322 }
3323 else if (n->kind == COURIER_KIND_HASH) {
3324 rb_gc_mark(n->u.hash.klass);
3325 }
3326 }
3327}
3328
3329/* The message copy traversal never calls #clone or #initialize_clone. Core container
3330 * types get a native shallow copy here (the traversal then rewrites the children inside
3331 * the copy); any other unshareable type falls back to a full Marshal round trip. */
3332static VALUE
3333ractor_native_shallow_copy(VALUE obj)
3334{
3335 VALUE copy;
3336
3337 /* An object with a singleton class cannot be copied natively; fall back to Marshal
3338 * so it reports a proper error. */
3339 VALUE klass = RBASIC_CLASS(obj);
3340 if (klass == 0 || FL_TEST_RAW(klass, FL_SINGLETON)) {
3341 return Qundef;
3342 }
3343
3344 switch (BUILTIN_TYPE(obj)) {
3345 case T_OBJECT:
3346 copy = rb_obj_alloc(rb_obj_class(obj));
3347 rb_obj_copy_ivar(copy, obj);
3348 break;
3349 case T_STRING:
3350 copy = rb_enc_str_new(RSTRING_PTR(obj), RSTRING_LEN(obj), rb_enc_get(obj));
3351 break;
3352 case T_ARRAY:
3354 break;
3355 case T_HASH:
3356 copy = rb_hash_dup(obj);
3357 break;
3358 case T_STRUCT:
3359 copy = rb_obj_alloc(rb_obj_class(obj));
3360 rb_struct_init_copy(copy, obj);
3361 break;
3362 case T_MATCH:
3363 copy = rb_obj_alloc(rb_obj_class(obj));
3364 rb_match_init_copy(copy, obj);
3365 break;
3366 case T_DATA:
3367 /* Keep a copied exception from carrying a raw pointer to the sender's backtrace
3368 * across objspaces */
3369 if (rb_backtrace_p(obj)) {
3370 copy = rb_backtrace_dup(obj);
3371 break;
3372 }
3373 return Qundef;
3374 default:
3375 return Qundef;
3376 }
3377
3378 /* A non-T_OBJECT host keeps its ivars in the generic fields table: copy them.
3379 * T_HASH is excluded: rb_hash_dup already ran rb_copy_generic_ivar, and a second
3380 * call asserts in rb_shape_rebuild (the first gave the copy an ivar shape). */
3381 if (BUILTIN_TYPE(obj) != T_OBJECT && BUILTIN_TYPE(obj) != T_HASH &&
3382 UNLIKELY(rb_obj_gen_fields_p(obj))) {
3383 rb_copy_generic_ivar(copy, obj);
3384 }
3385
3386 /* The traversal rewrites the children inside the copy with raw stores, so the frozen
3387 * bit can be set now: by the time leave runs the original is out of sight. */
3388 if (OBJ_FROZEN(obj)) {
3390 }
3391 return copy;
3392}
3393
3394static enum obj_traverse_iterator_result
3395copy_enter(VALUE obj, struct obj_traverse_replace_data *data)
3396{
3397 if (rb_ractor_shareable_p(obj)) {
3398 data->replacement = obj;
3399 return traverse_skip;
3400 }
3401 else {
3402 VALUE copy = ractor_native_shallow_copy(obj);
3403 if (UNDEF_P(copy)) return traverse_stop; /* no native copy for this type */
3404 data->replacement = copy;
3405 return traverse_cont;
3406 }
3407}
3408
3409static enum obj_traverse_iterator_result
3410copy_leave(VALUE obj, struct obj_traverse_replace_data *data)
3411{
3412 return traverse_cont;
3413}
3414
3415/* Native deep copy of obj's graph. Returns Qundef when it contains a type the native
3416 * copier does not support, and the caller falls back to Marshal. */
3417static VALUE
3418ractor_copy_native_try(VALUE obj)
3419{
3420 return rb_obj_traverse_replace(obj, copy_enter, copy_leave, false);
3421}
3422
3423/* Deep copy within one objspace (Ractor.make_shareable(obj, copy: true)): native first,
3424 * then a whole-graph Marshal round trip. */
3425static VALUE
3426ractor_copy(VALUE obj)
3427{
3428 VALUE copy = ractor_copy_native_try(obj);
3429 if (UNDEF_P(copy)) {
3430 copy = rb_marshal_load(rb_rescue2(ractor_marshal_dump_body, obj,
3431 ractor_marshal_dump_rescue, obj,
3432 rb_eTypeError, (VALUE)0));
3433 }
3434 return copy;
3435}
3436
3437// Ractor local storage
3438
3440 const struct rb_ractor_local_storage_type *type;
3441 void *main_cache;
3442};
3443
3445 int cnt;
3446 int capa;
3448} freed_ractor_local_keys;
3449
3450/* Purge deleted ractor-local keys from the storage tables and run their free hooks. */
3451static void
3452ractor_local_keys_purge(st_table *local_storage)
3453{
3454 for (int i=0; i<freed_ractor_local_keys.cnt; i++) {
3455 rb_ractor_local_key_t key = freed_ractor_local_keys.keys[i];
3456 st_data_t val, k = (st_data_t)key;
3457 if (st_delete(local_storage, &k, &val) &&
3458 (key = (rb_ractor_local_key_t)k)->type->free) {
3459 (*key->type->free)((void *)val);
3460 }
3461 }
3462}
3463
3464
3465static int
3466ractor_local_storage_mark_i(st_data_t key, st_data_t val, st_data_t dmy)
3467{
3469 if (k->type->mark) (*k->type->mark)((void *)val);
3470 return ST_CONTINUE;
3471}
3472
3473static enum rb_id_table_iterator_result
3474idkey_local_storage_mark_i(VALUE val, void *dmy)
3475{
3476 rb_gc_mark(val);
3477 return ID_TABLE_CONTINUE;
3478}
3479
3480static void
3481ractor_local_storage_mark(rb_ractor_t *r)
3482{
3483 if (r->local_storage) {
3484 st_foreach(r->local_storage, ractor_local_storage_mark_i, 0);
3485
3486 /* A deleted key is purged from every Ractor's storage in one collection, which
3487 * then frees its struct. Only a collection that visits every Ractor with no
3488 * other marker running can do that: a global GC, or a single objspace. */
3489 if (rb_gc_single_objspace_p() || rb_gc_during_global_gc_p()) {
3490 ractor_local_keys_purge(r->local_storage);
3491 }
3492 }
3493
3494 if (r->idkey_local_storage) {
3495 rb_id_table_foreach_values(r->idkey_local_storage, idkey_local_storage_mark_i, NULL);
3496 }
3497
3498 rb_gc_mark(r->local_storage_store_lock);
3499}
3500
3501static int
3502ractor_local_storage_free_i(st_data_t key, st_data_t val, st_data_t dmy)
3503{
3505 if (k->type->free) (*k->type->free)((void *)val);
3506 return ST_CONTINUE;
3507}
3508
3509static void
3510ractor_local_storage_free(rb_ractor_t *r)
3511{
3512 if (r->local_storage) {
3513 st_foreach(r->local_storage, ractor_local_storage_free_i, 0);
3514 st_free_table(r->local_storage);
3515 }
3516
3517 if (r->idkey_local_storage) {
3518 rb_id_table_free(r->idkey_local_storage);
3519 }
3520}
3521
3522static void
3523rb_ractor_local_storage_value_mark(void *ptr)
3524{
3525 rb_gc_mark((VALUE)ptr);
3526}
3527
3528static const struct rb_ractor_local_storage_type ractor_local_storage_type_null = {
3529 NULL,
3530 NULL,
3531};
3532
3534 NULL,
3535 ruby_xfree,
3536};
3537
3538static const struct rb_ractor_local_storage_type ractor_local_storage_type_value = {
3539 rb_ractor_local_storage_value_mark,
3540 NULL,
3541};
3542
3545{
3547 key->type = type ? type : &ractor_local_storage_type_null;
3548 key->main_cache = (void *)Qundef;
3549 return key;
3550}
3551
3554{
3555 return rb_ractor_local_storage_ptr_newkey(&ractor_local_storage_type_value);
3556}
3557
3558void
3559rb_ractor_local_storage_delkey(rb_ractor_local_key_t key)
3560{
3561 RB_VM_LOCKING() {
3562 if (freed_ractor_local_keys.cnt == freed_ractor_local_keys.capa) {
3563 freed_ractor_local_keys.capa = freed_ractor_local_keys.capa ? freed_ractor_local_keys.capa * 2 : 4;
3564 SIZED_REALLOC_N(freed_ractor_local_keys.keys, rb_ractor_local_key_t, freed_ractor_local_keys.capa, freed_ractor_local_keys.cnt);
3565 }
3566 freed_ractor_local_keys.keys[freed_ractor_local_keys.cnt++] = key;
3567 }
3568}
3569
3570static bool
3571ractor_local_ref(rb_ractor_local_key_t key, void **pret)
3572{
3573 if (rb_ractor_main_p()) {
3574 if (!UNDEF_P((VALUE)key->main_cache)) {
3575 *pret = key->main_cache;
3576 return true;
3577 }
3578 else {
3579 return false;
3580 }
3581 }
3582 else {
3583 rb_ractor_t *cr = GET_RACTOR();
3584
3585 if (cr->local_storage && st_lookup(cr->local_storage, (st_data_t)key, (st_data_t *)pret)) {
3586 return true;
3587 }
3588 else {
3589 return false;
3590 }
3591 }
3592}
3593
3594static void
3595ractor_local_set(rb_ractor_local_key_t key, void *ptr)
3596{
3597 rb_ractor_t *cr = GET_RACTOR();
3598
3599 if (cr->local_storage == NULL) {
3600 cr->local_storage = st_init_numtable();
3601 }
3602
3603 st_insert(cr->local_storage, (st_data_t)key, (st_data_t)ptr);
3604
3605 if (rb_ractor_main_p()) {
3606 key->main_cache = ptr;
3607 }
3608}
3609
3610VALUE
3612{
3613 void *val;
3614 if (ractor_local_ref(key, &val)) {
3615 return (VALUE)val;
3616 }
3617 else {
3618 return Qnil;
3619 }
3620}
3621
3622bool
3624{
3625 if (ractor_local_ref(key, (void **)val)) {
3626 return true;
3627 }
3628 else {
3629 return false;
3630 }
3631}
3632
3633void
3635{
3636 ractor_local_set(key, (void *)val);
3637}
3638
3639void *
3641{
3642 void *ret;
3643 if (ractor_local_ref(key, &ret)) {
3644 return ret;
3645 }
3646 else {
3647 return NULL;
3648 }
3649}
3650
3651void
3653{
3654 ractor_local_set(key, ptr);
3655}
3656
3657#define DEFAULT_KEYS_CAPA 0x10
3658
3659void
3660rb_ractor_finish_marking(bool full_mark)
3661{
3662 /* A freed key's struct may only be released by a collection that purged every
3663 * Ractor's storage with no other marker running: a global GC, or a single objspace.
3664 * A local GC also reaches here (gc_marks_finish) and must do nothing. */
3665 if (!(rb_gc_single_objspace_p() || rb_gc_during_global_gc_p())) {
3666 return;
3667 }
3668
3669 /* The root scan's purge never reaches a zombie's storage (not in the set;
3670 * zombie_objspaces only marks the join slot): purge here, under the barrier, before
3671 * the struct is freed, or a later ractor_free reads a freed key. */
3672 rb_vm_t *vm = GET_VM();
3673 rb_ractor_t *r;
3674
3675 for (size_t zi = 0; zi < vm->gc.zombie_objspaces_count; zi++) {
3676 rb_ractor_t *owner = vm->gc.zombie_objspaces[zi].owner;
3677 if (owner == NULL || owner->local_storage == NULL) continue;
3678 ractor_local_keys_purge(owner->local_storage);
3679 }
3680
3681 for (int i=0; i<freed_ractor_local_keys.cnt; i++) {
3682 SIZED_FREE(freed_ractor_local_keys.keys[i]);
3683 }
3684 freed_ractor_local_keys.cnt = 0;
3685 if (freed_ractor_local_keys.capa > DEFAULT_KEYS_CAPA) {
3686 freed_ractor_local_keys.capa = DEFAULT_KEYS_CAPA;
3687 SIZED_REALLOC_N(freed_ractor_local_keys.keys, rb_ractor_local_key_t, DEFAULT_KEYS_CAPA, freed_ractor_local_keys.capa);
3688 }
3689
3690 /* Under a minor mark an unmarked port is not a dead one. */
3691 if (full_mark) {
3692 ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
3693 rb_ractor_reap_dead_ports(r);
3694 }
3695 if (vm->ractor.cnt == 0 && vm->ractor.main_ractor) {
3696 rb_ractor_reap_dead_ports(vm->ractor.main_ractor);
3697 }
3698 }
3699}
3700
3701static VALUE
3702ractor_local_value(rb_execution_context_t *ec, VALUE self, VALUE sym)
3703{
3704 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
3705 ID id = rb_check_id(&sym);
3706 struct rb_id_table *tbl = cr->idkey_local_storage;
3707 VALUE val;
3708
3709 if (id && tbl && rb_id_table_lookup(tbl, id, &val)) {
3710 return val;
3711 }
3712 else {
3713 return Qnil;
3714 }
3715}
3716
3717static VALUE
3718ractor_local_value_set(rb_execution_context_t *ec, VALUE self, VALUE sym, VALUE val)
3719{
3720 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
3721 ID id = SYM2ID(rb_to_symbol(sym));
3722 struct rb_id_table *tbl = cr->idkey_local_storage;
3723
3724 if (tbl == NULL) {
3725 tbl = cr->idkey_local_storage = rb_id_table_create(2);
3726 }
3727 rb_id_table_insert(tbl, id, val);
3728 return val;
3729}
3730
3733 struct rb_id_table *tbl;
3734 ID id;
3735 VALUE sym;
3736};
3737
3738static VALUE
3739ractor_local_value_store_i(VALUE ptr)
3740{
3741 VALUE val;
3743
3744 if (rb_id_table_lookup(data->tbl, data->id, &val)) {
3745 // after synchronization, we found already registered entry
3746 }
3747 else {
3748 val = rb_yield(Qnil);
3749 ractor_local_value_set(data->ec, Qnil, data->sym, val);
3750 }
3751 return val;
3752}
3753
3754static VALUE
3755ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE sym)
3756{
3757 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
3758 struct ractor_local_storage_store_data data = {
3759 .ec = ec,
3760 .sym = sym,
3761 .id = SYM2ID(rb_to_symbol(sym)),
3762 .tbl = cr->idkey_local_storage,
3763 };
3764 VALUE val;
3765
3766 if (data.tbl == NULL) {
3767 data.tbl = cr->idkey_local_storage = rb_id_table_create(2);
3768 }
3769 else if (rb_id_table_lookup(data.tbl, data.id, &val)) {
3770 // already set
3771 return val;
3772 }
3773
3774 if (!cr->local_storage_store_lock) {
3775 cr->local_storage_store_lock = rb_mutex_new();
3776 }
3777
3778 return rb_mutex_synchronize(cr->local_storage_store_lock, ractor_local_value_store_i, (VALUE)&data);
3779}
3780
3781// shareable_proc
3782
3783static VALUE
3784ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_lambda)
3785{
3786 if (!rb_ractor_shareable_p(replace_self)) {
3787 rb_raise(rb_eRactorIsolationError, "self should be shareable: %" PRIsVALUE, replace_self);
3788 }
3789 else {
3790 VALUE proc = is_lambda ? rb_block_lambda() : rb_block_proc();
3791 return rb_proc_ractor_make_shareable(rb_proc_dup(proc), replace_self);
3792 }
3793}
3794
3795// Ractor#require
3796
3798 VALUE port;
3799 bool raised;
3800
3801 union {
3802 struct {
3803 VALUE feature;
3804 } require;
3805
3806 struct {
3807 VALUE module;
3808 ID name;
3809 } autoload;
3810 } as;
3811
3812 bool silent;
3813};
3814
3815RUBY_REFERENCES(cross_ractor_require_refs) = {
3816 RUBY_REF_EDGE(struct cross_ractor_require, port),
3817 RUBY_REF_EDGE(struct cross_ractor_require, as.require.feature),
3818 RUBY_REF_END
3819};
3820
3821static const rb_data_type_t cross_ractor_require_data_type = {
3822 "ractor/cross_ractor_require",
3823 {
3824 RUBY_REFS_LIST_PTR(cross_ractor_require_refs),
3826 NULL, // memsize
3827 NULL, // compact
3828 },
3829 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_DECL_MARKING | RUBY_TYPED_EMBEDDABLE
3830};
3831
3832static VALUE
3833require_body(VALUE crr_obj)
3834{
3835 struct cross_ractor_require *crr;
3836 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
3837 VALUE feature = crr->as.require.feature;
3838
3839 ID require;
3840 CONST_ID(require, "require");
3841
3842 if (crr->silent) {
3843 int rb_require_internal_silent(VALUE fname);
3844 return INT2NUM(rb_require_internal_silent(feature));
3845 }
3846 else {
3847 return rb_funcallv(Qnil, require, 1, &feature);
3848 }
3849}
3850
3851static VALUE
3852require_rescue(VALUE crr_obj, VALUE errinfo)
3853{
3854 struct cross_ractor_require *crr;
3855 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
3856 crr->raised = true;
3857 return errinfo;
3858}
3859
3860static VALUE
3861require_result_send_body(VALUE ary)
3862{
3863 VALUE port = RARRAY_AREF(ary, 0);
3864 VALUE results = RARRAY_AREF(ary, 1);
3865
3866 rb_execution_context_t *ec = GET_EC();
3867
3868 ractor_port_send(ec, port, results, Qfalse);
3869 return Qnil;
3870}
3871
3872static VALUE
3873require_result_send_resuce(VALUE port, VALUE errinfo)
3874{
3875 // TODO: need rescue?
3876 ractor_port_send(GET_EC(), port, errinfo, Qfalse);
3877 return Qnil;
3878}
3879
3880static VALUE
3881ractor_require_protect(VALUE crr_obj, VALUE (*func)(VALUE))
3882{
3883 struct cross_ractor_require *crr;
3884 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
3885
3886 const bool silent = crr->silent;
3887
3888 VALUE debug, errinfo;
3889 if (silent) {
3890 debug = ruby_debug;
3891 errinfo = rb_errinfo();
3892 }
3893
3894 // get normal result or raised exception (with crr->raised == true)
3895 VALUE result = rb_rescue2(func, crr_obj, require_rescue, crr_obj, rb_eException, 0);
3896
3897 if (silent) {
3898 ruby_debug = debug;
3899 rb_set_errinfo(errinfo);
3900 }
3901
3902 rb_rescue2(require_result_send_body,
3903 // [port, [result, raised]]
3904 rb_ary_new_from_args(2, crr->port, rb_ary_new_from_args(2, result, crr->raised ? Qtrue : Qfalse)),
3905 require_result_send_resuce, rb_eException, crr->port);
3906
3907 RB_GC_GUARD(crr_obj);
3908 return Qnil;
3909}
3910
3911static VALUE
3912ractor_require_func(void *crr_obj)
3913{
3914 return ractor_require_protect((VALUE)crr_obj, require_body);
3915}
3916
3917VALUE
3918rb_ractor_require(VALUE feature, bool silent)
3919{
3920 // We're about to block on the main ractor, so if we're holding the global lock we'll deadlock.
3921 ASSERT_vm_unlocking();
3922
3923 struct cross_ractor_require *crr;
3924 VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
3925 RB_OBJ_SET_SHAREABLE(crr_obj); // TODO: internal data?
3926
3927 // Convert feature to proper file path and make it shareable as fstring
3928 RB_OBJ_WRITE(crr_obj, &crr->as.require.feature, rb_fstring(FilePathValue(feature)));
3929 RB_OBJ_WRITE(crr_obj, &crr->port, rb_ractor_make_shareable(ractor_port_new(GET_RACTOR())));
3930 crr->raised = false;
3931 crr->silent = silent;
3932
3933 rb_execution_context_t *ec = GET_EC();
3934 rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
3935 rb_ractor_interrupt_exec(main_r, ractor_require_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
3936
3937 // wait for require done
3938 VALUE results = ractor_port_receive(ec, crr->port, Qnil);
3939 ractor_port_close(ec, crr->port);
3940
3941 VALUE exc = rb_ary_pop(results);
3942 VALUE result = rb_ary_pop(results);
3943 RB_GC_GUARD(crr_obj);
3944
3945 if (RTEST(exc)) {
3946 rb_exc_raise(result);
3947 }
3948 else {
3949 return result;
3950 }
3951}
3952
3953static VALUE
3954ractor_require(rb_execution_context_t *ec, VALUE self, VALUE feature)
3955{
3956 return rb_ractor_require(feature, false);
3957}
3958
3959static VALUE
3960autoload_load_body(VALUE crr_obj)
3961{
3962 struct cross_ractor_require *crr;
3963 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
3964 return rb_autoload_load(crr->as.autoload.module, crr->as.autoload.name);
3965}
3966
3967static VALUE
3968ractor_autoload_load_func(void *crr_obj)
3969{
3970 return ractor_require_protect((VALUE)crr_obj, autoload_load_body);
3971}
3972
3973VALUE
3974rb_ractor_autoload_load(VALUE module, ID name)
3975{
3976 struct cross_ractor_require *crr;
3977 VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
3978 RB_OBJ_SET_SHAREABLE(crr_obj); // TODO: internal data?
3979
3980 RB_OBJ_WRITE(crr_obj, &crr->as.autoload.module, module);
3981 RB_OBJ_WRITE(crr_obj, &crr->as.autoload.name, name);
3982 RB_OBJ_WRITE(crr_obj, &crr->port, rb_ractor_make_shareable(ractor_port_new(GET_RACTOR())));
3983
3984 rb_execution_context_t *ec = GET_EC();
3985 rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
3986 rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
3987
3988 // wait for require done
3989 VALUE results = ractor_port_receive(ec, crr->port, Qnil);
3990 ractor_port_close(ec, crr->port);
3991
3992 VALUE exc = rb_ary_pop(results);
3993 VALUE result = rb_ary_pop(results);
3994 RB_GC_GUARD(crr_obj);
3995
3996 if (RTEST(exc)) {
3997 rb_exc_raise(result);
3998 }
3999 else {
4000 return result;
4001 }
4002}
4003
4004#include "ractor.rbinc"
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define RUBY_ATOMIC_PTR_LOAD(var)
Identical to RUBY_ATOMIC_LOAD, except it expects its arguments are void*.
Definition atomic.h:338
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_FETCH_ADD(var, val)
Atomically replaces the value pointed by var with the result of addition of val to the old value of v...
Definition atomic.h:118
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
static VALUE RB_OBJ_FROZEN_RAW(VALUE obj)
This is an implementation detail of RB_OBJ_FROZEN().
Definition fl_type.h:696
static void RB_FL_SET_RAW(VALUE obj, VALUE flags)
This is an implementation detail of RB_FL_SET().
Definition fl_type.h:541
@ RUBY_FL_FREEZE
This flag has something to do with data immutability.
Definition fl_type.h:278
@ RUBY_FL_SHAREABLE
This flag has something to do with Ractor.
Definition fl_type.h:253
void rb_singleton_class_attached(VALUE klass, VALUE obj)
Attaches a singleton class to its corresponding object.
Definition class.c:1128
#define T_COMPLEX
Old name of RUBY_T_COMPLEX.
Definition value_type.h:59
#define TYPE(_)
Old name of rb_type.
Definition value_type.h:108
#define FL_SINGLETON
Old name of RUBY_FL_SINGLETON.
Definition fl_type.h:58
#define T_FILE
Old name of RUBY_T_FILE.
Definition value_type.h:62
#define FL_PROMOTED
Old name of RUBY_FL_PROMOTED.
Definition fl_type.h:60
#define REALLOC_N
Old name of RB_REALLOC_N.
Definition memory.h:403
#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 OBJ_FROZEN
Old name of RB_OBJ_FROZEN.
Definition fl_type.h:133
#define T_FLOAT
Old name of RUBY_T_FLOAT.
Definition value_type.h:64
#define T_IMEMO
Old name of RUBY_T_IMEMO.
Definition value_type.h:67
#define T_BIGNUM
Old name of RUBY_T_BIGNUM.
Definition value_type.h:57
#define SPECIAL_CONST_P
Old name of RB_SPECIAL_CONST_P.
#define T_STRUCT
Old name of RUBY_T_STRUCT.
Definition value_type.h:79
#define SYM2ID
Old name of RB_SYM2ID.
Definition symbol.h:45
#define T_DATA
Old name of RUBY_T_DATA.
Definition value_type.h:60
#define ZALLOC
Old name of RB_ZALLOC.
Definition memory.h:402
#define FL_SHAREABLE
Old name of RUBY_FL_SHAREABLE.
Definition fl_type.h:62
#define ENCODING_GET(obj)
Old name of RB_ENCODING_GET.
Definition encoding.h:109
#define T_MODULE
Old name of RUBY_T_MODULE.
Definition value_type.h:70
#define T_RATIONAL
Old name of RUBY_T_RATIONAL.
Definition value_type.h:76
#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 ALLOC_N
Old name of RB_ALLOC_N.
Definition memory.h:399
#define FL_TEST_RAW
Old name of RB_FL_TEST_RAW.
Definition fl_type.h:128
#define Qtrue
Old name of RUBY_Qtrue.
#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 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 T_SYMBOL
Old name of RUBY_T_SYMBOL.
Definition value_type.h:80
#define T_MATCH
Old name of RUBY_T_MATCH.
Definition value_type.h:69
#define T_CLASS
Old name of RUBY_T_CLASS.
Definition value_type.h:58
#define BUILTIN_TYPE
Old name of RB_BUILTIN_TYPE.
Definition value_type.h:85
#define FL_FREEZE
Old name of RUBY_FL_FREEZE.
Definition fl_type.h:65
#define CONST_ID
Old name of RUBY_CONST_ID.
Definition symbol.h:47
#define FL_SET_RAW
Old name of RB_FL_SET_RAW.
Definition fl_type.h:126
#define T_REGEXP
Old name of RUBY_T_REGEXP.
Definition value_type.h:77
#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:676
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1431
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1429
VALUE rb_eStopIteration
StopIteration exception.
Definition enumerator.c:195
VALUE rb_exc_new_str(VALUE etype, VALUE str)
Identical to rb_exc_new_cstr(), except it takes a Ruby's string instead of C's.
Definition error.c:1482
VALUE rb_eException
Mother of all exceptions.
Definition error.c:1423
VALUE rb_cArray
Array class.
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:2250
VALUE rb_obj_hide(VALUE obj)
Make the object invisible from Ruby code.
Definition object.c:94
VALUE rb_cRactor
Ractor class.
Definition ractor.c:36
VALUE rb_stdin
STDIN constant.
Definition io.c:203
VALUE rb_cHash
Hash class.
Definition hash.c:122
VALUE rb_stderr
STDERR constant.
Definition io.c:203
static VALUE rb_class_of(VALUE obj)
Object to class mapping function.
Definition globals.h:174
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:234
VALUE rb_cBasicObject
BasicObject class.
Definition object.c:58
VALUE rb_class_real(VALUE klass)
Finds a "real" class.
Definition object.c:225
VALUE rb_obj_freeze(VALUE obj)
Just calls rb_obj_freeze_inline() inside.
Definition object.c:1308
VALUE rb_stdout
STDOUT constant.
Definition io.c:203
VALUE rb_cString
String class.
Definition string.c:85
#define RB_OBJ_WRITTEN(old, oldv, young)
Identical to RB_OBJ_WRITE(), except it doesn't write any values, but only a WB declaration.
Definition gc.h:468
#define RB_OBJ_WRITE(old, slot, young)
Declaration of a "back" pointer.
Definition gc.h:456
Encoding relates APIs.
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1123
VALUE rb_funcallv(VALUE recv, ID mid, int argc, const VALUE *argv)
Identical to rb_funcall(), except it takes the method arguments as a C array.
Definition vm_eval.c:1081
VALUE rb_ary_new_from_values(long n, const VALUE *elts)
Identical to rb_ary_new_from_args(), except how objects are passed.
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_new_capa(long capa)
Identical to rb_ary_new(), except it additionally specifies how many rooms of objects it should alloc...
VALUE rb_ary_resize(VALUE ary, long len)
Expands or shrinks the passed array to the passed length.
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_hidden_new(long 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_freeze(VALUE obj)
Freeze an array, preventing further modifications.
VALUE rb_block_proc(void)
Constructs a Proc object from implicitly passed components.
Definition proc.c:1575
VALUE rb_block_lambda(void)
Identical to rb_proc_new(), except it returns a lambda.
Definition proc.c:1594
VALUE rb_obj_is_proc(VALUE recv)
Queries if the given object is a proc.
Definition proc.c:386
VALUE rb_str_new_frozen(VALUE str)
Creates a frozen copy of the string, if necessary.
Definition string.c:1555
VALUE rb_str_freeze(VALUE str)
This is the implementation of String#freeze.
Definition string.c:3376
VALUE rb_mutex_new(void)
Creates a mutex.
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_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:2060
VALUE rb_autoload_load(VALUE space, ID name)
Kicks the autoload procedure as if it was "touched".
Definition variable.c:3260
VALUE rb_class_name(VALUE obj)
Queries the name of the given object's class.
Definition variable.c:515
void rb_free_generic_ivar(VALUE obj)
Frees the list of instance variables.
Definition variable.c:1382
void rb_undef_alloc_func(VALUE klass)
Deletes the allocator function of a class.
Definition vm_method.c:1809
ID rb_check_id(volatile VALUE *namep)
Detects if the given name is already interned or not.
Definition symbol.c:1288
VALUE rb_to_symbol(VALUE name)
Identical to rb_intern_str(), except it generates a dynamic symbol if necessary.
Definition string.c:13740
int capa
Designed capacity of the buffer.
Definition io.h:11
int rb_io_fptr_finalize(rb_io_t *fptr)
Destroys the given IO.
Definition io.c:5714
int len
Length of the buffer.
Definition io.h:8
const struct rb_ractor_local_storage_type rb_ractor_local_storage_type_free
A type of ractor-local storage that destructs itself using ruby_xfree.
Definition ractor.c:3533
VALUE rb_ractor_make_shareable_copy(VALUE obj)
Identical to rb_ractor_make_shareable(), except it returns a (deep) copy of the passed one instead of...
Definition ractor.c:1923
struct rb_ractor_local_key_struct * rb_ractor_local_key_t
(Opaque) struct that holds a ractor-local storage key.
Definition ractor.h:42
void * rb_ractor_local_storage_ptr(rb_ractor_local_key_t key)
Identical to rb_ractor_local_storage_value() except the return type.
Definition ractor.c:3640
void rb_ractor_local_storage_ptr_set(rb_ractor_local_key_t key, void *ptr)
Identical to rb_ractor_local_storage_value_set() except the parameter type.
Definition ractor.c:3652
rb_ractor_local_key_t rb_ractor_local_storage_ptr_newkey(const struct rb_ractor_local_storage_type *type)
Extended version of rb_ractor_local_storage_value_newkey().
Definition ractor.c:3544
VALUE rb_ractor_stdin(void)
Queries the standard input of the current Ractor that is calling this function.
Definition ractor.c:1371
static bool rb_ractor_shareable_p(VALUE obj)
Queries if multiple Ractors can share the passed object or not.
Definition ractor.h:249
void rb_ractor_stderr_set(VALUE io)
Assigns an IO to the standard error of the Ractor that is calling this function.
Definition ractor.c:1440
void rb_ractor_local_storage_value_set(rb_ractor_local_key_t key, VALUE val)
Associates the passed value to the passed key.
Definition ractor.c:3634
bool rb_ractor_local_storage_value_lookup(rb_ractor_local_key_t key, VALUE *val)
Queries the key.
Definition ractor.c:3623
#define RB_OBJ_SHAREABLE_P(obj)
Queries if the passed object has previously classified as shareable or not.
Definition ractor.h:235
VALUE rb_ractor_make_shareable(VALUE obj)
Destructively transforms the passed object so that multiple Ractors can share it.
Definition ractor.c:1912
rb_ractor_local_key_t rb_ractor_local_storage_value_newkey(void)
Issues a new key.
Definition ractor.c:3553
void rb_ractor_stdout_set(VALUE io)
Assigns an IO to the standard output of the Ractor that is calling this function.
Definition ractor.c:1428
void rb_ractor_stdin_set(VALUE io)
Assigns an IO to the standard input of the Ractor that is calling this function.
Definition ractor.c:1416
VALUE rb_ractor_local_storage_value(rb_ractor_local_key_t key)
Queries the key.
Definition ractor.c:3611
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1378
rb_block_call_func * rb_block_call_func_t
Shorthand type that represents an iterator-written-in-C function pointer.
Definition iterator.h:88
VALUE rb_marshal_load(VALUE port)
Deserialises a previous output of rb_marshal_dump() into a network of objects.
Definition marshal.c:2671
#define MEMCPY(p1, p2, type, n)
Handy macro to call memcpy.
Definition memory.h:372
#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_proc_new(type *q, VALUE w)
Creates a rb_cProc instance.
VALUE type(ANYARGS)
ANYARGS-ed function type.
void rb_hash_foreach(VALUE q, int_type *w, VALUE e)
Iteration over the given hash.
void rb_ivar_foreach(VALUE q, int_type *w, VALUE e)
Iteration over each instance variable of the object.
VALUE rb_rescue2(type *q, VALUE w, type *e, VALUE r,...)
An equivalent of rescue clause.
void rb_copy_generic_ivar(VALUE clone, VALUE obj)
Copies the list of instance variables.
Definition variable.c:2254
#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
static void RARRAY_ASET(VALUE ary, long i, VALUE v)
Assigns an object in an array.
Definition rarray.h:385
#define RARRAY_AREF(a, i)
Definition rarray.h:402
#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 RUBY_DEFAULT_FREE
This is a value you can set to RData::dfree.
Definition rdata.h:56
#define RFILE(obj)
Convenient casting macro.
Definition rfile.h:50
#define RHASH_SET_IFNONE(h, ifnone)
Destructively updates the default value of the hash.
Definition rhash.h:92
#define RHASH_IFNONE(h)
Definition rhash.h:59
#define RHASH_SIZE(h)
Queries the size of the hash.
Definition rhash.h:69
#define RMATCH(obj)
Convenient casting macro.
Definition rmatch.h:37
#define RSTRING(obj)
Convenient casting macro.
Definition rstring.h:41
#define StringValueCStr(v)
Identical to StringValuePtr, except it additionally checks for the contents for viability as a C stri...
Definition rstring.h:89
static long RSTRUCT_LEN(VALUE st)
Returns the number of struct members.
Definition rstruct.h:82
static VALUE RSTRUCT_SET(VALUE st, int k, VALUE v)
Resembles Struct#[]=.
Definition rstruct.h:92
static VALUE RSTRUCT_GET(VALUE st, int k)
Resembles Struct#[].
Definition rstruct.h:102
#define RUBY_TYPED_FREE_IMMEDIATELY
Macros to see if each corresponding flag is defined.
Definition rtypeddata.h:122
#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:435
static const rb_data_type_t * RTYPEDDATA_TYPE(VALUE obj)
Queries for the type of given object.
Definition rtypeddata.h:692
#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 FilePathValue(v)
Ensures that the parameter object is a path.
Definition ruby.h:90
static bool RB_SPECIAL_CONST_P(VALUE obj)
Checks if the given object is of enum ruby_special_consts.
#define RTEST
This is an old name of RB_TEST.
Ruby object's base components.
Definition rbasic.h:69
Regular expression execution context.
Definition rmatch.h:79
VALUE regexp
The expression of this match.
Definition rmatch.h:92
VALUE str
The target string that the match was made against.
Definition rmatch.h:87
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:238
VALUE ecopts
Flags as Ruby hash.
Definition io.h:152
Ruby's IO, metadata and buffers.
Definition io.h:295
struct rb_io_encoding encs
Decomposed encoding flags.
Definition io.h:348
VALUE self
The IO's Ruby level counterpart.
Definition io.h:298
VALUE write_lock
This is a Ruby level mutex.
Definition io.h:400
VALUE timeout
The timeout associated with this IO when performing blocking operations.
Definition io.h:406
VALUE writeconv_pre_ecopts
Value of ::rb_io_t::rb_io_enc_t::ecopts stored right before initialising rb_io_t::writeconv.
Definition io.h:390
VALUE tied_io_for_writing
Duplex IO object, if set.
Definition io.h:345
VALUE writeconv_asciicompat
This is, when set, an instance of rb_cString which holds the "common" encoding.
Definition io.h:372
VALUE pathv
pathname for file
Definition io.h:322
Type that defines a ractor-local storage.
Definition ractor.h:21
void(* free)(void *ptr)
A function to destruct a ractor-local storage.
Definition ractor.h:37
void(* mark)(void *ptr)
A function to mark a ractor-local storage.
Definition ractor.h:29
Definition st.h:79
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_unlock.
void rb_native_mutex_destroy(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_destroy.
void rb_native_cond_signal(rb_nativethread_cond_t *cond)
Signals a condition variable.
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 enum ruby_value_type RB_BUILTIN_TYPE(VALUE obj)
Queries the type of the object.
Definition value_type.h:182
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