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