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