Ruby 3.5.0dev (2025-09-06 revision c0d168068f92e53c7ca67702d6d46fee35e2cc6d)
ractor.c (c0d168068f92e53c7ca67702d6d46fee35e2cc6d)
1// Ractor implementation
2
3#include "ruby/ruby.h"
4#include "ruby/thread.h"
5#include "ruby/ractor.h"
7#include "vm_core.h"
8#include "eval_intern.h"
9#include "vm_sync.h"
10#include "ractor_core.h"
11#include "internal/complex.h"
12#include "internal/error.h"
13#include "internal/gc.h"
14#include "internal/hash.h"
15#include "internal/object.h"
16#include "internal/ractor.h"
17#include "internal/rational.h"
18#include "internal/struct.h"
19#include "internal/thread.h"
20#include "variable.h"
21#include "yjit.h"
22#include "zjit.h"
23
25static VALUE rb_cRactorSelector;
26
27VALUE rb_eRactorUnsafeError;
28VALUE rb_eRactorIsolationError;
29static VALUE rb_eRactorError;
30static VALUE rb_eRactorRemoteError;
31static VALUE rb_eRactorMovedError;
32static VALUE rb_eRactorClosedError;
33static VALUE rb_cRactorMovedObject;
34
35static void vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line);
36
37
38#if RACTOR_CHECK_MODE > 0
39bool rb_ractor_ignore_belonging_flag = false;
40#endif
41
42// Ractor locking
43
44static void
45ASSERT_ractor_unlocking(rb_ractor_t *r)
46{
47#if RACTOR_CHECK_MODE > 0
48 const rb_execution_context_t *ec = rb_current_ec_noinline();
49 if (ec != NULL && r->sync.locked_by == rb_ractor_self(rb_ec_ractor_ptr(ec))) {
50 rb_bug("recursive ractor locking");
51 }
52#endif
53}
54
55static void
56ASSERT_ractor_locking(rb_ractor_t *r)
57{
58#if RACTOR_CHECK_MODE > 0
59 const rb_execution_context_t *ec = rb_current_ec_noinline();
60 if (ec != NULL && r->sync.locked_by != rb_ractor_self(rb_ec_ractor_ptr(ec))) {
61 rp(r->sync.locked_by);
62 rb_bug("ractor lock is not acquired.");
63 }
64#endif
65}
66
67static void
68ractor_lock(rb_ractor_t *r, const char *file, int line)
69{
70 RUBY_DEBUG_LOG2(file, line, "locking r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
71
72 ASSERT_ractor_unlocking(r);
73 rb_native_mutex_lock(&r->sync.lock);
74
75 if (rb_current_execution_context(false)) {
76 VM_ASSERT(!GET_RACTOR()->malloc_gc_disabled);
77 GET_RACTOR()->malloc_gc_disabled = true;
78 }
79
80#if RACTOR_CHECK_MODE > 0
81 if (rb_current_execution_context(false) != NULL) {
82 rb_ractor_t *cr = rb_current_ractor_raw(false);
83 r->sync.locked_by = cr ? rb_ractor_self(cr) : Qundef;
84 }
85#endif
86
87 RUBY_DEBUG_LOG2(file, line, "locked r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
88}
89
90static void
91ractor_lock_self(rb_ractor_t *cr, const char *file, int line)
92{
93 VM_ASSERT(cr == rb_ec_ractor_ptr(rb_current_ec_noinline()));
94#if RACTOR_CHECK_MODE > 0
95 VM_ASSERT(cr->sync.locked_by != cr->pub.self);
96#endif
97 ractor_lock(cr, file, line);
98}
99
100static void
101ractor_unlock(rb_ractor_t *r, const char *file, int line)
102{
103 ASSERT_ractor_locking(r);
104#if RACTOR_CHECK_MODE > 0
105 r->sync.locked_by = Qnil;
106#endif
107
108 if (rb_current_execution_context(false)) {
109 VM_ASSERT(GET_RACTOR()->malloc_gc_disabled);
110 GET_RACTOR()->malloc_gc_disabled = false;
111 }
112
113 rb_native_mutex_unlock(&r->sync.lock);
114
115 RUBY_DEBUG_LOG2(file, line, "r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
116}
117
118static void
119ractor_unlock_self(rb_ractor_t *cr, const char *file, int line)
120{
121 VM_ASSERT(cr == rb_ec_ractor_ptr(rb_current_ec_noinline()));
122#if RACTOR_CHECK_MODE > 0
123 VM_ASSERT(cr->sync.locked_by == cr->pub.self);
124#endif
125 ractor_unlock(cr, file, line);
126}
127
128#define RACTOR_LOCK(r) ractor_lock(r, __FILE__, __LINE__)
129#define RACTOR_UNLOCK(r) ractor_unlock(r, __FILE__, __LINE__)
130#define RACTOR_LOCK_SELF(r) ractor_lock_self(r, __FILE__, __LINE__)
131#define RACTOR_UNLOCK_SELF(r) ractor_unlock_self(r, __FILE__, __LINE__)
132
133void
134rb_ractor_lock_self(rb_ractor_t *r)
135{
136 RACTOR_LOCK_SELF(r);
137}
138
139void
140rb_ractor_unlock_self(rb_ractor_t *r)
141{
142 RACTOR_UNLOCK_SELF(r);
143}
144
145// Ractor status
146
147static const char *
148ractor_status_str(enum ractor_status status)
149{
150 switch (status) {
151 case ractor_created: return "created";
152 case ractor_running: return "running";
153 case ractor_blocking: return "blocking";
154 case ractor_terminated: return "terminated";
155 }
156 rb_bug("unreachable");
157}
158
159static void
160ractor_status_set(rb_ractor_t *r, enum ractor_status status)
161{
162 RUBY_DEBUG_LOG("r:%u [%s]->[%s]", r->pub.id, ractor_status_str(r->status_), ractor_status_str(status));
163
164 // check 1
165 if (r->status_ != ractor_created) {
166 VM_ASSERT(r == GET_RACTOR()); // only self-modification is allowed.
167 ASSERT_vm_locking();
168 }
169
170 // check2: transition check. assume it will be vanished on non-debug build.
171 switch (r->status_) {
172 case ractor_created:
173 VM_ASSERT(status == ractor_blocking);
174 break;
175 case ractor_running:
176 VM_ASSERT(status == ractor_blocking||
177 status == ractor_terminated);
178 break;
179 case ractor_blocking:
180 VM_ASSERT(status == ractor_running);
181 break;
182 case ractor_terminated:
183 rb_bug("unreachable");
184 break;
185 }
186
187 r->status_ = status;
188}
189
190static bool
191ractor_status_p(rb_ractor_t *r, enum ractor_status status)
192{
193 return rb_ractor_status_p(r, status);
194}
195
196// Ractor data/mark/free
197
198static void ractor_local_storage_mark(rb_ractor_t *r);
199static void ractor_local_storage_free(rb_ractor_t *r);
200
201static void ractor_sync_mark(rb_ractor_t *r);
202static void ractor_sync_free(rb_ractor_t *r);
203static size_t ractor_sync_memsize(const rb_ractor_t *r);
204static void ractor_sync_init(rb_ractor_t *r);
205
206static void
207ractor_mark(void *ptr)
208{
209 rb_ractor_t *r = (rb_ractor_t *)ptr;
210
211 // mark received messages
212 ractor_sync_mark(r);
213
214 rb_gc_mark(r->loc);
215 rb_gc_mark(r->name);
216 rb_gc_mark(r->r_stdin);
217 rb_gc_mark(r->r_stdout);
218 rb_gc_mark(r->r_stderr);
219 rb_hook_list_mark(&r->pub.hooks);
220
221 if (r->threads.cnt > 0) {
222 rb_thread_t *th = 0;
223 ccan_list_for_each(&r->threads.set, th, lt_node) {
224 VM_ASSERT(th != NULL);
225 rb_gc_mark(th->self);
226 }
227 }
228
229 ractor_local_storage_mark(r);
230}
231
232static void
233ractor_free(void *ptr)
234{
235 rb_ractor_t *r = (rb_ractor_t *)ptr;
236 RUBY_DEBUG_LOG("free r:%d", rb_ractor_id(r));
237 rb_native_mutex_destroy(&r->sync.lock);
238#ifdef RUBY_THREAD_WIN32_H
239 rb_native_cond_destroy(&r->sync.wakeup_cond);
240#endif
241 ractor_local_storage_free(r);
242 rb_hook_list_free(&r->pub.hooks);
243
244 if (r->newobj_cache) {
245 RUBY_ASSERT(r == ruby_single_main_ractor);
246
247 rb_gc_ractor_cache_free(r->newobj_cache);
248 r->newobj_cache = NULL;
249 }
250
251 ractor_sync_free(r);
252 ruby_xfree(r);
253}
254
255static size_t
256ractor_memsize(const void *ptr)
257{
258 rb_ractor_t *r = (rb_ractor_t *)ptr;
259
260 // TODO: more correct?
261 return sizeof(rb_ractor_t) + ractor_sync_memsize(r);
262}
263
264static const rb_data_type_t ractor_data_type = {
265 "ractor",
266 {
267 ractor_mark,
268 ractor_free,
269 ractor_memsize,
270 NULL, // update
271 },
272 0, 0, RUBY_TYPED_FREE_IMMEDIATELY /* | RUBY_TYPED_WB_PROTECTED */
273};
274
275bool
276rb_ractor_p(VALUE gv)
277{
278 if (rb_typeddata_is_kind_of(gv, &ractor_data_type)) {
279 return true;
280 }
281 else {
282 return false;
283 }
284}
285
286static inline rb_ractor_t *
287RACTOR_PTR(VALUE self)
288{
289 VM_ASSERT(rb_ractor_p(self));
290 rb_ractor_t *r = DATA_PTR(self);
291 return r;
292}
293
294static rb_atomic_t ractor_last_id;
295
296#if RACTOR_CHECK_MODE > 0
297uint32_t
298rb_ractor_current_id(void)
299{
300 if (GET_THREAD()->ractor == NULL) {
301 return 1; // main ractor
302 }
303 else {
304 return rb_ractor_id(GET_RACTOR());
305 }
306}
307#endif
308
309#include "ractor_sync.c"
310
311// creation/termination
312
313static uint32_t
314ractor_next_id(void)
315{
316 uint32_t id;
317
318 id = (uint32_t)(RUBY_ATOMIC_FETCH_ADD(ractor_last_id, 1) + 1);
319
320 return id;
321}
322
323static void
324vm_insert_ractor0(rb_vm_t *vm, rb_ractor_t *r, bool single_ractor_mode)
325{
326 RUBY_DEBUG_LOG("r:%u ractor.cnt:%u++", r->pub.id, vm->ractor.cnt);
327 VM_ASSERT(single_ractor_mode || RB_VM_LOCKED_P());
328
329 ccan_list_add_tail(&vm->ractor.set, &r->vmlr_node);
330 vm->ractor.cnt++;
331
332 if (r->newobj_cache) {
333 VM_ASSERT(r == ruby_single_main_ractor);
334 }
335 else {
336 r->newobj_cache = rb_gc_ractor_cache_alloc(r);
337 }
338}
339
340static void
341cancel_single_ractor_mode(void)
342{
343 // enable multi-ractor mode
344 RUBY_DEBUG_LOG("enable multi-ractor mode");
345
346 ruby_single_main_ractor = NULL;
347 rb_funcall(rb_cRactor, rb_intern("_activated"), 0);
348}
349
350static void
351vm_insert_ractor(rb_vm_t *vm, rb_ractor_t *r)
352{
353 VM_ASSERT(ractor_status_p(r, ractor_created));
354
355 if (rb_multi_ractor_p()) {
356 RB_VM_LOCK();
357 {
358 vm_insert_ractor0(vm, r, false);
359 vm_ractor_blocking_cnt_inc(vm, r, __FILE__, __LINE__);
360 }
361 RB_VM_UNLOCK();
362 }
363 else {
364 if (vm->ractor.cnt == 0) {
365 // main ractor
366 vm_insert_ractor0(vm, r, true);
367 ractor_status_set(r, ractor_blocking);
368 ractor_status_set(r, ractor_running);
369 }
370 else {
371 cancel_single_ractor_mode();
372 vm_insert_ractor0(vm, r, true);
373 vm_ractor_blocking_cnt_inc(vm, r, __FILE__, __LINE__);
374 }
375 }
376}
377
378static void
379vm_remove_ractor(rb_vm_t *vm, rb_ractor_t *cr)
380{
381 VM_ASSERT(ractor_status_p(cr, ractor_running));
382 VM_ASSERT(vm->ractor.cnt > 1);
383 VM_ASSERT(cr->threads.cnt == 1);
384
385 RB_VM_LOCK();
386 {
387 RUBY_DEBUG_LOG("ractor.cnt:%u-- terminate_waiting:%d",
388 vm->ractor.cnt, vm->ractor.sync.terminate_waiting);
389
390 VM_ASSERT(vm->ractor.cnt > 0);
391 ccan_list_del(&cr->vmlr_node);
392
393 if (vm->ractor.cnt <= 2 && vm->ractor.sync.terminate_waiting) {
394 rb_native_cond_signal(&vm->ractor.sync.terminate_cond);
395 }
396 vm->ractor.cnt--;
397
398 rb_gc_ractor_cache_free(cr->newobj_cache);
399 cr->newobj_cache = NULL;
400
401 ractor_status_set(cr, ractor_terminated);
402 }
403 RB_VM_UNLOCK();
404}
405
406static VALUE
407ractor_alloc(VALUE klass)
408{
409 rb_ractor_t *r;
410 VALUE rv = TypedData_Make_Struct(klass, rb_ractor_t, &ractor_data_type, r);
412 r->pub.self = rv;
413 VM_ASSERT(ractor_status_p(r, ractor_created));
414 return rv;
415}
416
418rb_ractor_main_alloc(void)
419{
420 rb_ractor_t *r = ruby_mimcalloc(1, sizeof(rb_ractor_t));
421 if (r == NULL) {
422 fprintf(stderr, "[FATAL] failed to allocate memory for main ractor\n");
423 exit(EXIT_FAILURE);
424 }
425 r->pub.id = ++ractor_last_id;
426 r->loc = Qnil;
427 r->name = Qnil;
428 r->pub.self = Qnil;
429 r->newobj_cache = rb_gc_ractor_cache_alloc(r);
430 ruby_single_main_ractor = r;
431
432 return r;
433}
434
435#if defined(HAVE_WORKING_FORK)
436// Set up the main Ractor for the VM after fork.
437// Puts us in "single Ractor mode"
438void
439rb_ractor_atfork(rb_vm_t *vm, rb_thread_t *th)
440{
441 // initialize as a main ractor
442 vm->ractor.cnt = 0;
443 vm->ractor.blocking_cnt = 0;
444 ruby_single_main_ractor = th->ractor;
445 th->ractor->status_ = ractor_created;
446
447 rb_ractor_living_threads_init(th->ractor);
448 rb_ractor_living_threads_insert(th->ractor, th);
449
450 VM_ASSERT(vm->ractor.blocking_cnt == 0);
451 VM_ASSERT(vm->ractor.cnt == 1);
452}
453
454void
455rb_ractor_terminate_atfork(rb_vm_t *vm, rb_ractor_t *r)
456{
457 rb_gc_ractor_cache_free(r->newobj_cache);
458 r->newobj_cache = NULL;
459 r->status_ = ractor_terminated;
460 ractor_sync_terminate_atfork(vm, r);
461}
462#endif
463
464void rb_thread_sched_init(struct rb_thread_sched *, bool atfork);
465
466void
467rb_ractor_living_threads_init(rb_ractor_t *r)
468{
469 ccan_list_head_init(&r->threads.set);
470 r->threads.cnt = 0;
471 r->threads.blocking_cnt = 0;
472}
473
474static void
475ractor_init(rb_ractor_t *r, VALUE name, VALUE loc)
476{
477 ractor_sync_init(r);
478
479 // thread management
480 rb_thread_sched_init(&r->threads.sched, false);
481 rb_ractor_living_threads_init(r);
482
483 // naming
484 if (!NIL_P(name)) {
485 rb_encoding *enc;
486 StringValueCStr(name);
487 enc = rb_enc_get(name);
488 if (!rb_enc_asciicompat(enc)) {
489 rb_raise(rb_eArgError, "ASCII incompatible encoding (%s)",
490 rb_enc_name(enc));
491 }
492 name = rb_str_new_frozen(name);
493 }
494 r->name = name;
495 r->loc = loc;
496}
497
498void
499rb_ractor_main_setup(rb_vm_t *vm, rb_ractor_t *r, rb_thread_t *th)
500{
501 VALUE rv = r->pub.self = TypedData_Wrap_Struct(rb_cRactor, &ractor_data_type, r);
502 FL_SET_RAW(r->pub.self, RUBY_FL_SHAREABLE);
503 ractor_init(r, Qnil, Qnil);
504 r->threads.main = th;
505 rb_ractor_living_threads_insert(r, th);
506
507 RB_GC_GUARD(rv);
508}
509
510static VALUE
511ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VALUE args, VALUE block)
512{
513 VALUE rv = ractor_alloc(self);
514 rb_ractor_t *r = RACTOR_PTR(rv);
515 ractor_init(r, name, loc);
516
517 // can block here
518 r->pub.id = ractor_next_id();
519 RUBY_DEBUG_LOG("r:%u", r->pub.id);
520
521 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
522 r->verbose = cr->verbose;
523 r->debug = cr->debug;
524
525 rb_yjit_before_ractor_spawn();
526 rb_zjit_before_ractor_spawn();
527 rb_thread_create_ractor(r, args, block);
528
529 RB_GC_GUARD(rv);
530 return rv;
531}
532
533#if 0
534static VALUE
535ractor_create_func(VALUE klass, VALUE loc, VALUE name, VALUE args, rb_block_call_func_t func)
536{
537 VALUE block = rb_proc_new(func, Qnil);
538 return ractor_create(rb_current_ec_noinline(), klass, loc, name, args, block);
539}
540#endif
541
542static void
543ractor_atexit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE result, bool exc)
544{
545 ractor_notify_exit(ec, cr, result, exc);
546}
547
548void
549rb_ractor_atexit(rb_execution_context_t *ec, VALUE result)
550{
551 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
552 ractor_atexit(ec, cr, result, false);
553}
554
555void
556rb_ractor_atexit_exception(rb_execution_context_t *ec)
557{
558 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
559 ractor_atexit(ec, cr, ec->errinfo, true);
560}
561
562void
563rb_ractor_teardown(rb_execution_context_t *ec)
564{
565 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
566
567 // sync with rb_ractor_terminate_interrupt_main_thread()
568 RB_VM_LOCKING() {
569 VM_ASSERT(cr->threads.main != NULL);
570 cr->threads.main = NULL;
571 }
572}
573
574void
575rb_ractor_receive_parameters(rb_execution_context_t *ec, rb_ractor_t *r, int len, VALUE *ptr)
576{
577 for (int i=0; i<len; i++) {
578 ptr[i] = ractor_receive(ec, ractor_default_port(r));
579 }
580}
581
582void
583rb_ractor_send_parameters(rb_execution_context_t *ec, rb_ractor_t *r, VALUE args)
584{
585 int len = RARRAY_LENINT(args);
586 for (int i=0; i<len; i++) {
587 ractor_send(ec, ractor_default_port(r), RARRAY_AREF(args, i), false);
588 }
589}
590
591bool
592rb_ractor_main_p_(void)
593{
594 VM_ASSERT(rb_multi_ractor_p());
595 rb_execution_context_t *ec = GET_EC();
596 return rb_ec_ractor_ptr(ec) == rb_ec_vm_ptr(ec)->ractor.main_ractor;
597}
598
599int
600rb_ractor_living_thread_num(const rb_ractor_t *r)
601{
602 return r->threads.cnt;
603}
604
605// only for current ractor
606VALUE
607rb_ractor_thread_list(void)
608{
609 rb_ractor_t *r = GET_RACTOR();
610 rb_thread_t *th = 0;
611 VALUE ary = rb_ary_new();
612
613 ccan_list_for_each(&r->threads.set, th, lt_node) {
614 switch (th->status) {
615 case THREAD_RUNNABLE:
616 case THREAD_STOPPED:
617 case THREAD_STOPPED_FOREVER:
618 rb_ary_push(ary, th->self);
619 default:
620 break;
621 }
622 }
623
624 return ary;
625}
626
627void
628rb_ractor_living_threads_insert(rb_ractor_t *r, rb_thread_t *th)
629{
630 VM_ASSERT(th != NULL);
631
632 RACTOR_LOCK(r);
633 {
634 RUBY_DEBUG_LOG("r(%d)->threads.cnt:%d++", r->pub.id, r->threads.cnt);
635 ccan_list_add_tail(&r->threads.set, &th->lt_node);
636 r->threads.cnt++;
637 }
638 RACTOR_UNLOCK(r);
639
640 // first thread for a ractor
641 if (r->threads.cnt == 1) {
642 VM_ASSERT(ractor_status_p(r, ractor_created));
643 vm_insert_ractor(th->vm, r);
644 }
645}
646
647static void
648vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line)
649{
650 ractor_status_set(r, ractor_blocking);
651
652 RUBY_DEBUG_LOG2(file, line, "vm->ractor.blocking_cnt:%d++", vm->ractor.blocking_cnt);
653 vm->ractor.blocking_cnt++;
654 VM_ASSERT(vm->ractor.blocking_cnt <= vm->ractor.cnt);
655}
656
657void
658rb_vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
659{
660 ASSERT_vm_locking();
661 VM_ASSERT(GET_RACTOR() == cr);
662 vm_ractor_blocking_cnt_inc(vm, cr, file, line);
663}
664
665void
666rb_vm_ractor_blocking_cnt_dec(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
667{
668 ASSERT_vm_locking();
669 VM_ASSERT(GET_RACTOR() == cr);
670
671 RUBY_DEBUG_LOG2(file, line, "vm->ractor.blocking_cnt:%d--", vm->ractor.blocking_cnt);
672 VM_ASSERT(vm->ractor.blocking_cnt > 0);
673 vm->ractor.blocking_cnt--;
674
675 ractor_status_set(cr, ractor_running);
676}
677
678static void
679ractor_check_blocking(rb_ractor_t *cr, unsigned int remained_thread_cnt, const char *file, int line)
680{
681 VM_ASSERT(cr == GET_RACTOR());
682
683 RUBY_DEBUG_LOG2(file, line,
684 "cr->threads.cnt:%u cr->threads.blocking_cnt:%u vm->ractor.cnt:%u vm->ractor.blocking_cnt:%u",
685 cr->threads.cnt, cr->threads.blocking_cnt,
686 GET_VM()->ractor.cnt, GET_VM()->ractor.blocking_cnt);
687
688 VM_ASSERT(cr->threads.cnt >= cr->threads.blocking_cnt + 1);
689
690 if (remained_thread_cnt > 0 &&
691 // will be block
692 cr->threads.cnt == cr->threads.blocking_cnt + 1) {
693 // change ractor status: running -> blocking
694 rb_vm_t *vm = GET_VM();
695
696 RB_VM_LOCKING() {
697 rb_vm_ractor_blocking_cnt_inc(vm, cr, file, line);
698 }
699 }
700}
701
702void rb_threadptr_remove(rb_thread_t *th);
703
704void
705rb_ractor_living_threads_remove(rb_ractor_t *cr, rb_thread_t *th)
706{
707 VM_ASSERT(cr == GET_RACTOR());
708 RUBY_DEBUG_LOG("r->threads.cnt:%d--", cr->threads.cnt);
709 ractor_check_blocking(cr, cr->threads.cnt - 1, __FILE__, __LINE__);
710
711 rb_threadptr_remove(th);
712
713 if (cr->threads.cnt == 1) {
714 vm_remove_ractor(th->vm, cr);
715 }
716 else {
717 RACTOR_LOCK(cr);
718 {
719 ccan_list_del(&th->lt_node);
720 cr->threads.cnt--;
721 }
722 RACTOR_UNLOCK(cr);
723 }
724}
725
726void
727rb_ractor_blocking_threads_inc(rb_ractor_t *cr, const char *file, int line)
728{
729 RUBY_DEBUG_LOG2(file, line, "cr->threads.blocking_cnt:%d++", cr->threads.blocking_cnt);
730
731 VM_ASSERT(cr->threads.cnt > 0);
732 VM_ASSERT(cr == GET_RACTOR());
733
734 ractor_check_blocking(cr, cr->threads.cnt, __FILE__, __LINE__);
735 cr->threads.blocking_cnt++;
736}
737
738void
739rb_ractor_blocking_threads_dec(rb_ractor_t *cr, const char *file, int line)
740{
741 RUBY_DEBUG_LOG2(file, line,
742 "r->threads.blocking_cnt:%d--, r->threads.cnt:%u",
743 cr->threads.blocking_cnt, cr->threads.cnt);
744
745 VM_ASSERT(cr == GET_RACTOR());
746
747 if (cr->threads.cnt == cr->threads.blocking_cnt) {
748 rb_vm_t *vm = GET_VM();
749
750 RB_VM_LOCKING() {
751 rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__);
752 }
753 }
754
755 cr->threads.blocking_cnt--;
756}
757
758void
759rb_ractor_vm_barrier_interrupt_running_thread(rb_ractor_t *r)
760{
761 VM_ASSERT(r != GET_RACTOR());
762 ASSERT_ractor_unlocking(r);
763 ASSERT_vm_locking();
764
765 RACTOR_LOCK(r);
766 {
767 if (ractor_status_p(r, ractor_running)) {
768 rb_execution_context_t *ec = r->threads.running_ec;
769 if (ec) {
770 RUBY_VM_SET_VM_BARRIER_INTERRUPT(ec);
771 }
772 }
773 }
774 RACTOR_UNLOCK(r);
775}
776
777void
778rb_ractor_terminate_interrupt_main_thread(rb_ractor_t *r)
779{
780 VM_ASSERT(r != GET_RACTOR());
781 ASSERT_ractor_unlocking(r);
782 ASSERT_vm_locking();
783
784 rb_thread_t *main_th = r->threads.main;
785 if (main_th) {
786 if (main_th->status != THREAD_KILLED) {
787 RUBY_VM_SET_TERMINATE_INTERRUPT(main_th->ec);
788 rb_threadptr_interrupt(main_th);
789 }
790 else {
791 RUBY_DEBUG_LOG("killed (%p)", (void *)main_th);
792 }
793 }
794}
795
796void rb_thread_terminate_all(rb_thread_t *th); // thread.c
797
798static void
799ractor_terminal_interrupt_all(rb_vm_t *vm)
800{
801 if (vm->ractor.cnt > 1) {
802 // send terminate notification to all ractors
803 rb_ractor_t *r = 0;
804 ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
805 if (r != vm->ractor.main_ractor) {
806 RUBY_DEBUG_LOG("r:%d", rb_ractor_id(r));
807 rb_ractor_terminate_interrupt_main_thread(r);
808 }
809 }
810 }
811}
812
813void rb_add_running_thread(rb_thread_t *th);
814void rb_del_running_thread(rb_thread_t *th);
815
816void
817rb_ractor_terminate_all(void)
818{
819 rb_vm_t *vm = GET_VM();
820 rb_ractor_t *cr = vm->ractor.main_ractor;
821
822 RUBY_DEBUG_LOG("ractor.cnt:%d", (int)vm->ractor.cnt);
823
824 VM_ASSERT(cr == GET_RACTOR()); // only main-ractor's main-thread should kick it.
825
826 if (vm->ractor.cnt > 1) {
827 RB_VM_LOCK();
828 {
829 ractor_terminal_interrupt_all(vm); // kill all ractors
830 }
831 RB_VM_UNLOCK();
832 }
833 rb_thread_terminate_all(GET_THREAD()); // kill other threads in main-ractor and wait
834
835 RB_VM_LOCK();
836 {
837 while (vm->ractor.cnt > 1) {
838 RUBY_DEBUG_LOG("terminate_waiting:%d", vm->ractor.sync.terminate_waiting);
839 vm->ractor.sync.terminate_waiting = true;
840
841 // wait for 1sec
842 rb_vm_ractor_blocking_cnt_inc(vm, cr, __FILE__, __LINE__);
843 rb_del_running_thread(rb_ec_thread_ptr(cr->threads.running_ec));
844 rb_vm_cond_timedwait(vm, &vm->ractor.sync.terminate_cond, 1000 /* ms */);
845 rb_add_running_thread(rb_ec_thread_ptr(cr->threads.running_ec));
846 rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__);
847
848 ractor_terminal_interrupt_all(vm);
849 }
850 }
851 RB_VM_UNLOCK();
852}
853
855rb_vm_main_ractor_ec(rb_vm_t *vm)
856{
857 /* This code needs to carefully work around two bugs:
858 * - Bug #20016: When M:N threading is enabled, running_ec is NULL if no thread is
859 * actually currently running (as opposed to without M:N threading, when
860 * running_ec will still point to the _last_ thread which ran)
861 * - Bug #20197: If the main thread is sleeping, setting its postponed job
862 * interrupt flag is pointless; it won't look at the flag until it stops sleeping
863 * for some reason. It would be better to set the flag on the running ec, which
864 * will presumably look at it soon.
865 *
866 * Solution: use running_ec if it's set, otherwise fall back to the main thread ec.
867 * This is still susceptible to some rare race conditions (what if the last thread
868 * to run just entered a long-running sleep?), but seems like the best balance of
869 * robustness and complexity.
870 */
871 rb_execution_context_t *running_ec = vm->ractor.main_ractor->threads.running_ec;
872 if (running_ec) { return running_ec; }
873 return vm->ractor.main_thread->ec;
874}
875
876static VALUE
877ractor_moved_missing(int argc, VALUE *argv, VALUE self)
878{
879 rb_raise(rb_eRactorMovedError, "can not send any methods to a moved object");
880}
881
882/*
883 * Document-class: Ractor::Error
884 *
885 * The parent class of Ractor-related error classes.
886 */
887
888/*
889 * Document-class: Ractor::ClosedError
890 *
891 * Raised when an attempt is made to send a message to a closed port,
892 * or to retrieve a message from a closed and empty port.
893 * Ports may be closed explicitly with Ractor#close_outgoing/close_incoming
894 * and are closed implicitly when a Ractor terminates.
895 *
896 * r = Ractor.new { sleep(500) }
897 * r.close_outgoing
898 * r.take # Ractor::ClosedError
899 *
900 * ClosedError is a descendant of StopIteration, so the closing of the ractor will break
901 * the loops without propagating the error:
902 *
903 * r = Ractor.new do
904 * loop do
905 * msg = receive # raises ClosedError and loop traps it
906 * puts "Received: #{msg}"
907 * end
908 * puts "loop exited"
909 * end
910 *
911 * 3.times{|i| r << i}
912 * r.close_incoming
913 * r.take
914 * puts "Continue successfully"
915 *
916 * This will print:
917 *
918 * Received: 0
919 * Received: 1
920 * Received: 2
921 * loop exited
922 * Continue successfully
923 */
924
925/*
926 * Document-class: Ractor::IsolationError
927 *
928 * Raised on attempt to make a Ractor-unshareable object
929 * Ractor-shareable.
930 */
931
932/*
933 * Document-class: Ractor::RemoteError
934 *
935 * Raised on attempt to Ractor#take if there was an uncaught exception in the Ractor.
936 * Its +cause+ will contain the original exception, and +ractor+ is the original ractor
937 * it was raised in.
938 *
939 * r = Ractor.new { raise "Something weird happened" }
940 *
941 * begin
942 * r.take
943 * rescue => e
944 * p e # => #<Ractor::RemoteError: thrown by remote Ractor.>
945 * p e.ractor == r # => true
946 * p e.cause # => #<RuntimeError: Something weird happened>
947 * end
948 *
949 */
950
951/*
952 * Document-class: Ractor::MovedError
953 *
954 * Raised on an attempt to access an object which was moved in Ractor#send or Ractor.yield.
955 *
956 * r = Ractor.new { sleep }
957 *
958 * ary = [1, 2, 3]
959 * r.send(ary, move: true)
960 * ary.inspect
961 * # Ractor::MovedError (can not send any methods to a moved object)
962 *
963 */
964
965/*
966 * Document-class: Ractor::MovedObject
967 *
968 * A special object which replaces any value that was moved to another ractor in Ractor#send
969 * or Ractor.yield. Any attempt to access the object results in Ractor::MovedError.
970 *
971 * r = Ractor.new { receive }
972 *
973 * ary = [1, 2, 3]
974 * r.send(ary, move: true)
975 * p Ractor::MovedObject === ary
976 * # => true
977 * ary.inspect
978 * # Ractor::MovedError (can not send any methods to a moved object)
979 */
980
981/*
982 * Document-class: Ractor::UnsafeError
983 *
984 * Raised when Ractor-unsafe C-methods is invoked by a non-main Ractor.
985 */
986
987// Main docs are in ractor.rb, but without this clause there are weird artifacts
988// in their rendering.
989/*
990 * Document-class: Ractor
991 *
992 */
993
994void
995Init_Ractor(void)
996{
997 rb_cRactor = rb_define_class("Ractor", rb_cObject);
999
1000 rb_eRactorError = rb_define_class_under(rb_cRactor, "Error", rb_eRuntimeError);
1001 rb_eRactorIsolationError = rb_define_class_under(rb_cRactor, "IsolationError", rb_eRactorError);
1002 rb_eRactorRemoteError = rb_define_class_under(rb_cRactor, "RemoteError", rb_eRactorError);
1003 rb_eRactorMovedError = rb_define_class_under(rb_cRactor, "MovedError", rb_eRactorError);
1004 rb_eRactorClosedError = rb_define_class_under(rb_cRactor, "ClosedError", rb_eStopIteration);
1005 rb_eRactorUnsafeError = rb_define_class_under(rb_cRactor, "UnsafeError", rb_eRactorError);
1006
1007 rb_cRactorMovedObject = rb_define_class_under(rb_cRactor, "MovedObject", rb_cBasicObject);
1008 rb_undef_alloc_func(rb_cRactorMovedObject);
1009 rb_define_method(rb_cRactorMovedObject, "method_missing", ractor_moved_missing, -1);
1010
1011 // override methods defined in BasicObject
1012 rb_define_method(rb_cRactorMovedObject, "__send__", ractor_moved_missing, -1);
1013 rb_define_method(rb_cRactorMovedObject, "!", ractor_moved_missing, -1);
1014 rb_define_method(rb_cRactorMovedObject, "==", ractor_moved_missing, -1);
1015 rb_define_method(rb_cRactorMovedObject, "!=", ractor_moved_missing, -1);
1016 rb_define_method(rb_cRactorMovedObject, "__id__", ractor_moved_missing, -1);
1017 rb_define_method(rb_cRactorMovedObject, "equal?", ractor_moved_missing, -1);
1018 rb_define_method(rb_cRactorMovedObject, "instance_eval", ractor_moved_missing, -1);
1019 rb_define_method(rb_cRactorMovedObject, "instance_exec", ractor_moved_missing, -1);
1020
1021 Init_RactorPort();
1022}
1023
1024void
1025rb_ractor_dump(void)
1026{
1027 rb_vm_t *vm = GET_VM();
1028 rb_ractor_t *r = 0;
1029
1030 ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
1031 if (r != vm->ractor.main_ractor) {
1032 fprintf(stderr, "r:%u (%s)\n", r->pub.id, ractor_status_str(r->status_));
1033 }
1034 }
1035}
1036
1037VALUE
1039{
1040 if (rb_ractor_main_p()) {
1041 return rb_stdin;
1042 }
1043 else {
1044 rb_ractor_t *cr = GET_RACTOR();
1045 return cr->r_stdin;
1046 }
1047}
1048
1049VALUE
1050rb_ractor_stdout(void)
1051{
1052 if (rb_ractor_main_p()) {
1053 return rb_stdout;
1054 }
1055 else {
1056 rb_ractor_t *cr = GET_RACTOR();
1057 return cr->r_stdout;
1058 }
1059}
1060
1061VALUE
1062rb_ractor_stderr(void)
1063{
1064 if (rb_ractor_main_p()) {
1065 return rb_stderr;
1066 }
1067 else {
1068 rb_ractor_t *cr = GET_RACTOR();
1069 return cr->r_stderr;
1070 }
1071}
1072
1073void
1075{
1076 if (rb_ractor_main_p()) {
1077 rb_stdin = in;
1078 }
1079 else {
1080 rb_ractor_t *cr = GET_RACTOR();
1081 RB_OBJ_WRITE(cr->pub.self, &cr->r_stdin, in);
1082 }
1083}
1084
1085void
1087{
1088 if (rb_ractor_main_p()) {
1089 rb_stdout = out;
1090 }
1091 else {
1092 rb_ractor_t *cr = GET_RACTOR();
1093 RB_OBJ_WRITE(cr->pub.self, &cr->r_stdout, out);
1094 }
1095}
1096
1097void
1099{
1100 if (rb_ractor_main_p()) {
1101 rb_stderr = err;
1102 }
1103 else {
1104 rb_ractor_t *cr = GET_RACTOR();
1105 RB_OBJ_WRITE(cr->pub.self, &cr->r_stderr, err);
1106 }
1107}
1108
1110rb_ractor_hooks(rb_ractor_t *cr)
1111{
1112 return &cr->pub.hooks;
1113}
1114
1116
1117// 2: stop search
1118// 1: skip child
1119// 0: continue
1120
1121enum obj_traverse_iterator_result {
1122 traverse_cont,
1123 traverse_skip,
1124 traverse_stop,
1125};
1126
1127typedef enum obj_traverse_iterator_result (*rb_obj_traverse_enter_func)(VALUE obj);
1128typedef enum obj_traverse_iterator_result (*rb_obj_traverse_leave_func)(VALUE obj);
1129typedef enum obj_traverse_iterator_result (*rb_obj_traverse_final_func)(VALUE obj);
1130
1131static enum obj_traverse_iterator_result null_leave(VALUE obj);
1132
1134 rb_obj_traverse_enter_func enter_func;
1135 rb_obj_traverse_leave_func leave_func;
1136
1137 st_table *rec;
1138 VALUE rec_hash;
1139};
1140
1141
1143 bool stop;
1144 struct obj_traverse_data *data;
1145};
1146
1147static int obj_traverse_i(VALUE obj, struct obj_traverse_data *data);
1148
1149static int
1150obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
1151{
1153
1154 if (obj_traverse_i(key, d->data)) {
1155 d->stop = true;
1156 return ST_STOP;
1157 }
1158
1159 if (obj_traverse_i(val, d->data)) {
1160 d->stop = true;
1161 return ST_STOP;
1162 }
1163
1164 return ST_CONTINUE;
1165}
1166
1167static void
1168obj_traverse_reachable_i(VALUE obj, void *ptr)
1169{
1171
1172 if (obj_traverse_i(obj, d->data)) {
1173 d->stop = true;
1174 }
1175}
1176
1177static struct st_table *
1178obj_traverse_rec(struct obj_traverse_data *data)
1179{
1180 if (UNLIKELY(!data->rec)) {
1181 data->rec_hash = rb_ident_hash_new();
1182 data->rec = RHASH_ST_TABLE(data->rec_hash);
1183 }
1184 return data->rec;
1185}
1186
1187static int
1188obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
1189{
1191
1192 if (obj_traverse_i(val, d->data)) {
1193 d->stop = true;
1194 return ST_STOP;
1195 }
1196
1197 return ST_CONTINUE;
1198}
1199
1200static int
1201obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
1202{
1203 if (RB_SPECIAL_CONST_P(obj)) return 0;
1204
1205 switch (data->enter_func(obj)) {
1206 case traverse_cont: break;
1207 case traverse_skip: return 0; // skip children
1208 case traverse_stop: return 1; // stop search
1209 }
1210
1211 if (UNLIKELY(st_insert(obj_traverse_rec(data), obj, 1))) {
1212 // already traversed
1213 return 0;
1214 }
1215 RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj);
1216
1217 struct obj_traverse_callback_data d = {
1218 .stop = false,
1219 .data = data,
1220 };
1221 rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1222 if (d.stop) return 1;
1223
1224 switch (BUILTIN_TYPE(obj)) {
1225 // no child node
1226 case T_STRING:
1227 case T_FLOAT:
1228 case T_BIGNUM:
1229 case T_REGEXP:
1230 case T_FILE:
1231 case T_SYMBOL:
1232 case T_MATCH:
1233 break;
1234
1235 case T_OBJECT:
1236 /* Instance variables already traversed. */
1237 break;
1238
1239 case T_ARRAY:
1240 {
1241 for (int i = 0; i < RARRAY_LENINT(obj); i++) {
1242 VALUE e = rb_ary_entry(obj, i);
1243 if (obj_traverse_i(e, data)) return 1;
1244 }
1245 }
1246 break;
1247
1248 case T_HASH:
1249 {
1250 if (obj_traverse_i(RHASH_IFNONE(obj), data)) return 1;
1251
1252 struct obj_traverse_callback_data d = {
1253 .stop = false,
1254 .data = data,
1255 };
1256 rb_hash_foreach(obj, obj_hash_traverse_i, (VALUE)&d);
1257 if (d.stop) return 1;
1258 }
1259 break;
1260
1261 case T_STRUCT:
1262 {
1263 long len = RSTRUCT_LEN(obj);
1264 const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
1265
1266 for (long i=0; i<len; i++) {
1267 if (obj_traverse_i(ptr[i], data)) return 1;
1268 }
1269 }
1270 break;
1271
1272 case T_RATIONAL:
1273 if (obj_traverse_i(RRATIONAL(obj)->num, data)) return 1;
1274 if (obj_traverse_i(RRATIONAL(obj)->den, data)) return 1;
1275 break;
1276 case T_COMPLEX:
1277 if (obj_traverse_i(RCOMPLEX(obj)->real, data)) return 1;
1278 if (obj_traverse_i(RCOMPLEX(obj)->imag, data)) return 1;
1279 break;
1280
1281 case T_DATA:
1282 case T_IMEMO:
1283 {
1284 struct obj_traverse_callback_data d = {
1285 .stop = false,
1286 .data = data,
1287 };
1288 RB_VM_LOCKING_NO_BARRIER() {
1289 rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d);
1290 }
1291 if (d.stop) return 1;
1292 }
1293 break;
1294
1295 // unreachable
1296 case T_CLASS:
1297 case T_MODULE:
1298 case T_ICLASS:
1299 default:
1300 rp(obj);
1301 rb_bug("unreachable");
1302 }
1303
1304 if (data->leave_func(obj) == traverse_stop) {
1305 return 1;
1306 }
1307 else {
1308 return 0;
1309 }
1310}
1311
1313 rb_obj_traverse_final_func final_func;
1314 int stopped;
1315};
1316
1317static int
1318obj_traverse_final_i(st_data_t key, st_data_t val, st_data_t arg)
1319{
1320 struct rb_obj_traverse_final_data *data = (void *)arg;
1321 if (data->final_func(key)) {
1322 data->stopped = 1;
1323 return ST_STOP;
1324 }
1325 return ST_CONTINUE;
1326}
1327
1328// 0: traverse all
1329// 1: stopped
1330static int
1331rb_obj_traverse(VALUE obj,
1332 rb_obj_traverse_enter_func enter_func,
1333 rb_obj_traverse_leave_func leave_func,
1334 rb_obj_traverse_final_func final_func)
1335{
1336 struct obj_traverse_data data = {
1337 .enter_func = enter_func,
1338 .leave_func = leave_func,
1339 .rec = NULL,
1340 };
1341
1342 if (obj_traverse_i(obj, &data)) return 1;
1343 if (final_func && data.rec) {
1344 struct rb_obj_traverse_final_data f = {final_func, 0};
1345 st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
1346 return f.stopped;
1347 }
1348 return 0;
1349}
1350
1351static int
1352allow_frozen_shareable_p(VALUE obj)
1353{
1354 if (!RB_TYPE_P(obj, T_DATA)) {
1355 return true;
1356 }
1357 else if (RTYPEDDATA_P(obj)) {
1358 const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
1359 if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE) {
1360 return true;
1361 }
1362 }
1363
1364 return false;
1365}
1366
1367static enum obj_traverse_iterator_result
1368make_shareable_check_shareable(VALUE obj)
1369{
1370 VM_ASSERT(!SPECIAL_CONST_P(obj));
1371
1372 if (rb_ractor_shareable_p(obj)) {
1373 return traverse_skip;
1374 }
1375 else if (!allow_frozen_shareable_p(obj)) {
1376 if (rb_obj_is_proc(obj)) {
1377 rb_proc_ractor_make_shareable(obj);
1378 return traverse_cont;
1379 }
1380 else {
1381 rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1382 }
1383 }
1384
1385 switch (TYPE(obj)) {
1386 case T_IMEMO:
1387 return traverse_skip;
1388 case T_OBJECT:
1389 {
1390 // If a T_OBJECT is shared and has no free capacity, we can't safely store the object_id inline,
1391 // as it would require to move the object content into an external buffer.
1392 // This is only a problem for T_OBJECT, given other types have external fields and can do RCU.
1393 // To avoid this issue, we proactively create the object_id.
1394 shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
1395 attr_index_t capacity = RSHAPE_CAPACITY(shape_id);
1396 attr_index_t free_capacity = capacity - RSHAPE_LEN(shape_id);
1397 if (!rb_shape_has_object_id(shape_id) && capacity && !free_capacity) {
1398 rb_obj_id(obj);
1399 }
1400 }
1401 break;
1402 default:
1403 break;
1404 }
1405
1406 if (!RB_OBJ_FROZEN_RAW(obj)) {
1407 rb_funcall(obj, idFreeze, 0);
1408
1409 if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
1410 rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
1411 }
1412
1413 if (RB_OBJ_SHAREABLE_P(obj)) {
1414 return traverse_skip;
1415 }
1416 }
1417
1418 return traverse_cont;
1419}
1420
1421static enum obj_traverse_iterator_result
1422mark_shareable(VALUE obj)
1423{
1425 return traverse_cont;
1426}
1427
1428VALUE
1430{
1431 rb_obj_traverse(obj,
1432 make_shareable_check_shareable,
1433 null_leave, mark_shareable);
1434 return obj;
1435}
1436
1437VALUE
1439{
1440 VALUE copy = ractor_copy(obj);
1441 return rb_ractor_make_shareable(copy);
1442}
1443
1444VALUE
1445rb_ractor_ensure_shareable(VALUE obj, VALUE name)
1446{
1447 if (!rb_ractor_shareable_p(obj)) {
1448 VALUE message = rb_sprintf("cannot assign unshareable object to %"PRIsVALUE,
1449 name);
1450 rb_exc_raise(rb_exc_new_str(rb_eRactorIsolationError, message));
1451 }
1452 return obj;
1453}
1454
1455void
1456rb_ractor_ensure_main_ractor(const char *msg)
1457{
1458 if (!rb_ractor_main_p()) {
1459 rb_raise(rb_eRactorIsolationError, "%s", msg);
1460 }
1461}
1462
1463static enum obj_traverse_iterator_result
1464shareable_p_enter(VALUE obj)
1465{
1466 if (RB_OBJ_SHAREABLE_P(obj)) {
1467 return traverse_skip;
1468 }
1469 else if (RB_TYPE_P(obj, T_CLASS) ||
1470 RB_TYPE_P(obj, T_MODULE) ||
1471 RB_TYPE_P(obj, T_ICLASS)) {
1472 // TODO: remove it
1473 mark_shareable(obj);
1474 return traverse_skip;
1475 }
1476 else if (RB_OBJ_FROZEN_RAW(obj) &&
1477 allow_frozen_shareable_p(obj)) {
1478 return traverse_cont;
1479 }
1480
1481 return traverse_stop; // fail
1482}
1483
1484bool
1485rb_ractor_shareable_p_continue(VALUE obj)
1486{
1487 if (rb_obj_traverse(obj,
1488 shareable_p_enter, null_leave,
1489 mark_shareable)) {
1490 return false;
1491 }
1492 else {
1493 return true;
1494 }
1495}
1496
1497#if RACTOR_CHECK_MODE > 0
1498void
1499rb_ractor_setup_belonging(VALUE obj)
1500{
1501 rb_ractor_setup_belonging_to(obj, rb_ractor_current_id());
1502}
1503
1504static enum obj_traverse_iterator_result
1505reset_belonging_enter(VALUE obj)
1506{
1507 if (rb_ractor_shareable_p(obj)) {
1508 return traverse_skip;
1509 }
1510 else {
1511 rb_ractor_setup_belonging(obj);
1512 return traverse_cont;
1513 }
1514}
1515#endif
1516
1517static enum obj_traverse_iterator_result
1518null_leave(VALUE obj)
1519{
1520 return traverse_cont;
1521}
1522
1523static VALUE
1524ractor_reset_belonging(VALUE obj)
1525{
1526#if RACTOR_CHECK_MODE > 0
1527 rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
1528#endif
1529 return obj;
1530}
1531
1532
1534
1535// 2: stop search
1536// 1: skip child
1537// 0: continue
1538
1540static int obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data);
1541typedef enum obj_traverse_iterator_result (*rb_obj_traverse_replace_enter_func)(VALUE obj, struct obj_traverse_replace_data *data);
1542typedef enum obj_traverse_iterator_result (*rb_obj_traverse_replace_leave_func)(VALUE obj, struct obj_traverse_replace_data *data);
1543
1545 rb_obj_traverse_replace_enter_func enter_func;
1546 rb_obj_traverse_replace_leave_func leave_func;
1547
1548 st_table *rec;
1549 VALUE rec_hash;
1550
1551 VALUE replacement;
1552 bool move;
1553};
1554
1556 bool stop;
1557 VALUE src;
1558 struct obj_traverse_replace_data *data;
1559};
1560
1561static int
1562obj_hash_traverse_replace_foreach_i(st_data_t key, st_data_t value, st_data_t argp, int error)
1563{
1564 return ST_REPLACE;
1565}
1566
1567static int
1568obj_hash_traverse_replace_i(st_data_t *key, st_data_t *val, st_data_t ptr, int exists)
1569{
1571 struct obj_traverse_replace_data *data = d->data;
1572
1573 if (obj_traverse_replace_i(*key, data)) {
1574 d->stop = true;
1575 return ST_STOP;
1576 }
1577 else if (*key != data->replacement) {
1578 VALUE v = *key = data->replacement;
1579 RB_OBJ_WRITTEN(d->src, Qundef, v);
1580 }
1581
1582 if (obj_traverse_replace_i(*val, data)) {
1583 d->stop = true;
1584 return ST_STOP;
1585 }
1586 else if (*val != data->replacement) {
1587 VALUE v = *val = data->replacement;
1588 RB_OBJ_WRITTEN(d->src, Qundef, v);
1589 }
1590
1591 return ST_CONTINUE;
1592}
1593
1594static int
1595obj_iv_hash_traverse_replace_foreach_i(st_data_t _key, st_data_t _val, st_data_t _data, int _x)
1596{
1597 return ST_REPLACE;
1598}
1599
1600static int
1601obj_iv_hash_traverse_replace_i(st_data_t * _key, st_data_t * val, st_data_t ptr, int exists)
1602{
1604 struct obj_traverse_replace_data *data = d->data;
1605
1606 if (obj_traverse_replace_i(*(VALUE *)val, data)) {
1607 d->stop = true;
1608 return ST_STOP;
1609 }
1610 else if (*(VALUE *)val != data->replacement) {
1611 VALUE v = *(VALUE *)val = data->replacement;
1612 RB_OBJ_WRITTEN(d->src, Qundef, v);
1613 }
1614
1615 return ST_CONTINUE;
1616}
1617
1618static struct st_table *
1619obj_traverse_replace_rec(struct obj_traverse_replace_data *data)
1620{
1621 if (UNLIKELY(!data->rec)) {
1622 data->rec_hash = rb_ident_hash_new();
1623 data->rec = RHASH_ST_TABLE(data->rec_hash);
1624 }
1625 return data->rec;
1626}
1627
1628static void
1629obj_refer_only_shareables_p_i(VALUE obj, void *ptr)
1630{
1631 int *pcnt = (int *)ptr;
1632
1633 if (!rb_ractor_shareable_p(obj)) {
1634 ++*pcnt;
1635 }
1636}
1637
1638static int
1639obj_refer_only_shareables_p(VALUE obj)
1640{
1641 int cnt = 0;
1642 RB_VM_LOCKING_NO_BARRIER() {
1643 rb_objspace_reachable_objects_from(obj, obj_refer_only_shareables_p_i, &cnt);
1644 }
1645 return cnt == 0;
1646}
1647
1648static int
1649obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data)
1650{
1651 st_data_t replacement;
1652
1653 if (RB_SPECIAL_CONST_P(obj)) {
1654 data->replacement = obj;
1655 return 0;
1656 }
1657
1658 switch (data->enter_func(obj, data)) {
1659 case traverse_cont: break;
1660 case traverse_skip: return 0; // skip children
1661 case traverse_stop: return 1; // stop search
1662 }
1663
1664 replacement = (st_data_t)data->replacement;
1665
1666 if (UNLIKELY(st_lookup(obj_traverse_replace_rec(data), (st_data_t)obj, &replacement))) {
1667 data->replacement = (VALUE)replacement;
1668 return 0;
1669 }
1670 else {
1671 st_insert(obj_traverse_replace_rec(data), (st_data_t)obj, replacement);
1672 RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj);
1673 RB_OBJ_WRITTEN(data->rec_hash, Qundef, replacement);
1674 }
1675
1676 if (!data->move) {
1677 obj = replacement;
1678 }
1679
1680#define CHECK_AND_REPLACE(parent_obj, v) do { \
1681 VALUE _val = (v); \
1682 if (obj_traverse_replace_i(_val, data)) { return 1; } \
1683 else if (data->replacement != _val) { RB_OBJ_WRITE(parent_obj, &v, data->replacement); } \
1684} while (0)
1685
1686 if (UNLIKELY(rb_obj_exivar_p(obj))) {
1687 VALUE fields_obj = rb_obj_fields_no_ractor_check(obj);
1688
1689 if (UNLIKELY(rb_shape_obj_too_complex_p(obj))) {
1691 .stop = false,
1692 .data = data,
1693 .src = fields_obj,
1694 };
1695 rb_st_foreach_with_replace(
1696 rb_imemo_fields_complex_tbl(fields_obj),
1697 obj_iv_hash_traverse_replace_foreach_i,
1698 obj_iv_hash_traverse_replace_i,
1699 (st_data_t)&d
1700 );
1701 if (d.stop) return 1;
1702 }
1703 else {
1704 uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj));
1705 VALUE *fields = rb_imemo_fields_ptr(fields_obj);
1706 for (uint32_t i = 0; i < fields_count; i++) {
1707 CHECK_AND_REPLACE(fields_obj, fields[i]);
1708 }
1709 }
1710 }
1711
1712 switch (BUILTIN_TYPE(obj)) {
1713 // no child node
1714 case T_FLOAT:
1715 case T_BIGNUM:
1716 case T_REGEXP:
1717 case T_FILE:
1718 case T_SYMBOL:
1719 case T_MATCH:
1720 break;
1721 case T_STRING:
1722 rb_str_make_independent(obj);
1723 break;
1724
1725 case T_OBJECT:
1726 {
1727 if (rb_shape_obj_too_complex_p(obj)) {
1729 .stop = false,
1730 .data = data,
1731 .src = obj,
1732 };
1733 rb_st_foreach_with_replace(
1734 ROBJECT_FIELDS_HASH(obj),
1735 obj_iv_hash_traverse_replace_foreach_i,
1736 obj_iv_hash_traverse_replace_i,
1737 (st_data_t)&d
1738 );
1739 if (d.stop) return 1;
1740 }
1741 else {
1742 uint32_t len = ROBJECT_FIELDS_COUNT(obj);
1743 VALUE *ptr = ROBJECT_FIELDS(obj);
1744
1745 for (uint32_t i = 0; i < len; i++) {
1746 CHECK_AND_REPLACE(obj, ptr[i]);
1747 }
1748 }
1749 }
1750 break;
1751
1752 case T_ARRAY:
1753 {
1754 rb_ary_cancel_sharing(obj);
1755
1756 for (int i = 0; i < RARRAY_LENINT(obj); i++) {
1757 VALUE e = rb_ary_entry(obj, i);
1758
1759 if (obj_traverse_replace_i(e, data)) {
1760 return 1;
1761 }
1762 else if (e != data->replacement) {
1763 RARRAY_ASET(obj, i, data->replacement);
1764 }
1765 }
1766 RB_GC_GUARD(obj);
1767 }
1768 break;
1769 case T_HASH:
1770 {
1772 .stop = false,
1773 .data = data,
1774 .src = obj,
1775 };
1776 rb_hash_stlike_foreach_with_replace(obj,
1777 obj_hash_traverse_replace_foreach_i,
1778 obj_hash_traverse_replace_i,
1779 (VALUE)&d);
1780 if (d.stop) return 1;
1781 // TODO: rehash here?
1782
1783 VALUE ifnone = RHASH_IFNONE(obj);
1784 if (obj_traverse_replace_i(ifnone, data)) {
1785 return 1;
1786 }
1787 else if (ifnone != data->replacement) {
1788 RHASH_SET_IFNONE(obj, data->replacement);
1789 }
1790 }
1791 break;
1792
1793 case T_STRUCT:
1794 {
1795 long len = RSTRUCT_LEN(obj);
1796 const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
1797
1798 for (long i=0; i<len; i++) {
1799 CHECK_AND_REPLACE(obj, ptr[i]);
1800 }
1801 }
1802 break;
1803
1804 case T_RATIONAL:
1805 CHECK_AND_REPLACE(obj, RRATIONAL(obj)->num);
1806 CHECK_AND_REPLACE(obj, RRATIONAL(obj)->den);
1807 break;
1808 case T_COMPLEX:
1809 CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->real);
1810 CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->imag);
1811 break;
1812
1813 case T_DATA:
1814 if (!data->move && obj_refer_only_shareables_p(obj)) {
1815 break;
1816 }
1817 else {
1818 rb_raise(rb_eRactorError, "can not %s %"PRIsVALUE" object.",
1819 data->move ? "move" : "copy", rb_class_of(obj));
1820 }
1821
1822 case T_IMEMO:
1823 // not supported yet
1824 return 1;
1825
1826 // unreachable
1827 case T_CLASS:
1828 case T_MODULE:
1829 case T_ICLASS:
1830 default:
1831 rp(obj);
1832 rb_bug("unreachable");
1833 }
1834
1835 data->replacement = (VALUE)replacement;
1836
1837 if (data->leave_func(obj, data) == traverse_stop) {
1838 return 1;
1839 }
1840 else {
1841 return 0;
1842 }
1843}
1844
1845// 0: traverse all
1846// 1: stopped
1847static VALUE
1848rb_obj_traverse_replace(VALUE obj,
1849 rb_obj_traverse_replace_enter_func enter_func,
1850 rb_obj_traverse_replace_leave_func leave_func,
1851 bool move)
1852{
1853 struct obj_traverse_replace_data data = {
1854 .enter_func = enter_func,
1855 .leave_func = leave_func,
1856 .rec = NULL,
1857 .replacement = Qundef,
1858 .move = move,
1859 };
1860
1861 if (obj_traverse_replace_i(obj, &data)) {
1862 return Qundef;
1863 }
1864 else {
1865 return data.replacement;
1866 }
1867}
1868
1869static const bool wb_protected_types[RUBY_T_MASK] = {
1880};
1881
1882static enum obj_traverse_iterator_result
1883move_enter(VALUE obj, struct obj_traverse_replace_data *data)
1884{
1885 if (rb_ractor_shareable_p(obj)) {
1886 data->replacement = obj;
1887 return traverse_skip;
1888 }
1889 else {
1890 VALUE type = RB_BUILTIN_TYPE(obj);
1891 type |= wb_protected_types[type] ? FL_WB_PROTECTED : 0;
1892 NEWOBJ_OF(moved, struct RBasic, 0, type, rb_gc_obj_slot_size(obj), 0);
1893 data->replacement = (VALUE)moved;
1894 return traverse_cont;
1895 }
1896}
1897
1898static enum obj_traverse_iterator_result
1899move_leave(VALUE obj, struct obj_traverse_replace_data *data)
1900{
1901 // Copy flags
1902 VALUE ignored_flags = RUBY_FL_PROMOTED;
1903 RBASIC(data->replacement)->flags = (RBASIC(obj)->flags & ~ignored_flags) | (RBASIC(data->replacement)->flags & ignored_flags);
1904 // Copy contents without the flags
1905 memcpy(
1906 (char *)data->replacement + sizeof(VALUE),
1907 (char *)obj + sizeof(VALUE),
1908 rb_gc_obj_slot_size(obj) - sizeof(VALUE)
1909 );
1910
1911 // We've copied obj's references to the replacement
1912 rb_gc_writebarrier_remember(data->replacement);
1913
1914 void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c
1915
1916 rb_gc_obj_id_moved(data->replacement);
1917
1918 if (UNLIKELY(rb_obj_exivar_p(obj))) {
1919 rb_replace_generic_ivar(data->replacement, obj);
1920 }
1921
1922 VALUE flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_PROMOTED);
1923
1924 // Avoid mutations using bind_call, etc.
1925 MEMZERO((char *)obj, char, sizeof(struct RBasic));
1926 RBASIC(obj)->flags = flags;
1927 RBASIC_SET_CLASS_RAW(obj, rb_cRactorMovedObject);
1928 return traverse_cont;
1929}
1930
1931static VALUE
1932ractor_move(VALUE obj)
1933{
1934 VALUE val = rb_obj_traverse_replace(obj, move_enter, move_leave, true);
1935 if (!UNDEF_P(val)) {
1936 return val;
1937 }
1938 else {
1939 rb_raise(rb_eRactorError, "can not move the object");
1940 }
1941}
1942
1943static enum obj_traverse_iterator_result
1944copy_enter(VALUE obj, struct obj_traverse_replace_data *data)
1945{
1946 if (rb_ractor_shareable_p(obj)) {
1947 data->replacement = obj;
1948 return traverse_skip;
1949 }
1950 else {
1951 data->replacement = rb_obj_clone(obj);
1952 return traverse_cont;
1953 }
1954}
1955
1956static enum obj_traverse_iterator_result
1957copy_leave(VALUE obj, struct obj_traverse_replace_data *data)
1958{
1959 return traverse_cont;
1960}
1961
1962static VALUE
1963ractor_copy(VALUE obj)
1964{
1965 VALUE val = rb_obj_traverse_replace(obj, copy_enter, copy_leave, false);
1966 if (!UNDEF_P(val)) {
1967 return val;
1968 }
1969 else {
1970 rb_raise(rb_eRactorError, "can not copy the object");
1971 }
1972}
1973
1974// Ractor local storage
1975
1977 const struct rb_ractor_local_storage_type *type;
1978 void *main_cache;
1979};
1980
1982 int cnt;
1983 int capa;
1985} freed_ractor_local_keys;
1986
1987static int
1988ractor_local_storage_mark_i(st_data_t key, st_data_t val, st_data_t dmy)
1989{
1991 if (k->type->mark) (*k->type->mark)((void *)val);
1992 return ST_CONTINUE;
1993}
1994
1995static enum rb_id_table_iterator_result
1996idkey_local_storage_mark_i(VALUE val, void *dmy)
1997{
1998 rb_gc_mark(val);
1999 return ID_TABLE_CONTINUE;
2000}
2001
2002static void
2003ractor_local_storage_mark(rb_ractor_t *r)
2004{
2005 if (r->local_storage) {
2006 st_foreach(r->local_storage, ractor_local_storage_mark_i, 0);
2007
2008 for (int i=0; i<freed_ractor_local_keys.cnt; i++) {
2009 rb_ractor_local_key_t key = freed_ractor_local_keys.keys[i];
2010 st_data_t val, k = (st_data_t)key;
2011 if (st_delete(r->local_storage, &k, &val) &&
2012 (key = (rb_ractor_local_key_t)k)->type->free) {
2013 (*key->type->free)((void *)val);
2014 }
2015 }
2016 }
2017
2018 if (r->idkey_local_storage) {
2019 rb_id_table_foreach_values(r->idkey_local_storage, idkey_local_storage_mark_i, NULL);
2020 }
2021
2022 rb_gc_mark(r->local_storage_store_lock);
2023}
2024
2025static int
2026ractor_local_storage_free_i(st_data_t key, st_data_t val, st_data_t dmy)
2027{
2029 if (k->type->free) (*k->type->free)((void *)val);
2030 return ST_CONTINUE;
2031}
2032
2033static void
2034ractor_local_storage_free(rb_ractor_t *r)
2035{
2036 if (r->local_storage) {
2037 st_foreach(r->local_storage, ractor_local_storage_free_i, 0);
2038 st_free_table(r->local_storage);
2039 }
2040
2041 if (r->idkey_local_storage) {
2042 rb_id_table_free(r->idkey_local_storage);
2043 }
2044}
2045
2046static void
2047rb_ractor_local_storage_value_mark(void *ptr)
2048{
2049 rb_gc_mark((VALUE)ptr);
2050}
2051
2052static const struct rb_ractor_local_storage_type ractor_local_storage_type_null = {
2053 NULL,
2054 NULL,
2055};
2056
2058 NULL,
2059 ruby_xfree,
2060};
2061
2062static const struct rb_ractor_local_storage_type ractor_local_storage_type_value = {
2063 rb_ractor_local_storage_value_mark,
2064 NULL,
2065};
2066
2069{
2071 key->type = type ? type : &ractor_local_storage_type_null;
2072 key->main_cache = (void *)Qundef;
2073 return key;
2074}
2075
2078{
2079 return rb_ractor_local_storage_ptr_newkey(&ractor_local_storage_type_value);
2080}
2081
2082void
2083rb_ractor_local_storage_delkey(rb_ractor_local_key_t key)
2084{
2085 RB_VM_LOCKING() {
2086 if (freed_ractor_local_keys.cnt == freed_ractor_local_keys.capa) {
2087 freed_ractor_local_keys.capa = freed_ractor_local_keys.capa ? freed_ractor_local_keys.capa * 2 : 4;
2088 REALLOC_N(freed_ractor_local_keys.keys, rb_ractor_local_key_t, freed_ractor_local_keys.capa);
2089 }
2090 freed_ractor_local_keys.keys[freed_ractor_local_keys.cnt++] = key;
2091 }
2092}
2093
2094static bool
2095ractor_local_ref(rb_ractor_local_key_t key, void **pret)
2096{
2097 if (rb_ractor_main_p()) {
2098 if (!UNDEF_P((VALUE)key->main_cache)) {
2099 *pret = key->main_cache;
2100 return true;
2101 }
2102 else {
2103 return false;
2104 }
2105 }
2106 else {
2107 rb_ractor_t *cr = GET_RACTOR();
2108
2109 if (cr->local_storage && st_lookup(cr->local_storage, (st_data_t)key, (st_data_t *)pret)) {
2110 return true;
2111 }
2112 else {
2113 return false;
2114 }
2115 }
2116}
2117
2118static void
2119ractor_local_set(rb_ractor_local_key_t key, void *ptr)
2120{
2121 rb_ractor_t *cr = GET_RACTOR();
2122
2123 if (cr->local_storage == NULL) {
2124 cr->local_storage = st_init_numtable();
2125 }
2126
2127 st_insert(cr->local_storage, (st_data_t)key, (st_data_t)ptr);
2128
2129 if (rb_ractor_main_p()) {
2130 key->main_cache = ptr;
2131 }
2132}
2133
2134VALUE
2136{
2137 void *val;
2138 if (ractor_local_ref(key, &val)) {
2139 return (VALUE)val;
2140 }
2141 else {
2142 return Qnil;
2143 }
2144}
2145
2146bool
2148{
2149 if (ractor_local_ref(key, (void **)val)) {
2150 return true;
2151 }
2152 else {
2153 return false;
2154 }
2155}
2156
2157void
2159{
2160 ractor_local_set(key, (void *)val);
2161}
2162
2163void *
2165{
2166 void *ret;
2167 if (ractor_local_ref(key, &ret)) {
2168 return ret;
2169 }
2170 else {
2171 return NULL;
2172 }
2173}
2174
2175void
2177{
2178 ractor_local_set(key, ptr);
2179}
2180
2181#define DEFAULT_KEYS_CAPA 0x10
2182
2183void
2184rb_ractor_finish_marking(void)
2185{
2186 for (int i=0; i<freed_ractor_local_keys.cnt; i++) {
2187 ruby_xfree(freed_ractor_local_keys.keys[i]);
2188 }
2189 freed_ractor_local_keys.cnt = 0;
2190 if (freed_ractor_local_keys.capa > DEFAULT_KEYS_CAPA) {
2191 freed_ractor_local_keys.capa = DEFAULT_KEYS_CAPA;
2192 REALLOC_N(freed_ractor_local_keys.keys, rb_ractor_local_key_t, DEFAULT_KEYS_CAPA);
2193 }
2194}
2195
2196static VALUE
2197ractor_local_value(rb_execution_context_t *ec, VALUE self, VALUE sym)
2198{
2199 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
2200 ID id = rb_check_id(&sym);
2201 struct rb_id_table *tbl = cr->idkey_local_storage;
2202 VALUE val;
2203
2204 if (id && tbl && rb_id_table_lookup(tbl, id, &val)) {
2205 return val;
2206 }
2207 else {
2208 return Qnil;
2209 }
2210}
2211
2212static VALUE
2213ractor_local_value_set(rb_execution_context_t *ec, VALUE self, VALUE sym, VALUE val)
2214{
2215 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
2216 ID id = SYM2ID(rb_to_symbol(sym));
2217 struct rb_id_table *tbl = cr->idkey_local_storage;
2218
2219 if (tbl == NULL) {
2220 tbl = cr->idkey_local_storage = rb_id_table_create(2);
2221 }
2222 rb_id_table_insert(tbl, id, val);
2223 return val;
2224}
2225
2228 struct rb_id_table *tbl;
2229 ID id;
2230 VALUE sym;
2231};
2232
2233static VALUE
2234ractor_local_value_store_i(VALUE ptr)
2235{
2236 VALUE val;
2238
2239 if (rb_id_table_lookup(data->tbl, data->id, &val)) {
2240 // after synchronization, we found already registered entry
2241 }
2242 else {
2243 val = rb_yield(Qnil);
2244 ractor_local_value_set(data->ec, Qnil, data->sym, val);
2245 }
2246 return val;
2247}
2248
2249static VALUE
2250ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE sym)
2251{
2252 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
2253 struct ractor_local_storage_store_data data = {
2254 .ec = ec,
2255 .sym = sym,
2256 .id = SYM2ID(rb_to_symbol(sym)),
2257 .tbl = cr->idkey_local_storage,
2258 };
2259 VALUE val;
2260
2261 if (data.tbl == NULL) {
2262 data.tbl = cr->idkey_local_storage = rb_id_table_create(2);
2263 }
2264 else if (rb_id_table_lookup(data.tbl, data.id, &val)) {
2265 // already set
2266 return val;
2267 }
2268
2269 if (!cr->local_storage_store_lock) {
2270 cr->local_storage_store_lock = rb_mutex_new();
2271 }
2272
2273 return rb_mutex_synchronize(cr->local_storage_store_lock, ractor_local_value_store_i, (VALUE)&data);
2274}
2275
2276// Ractor#require
2277
2279 VALUE port;
2280 VALUE result;
2281 VALUE exception;
2282
2283 // require
2284 VALUE feature;
2285
2286 // autoload
2287 VALUE module;
2288 ID name;
2289
2290 bool silent;
2291};
2292
2293RUBY_REFERENCES(cross_ractor_require_refs) = {
2294 RUBY_REF_EDGE(struct cross_ractor_require, port),
2295 RUBY_REF_EDGE(struct cross_ractor_require, result),
2296 RUBY_REF_EDGE(struct cross_ractor_require, exception),
2297 RUBY_REF_EDGE(struct cross_ractor_require, feature),
2298 RUBY_REF_EDGE(struct cross_ractor_require, module),
2299 RUBY_REF_END
2300};
2301
2302static const rb_data_type_t cross_ractor_require_data_type = {
2303 "ractor/cross_ractor_require",
2304 {
2305 RUBY_REFS_LIST_PTR(cross_ractor_require_refs),
2307 NULL, // memsize
2308 NULL, // compact
2309 },
2310 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_DECL_MARKING | RUBY_TYPED_EMBEDDABLE
2311};
2312
2313static VALUE
2314require_body(VALUE crr_obj)
2315{
2316 struct cross_ractor_require *crr;
2317 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2318
2319 ID require;
2320 CONST_ID(require, "require");
2321
2322 if (crr->silent) {
2323 int rb_require_internal_silent(VALUE fname);
2324
2325 RB_OBJ_WRITE(crr_obj, &crr->result, INT2NUM(rb_require_internal_silent(crr->feature)));
2326 }
2327 else {
2328 RB_OBJ_WRITE(crr_obj, &crr->result, rb_funcallv(Qnil, require, 1, &crr->feature));
2329 }
2330
2331 return Qnil;
2332}
2333
2334static VALUE
2335require_rescue(VALUE crr_obj, VALUE errinfo)
2336{
2337 struct cross_ractor_require *crr;
2338 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2339
2340 RB_OBJ_WRITE(crr_obj, &crr->exception, errinfo);
2341
2342 return Qundef;
2343}
2344
2345static VALUE
2346require_result_copy_body(VALUE crr_obj)
2347{
2348 struct cross_ractor_require *crr;
2349 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2350
2351 if (crr->exception != Qundef) {
2352 VM_ASSERT(crr->result == Qundef);
2353 RB_OBJ_WRITE(crr_obj, &crr->exception, ractor_copy(crr->exception));
2354 }
2355 else{
2356 VM_ASSERT(crr->result != Qundef);
2357 RB_OBJ_WRITE(crr_obj, &crr->result, ractor_copy(crr->result));
2358 }
2359
2360 return Qnil;
2361}
2362
2363static VALUE
2364require_result_copy_resuce(VALUE crr_obj, VALUE errinfo)
2365{
2366 struct cross_ractor_require *crr;
2367 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2368
2369 RB_OBJ_WRITE(crr_obj, &crr->exception, errinfo);
2370
2371 return Qnil;
2372}
2373
2374static VALUE
2375ractor_require_protect(VALUE crr_obj, VALUE (*func)(VALUE))
2376{
2377 struct cross_ractor_require *crr;
2378 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2379
2380 const bool silent = crr->silent;
2381 VALUE debug, errinfo;
2382 if (silent) {
2383 debug = ruby_debug;
2384 errinfo = rb_errinfo();
2385 }
2386
2387 // catch any error
2388 rb_rescue2(func, crr_obj,
2389 require_rescue, crr_obj, rb_eException, 0);
2390
2391 if (silent) {
2392 ruby_debug = debug;
2393 rb_set_errinfo(errinfo);
2394 }
2395
2396 rb_rescue2(require_result_copy_body, crr_obj,
2397 require_result_copy_resuce, crr_obj, rb_eException, 0);
2398
2399 ractor_port_send(GET_EC(), crr->port, Qtrue, Qfalse);
2400 RB_GC_GUARD(crr_obj);
2401 return Qnil;
2402}
2403
2404static VALUE
2405ractor_require_func(void *crr_obj)
2406{
2407 return ractor_require_protect((VALUE)crr_obj, require_body);
2408}
2409
2410VALUE
2411rb_ractor_require(VALUE feature, bool silent)
2412{
2413 // We're about to block on the main ractor, so if we're holding the global lock we'll deadlock.
2414 ASSERT_vm_unlocking();
2415
2416 struct cross_ractor_require *crr;
2417 VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2418 FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE);
2419
2420 // Convert feature to proper file path and make it shareable as fstring
2421 RB_OBJ_WRITE(crr_obj, &crr->feature, rb_fstring(FilePathValue(feature)));
2422 RB_OBJ_WRITE(crr_obj, &crr->port, ractor_port_new(GET_RACTOR()));
2423 crr->result = Qundef;
2424 crr->exception = Qundef;
2425 crr->silent = silent;
2426
2427 rb_execution_context_t *ec = GET_EC();
2428 rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
2429 rb_ractor_interrupt_exec(main_r, ractor_require_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
2430
2431 // wait for require done
2432 ractor_port_receive(ec, crr->port);
2433 ractor_port_close(ec, crr->port);
2434
2435 VALUE exc = crr->exception;
2436 VALUE result = crr->result;
2437 RB_GC_GUARD(crr_obj);
2438
2439 if (exc != Qundef) {
2440 ractor_reset_belonging(exc);
2441 rb_exc_raise(exc);
2442 }
2443 else {
2444 RUBY_ASSERT(result != Qundef);
2445 ractor_reset_belonging(result);
2446 return result;
2447 }
2448}
2449
2450static VALUE
2451ractor_require(rb_execution_context_t *ec, VALUE self, VALUE feature)
2452{
2453 return rb_ractor_require(feature, false);
2454}
2455
2456static VALUE
2457autoload_load_body(VALUE crr_obj)
2458{
2459 struct cross_ractor_require *crr;
2460 TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2461
2462 RB_OBJ_WRITE(crr_obj, &crr->result, rb_autoload_load(crr->module, crr->name));
2463
2464 return Qnil;
2465}
2466
2467static VALUE
2468ractor_autoload_load_func(void *crr_obj)
2469{
2470 return ractor_require_protect((VALUE)crr_obj, autoload_load_body);
2471}
2472
2473VALUE
2474rb_ractor_autoload_load(VALUE module, ID name)
2475{
2476 struct cross_ractor_require *crr;
2477 VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
2478 FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE);
2479 RB_OBJ_WRITE(crr_obj, &crr->module, module);
2480 RB_OBJ_WRITE(crr_obj, &crr->name, name);
2481 RB_OBJ_WRITE(crr_obj, &crr->port, ractor_port_new(GET_RACTOR()));
2482 crr->result = Qundef;
2483 crr->exception = Qundef;
2484
2485 rb_execution_context_t *ec = GET_EC();
2486 rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
2487 rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
2488
2489 // wait for require done
2490 ractor_port_receive(ec, crr->port);
2491 ractor_port_close(ec, crr->port);
2492
2493 VALUE exc = crr->exception;
2494 VALUE result = crr->result;
2495 RB_GC_GUARD(crr_obj);
2496
2497 if (exc != Qundef) {
2498 rb_exc_raise(exc);
2499 }
2500 else {
2501 return result;
2502 }
2503}
2504
2505#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:877
@ RUBY_FL_PROMOTED
Ruby objects are "generational".
Definition fl_type.h:217
@ RUBY_FL_SHAREABLE
This flag has something to do with Ractor.
Definition fl_type.h:280
VALUE rb_define_class(const char *name, VALUE super)
Defines a top-level class.
Definition class.c:1476
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
Defines a class under the namespace of outer.
Definition class.c:1512
#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 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 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 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 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 FL_WB_PROTECTED
Old name of RUBY_FL_WB_PROTECTED.
Definition fl_type.h:59
#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:66
#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:129
#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:486
void rb_exc_raise(VALUE mesg)
Raises an exception in the current thread.
Definition eval.c:682
int rb_typeddata_is_kind_of(VALUE obj, const rb_data_type_t *data_type)
Checks if the given object is of given kind.
Definition error.c:1380
VALUE rb_eRuntimeError
RuntimeError exception.
Definition error.c:1428
VALUE rb_eStopIteration
StopIteration exception.
Definition enumerator.c:180
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:1481
VALUE rb_eException
Mother of all exceptions.
Definition error.c:1422
VALUE rb_cRactor
Ractor class.
Definition ractor.c:24
VALUE rb_stdin
STDIN constant.
Definition io.c:201
VALUE rb_stderr
STDERR constant.
Definition io.c:201
static VALUE rb_class_of(VALUE obj)
Object to class mapping function.
Definition globals.h:175
VALUE rb_cBasicObject
BasicObject class.
Definition object.c:59
VALUE rb_obj_clone(VALUE obj)
Produces a shallow copy of the given object.
Definition object.c:525
VALUE rb_stdout
STDOUT constant.
Definition io.c:201
#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:615
#define RB_OBJ_WRITE(old, slot, young)
Declaration of a "back" pointer.
Definition gc.h:603
VALUE rb_funcall(VALUE recv, ID mid, int n,...)
Calls a method.
Definition vm_eval.c:1117
#define RGENGC_WB_PROTECTED_STRUCT
This is a compile-time flag to enable/disable write barrier for struct RStruct.
Definition gc.h:468
#define RGENGC_WB_PROTECTED_STRING
This is a compile-time flag to enable/disable write barrier for struct RString.
Definition gc.h:479
#define RGENGC_WB_PROTECTED_HASH
This is a compile-time flag to enable/disable write barrier for struct RHash.
Definition gc.h:457
#define RGENGC_WB_PROTECTED_MATCH
This is a compile-time flag to enable/disable write barrier for struct RMatch.
Definition gc.h:512
#define RGENGC_WB_PROTECTED_ARRAY
This is a compile-time flag to enable/disable write barrier for struct RArray.
Definition gc.h:446
#define RGENGC_WB_PROTECTED_COMPLEX
This is a compile-time flag to enable/disable write barrier for struct RComplex.
Definition gc.h:545
#define RGENGC_WB_PROTECTED_FLOAT
This is a compile-time flag to enable/disable write barrier for struct RFloat.
Definition gc.h:534
#define RGENGC_WB_PROTECTED_RATIONAL
This is a compile-time flag to enable/disable write barrier for struct RRational.
Definition gc.h:556
#define RGENGC_WB_PROTECTED_REGEXP
This is a compile-time flag to enable/disable write barrier for struct RRegexp.
Definition gc.h:501
#define RGENGC_WB_PROTECTED_OBJECT
This is a compile-time flag to enable/disable write barrier for struct RObject.
Definition gc.h:490
VALUE rb_ary_new(void)
Allocates a new, 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_entry(VALUE ary, long off)
Queries an element of an array.
VALUE rb_obj_is_proc(VALUE recv)
Queries if the given object is a proc.
Definition proc.c:120
VALUE rb_str_new_frozen(VALUE str)
Creates a frozen copy of the string, if necessary.
Definition string.c:1493
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_autoload_load(VALUE space, ID name)
Kicks the autoload procedure as if it was "touched".
Definition variable.c:3292
void rb_undef_alloc_func(VALUE klass)
Deletes the allocator function of a class.
Definition vm_method.c:1603
ID rb_check_id(volatile VALUE *namep)
Detects if the given name is already interned or not.
Definition symbol.c:1133
VALUE rb_to_symbol(VALUE name)
Identical to rb_intern_str(), except it generates a dynamic symbol if necessary.
Definition string.c:12605
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:2057
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:1438
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:2164
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:2176
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:2068
VALUE rb_ractor_stdin(void)
Queries the standard input of the current Ractor that is calling this function.
Definition ractor.c:1038
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:1098
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:2158
bool rb_ractor_local_storage_value_lookup(rb_ractor_local_key_t key, VALUE *val)
Queries the key.
Definition ractor.c:2147
#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:1429
rb_ractor_local_key_t rb_ractor_local_storage_value_newkey(void)
Issues a new key.
Definition ractor.c:2077
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:1086
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:1074
VALUE rb_ractor_local_storage_value(rb_ractor_local_key_t key)
Queries the key.
Definition ractor.c:2135
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1372
rb_block_call_func * rb_block_call_func_t
Shorthand type that represents an iterator-written-in-C function pointer.
Definition iterator.h:88
#define 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.
static int RARRAY_LENINT(VALUE ary)
Identical to rb_array_len(), except it differs for the return type.
Definition rarray.h:281
static void RARRAY_ASET(VALUE ary, long i, VALUE v)
Assigns an object in an array.
Definition rarray.h:386
#define RARRAY_AREF(a, i)
Definition rarray.h:403
#define RBASIC(obj)
Convenient casting macro.
Definition rbasic.h:40
#define DATA_PTR(obj)
Convenient getter macro.
Definition rdata.h:67
#define RUBY_DEFAULT_FREE
This is a value you can set to RData::dfree.
Definition rdata.h:78
#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
static VALUE * ROBJECT_FIELDS(VALUE obj)
Queries the instance variables.
Definition robject.h:128
#define StringValueCStr(v)
Identical to StringValuePtr, except it additionally checks for the contents for viability as a C stri...
Definition rstring.h:89
static bool RTYPEDDATA_P(VALUE obj)
Checks whether the passed object is RTypedData or RData.
Definition rtypeddata.h:586
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:523
#define TypedData_Wrap_Struct(klass, data_type, sval)
Converts sval, a pointer to your struct, into a Ruby object.
Definition rtypeddata.h:458
#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:505
static const struct rb_data_type_struct * RTYPEDDATA_TYPE(VALUE obj)
Queries for the type of given object.
Definition rtypeddata.h:609
#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.
Ruby object's base components.
Definition rbasic.h:69
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:204
Type that defines a ractor-local storage.
Definition ractor.h:21
void(* free)(void *ptr)
A function to destruct a ractor-local storage.
Definition ractor.h:37
void(* mark)(void *ptr)
A function to mark a ractor-local storage.
Definition ractor.h:29
Definition st.h:79
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_unlock.
void rb_native_mutex_destroy(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_destroy.
void rb_native_cond_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
@ RUBY_T_MASK
Bitmask of ruby_value_type.
Definition value_type.h:145