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