Ruby 3.5.0dev (2025-04-25 revision 62a7f17157c5c67956d95a2582f8f256df13f9e2)
ractor.c (62a7f17157c5c67956d95a2582f8f256df13f9e2)
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
24static VALUE rb_cRactorSelector;
25
26VALUE rb_eRactorUnsafeError;
27VALUE rb_eRactorIsolationError;
28static VALUE rb_eRactorError;
29static VALUE rb_eRactorRemoteError;
30static VALUE rb_eRactorMovedError;
31static VALUE rb_eRactorClosedError;
32static VALUE rb_cRactorMovedObject;
33
34static void vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line);
35
36// Ractor locking
37
38static void
39ASSERT_ractor_unlocking(rb_ractor_t *r)
40{
41#if RACTOR_CHECK_MODE > 0
42 if (rb_current_execution_context(false) != NULL && r->sync.locked_by == rb_ractor_self(GET_RACTOR())) {
43 rb_bug("recursive ractor locking");
44 }
45#endif
46}
47
48static void
49ASSERT_ractor_locking(rb_ractor_t *r)
50{
51#if RACTOR_CHECK_MODE > 0
52 if (rb_current_execution_context(false) != NULL && r->sync.locked_by != rb_ractor_self(GET_RACTOR())) {
53 rp(r->sync.locked_by);
54 rb_bug("ractor lock is not acquired.");
55 }
56#endif
57}
58
59static void
60ractor_lock(rb_ractor_t *r, const char *file, int line)
61{
62 RUBY_DEBUG_LOG2(file, line, "locking r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
63
64 ASSERT_ractor_unlocking(r);
65 rb_native_mutex_lock(&r->sync.lock);
66
67#if RACTOR_CHECK_MODE > 0
68 if (rb_current_execution_context(false) != NULL) {
69 rb_ractor_t *cr = rb_current_ractor_raw(false);
70 r->sync.locked_by = cr ? rb_ractor_self(cr) : Qundef;
71 }
72#endif
73
74 RUBY_DEBUG_LOG2(file, line, "locked r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
75}
76
77static void
78ractor_lock_self(rb_ractor_t *cr, const char *file, int line)
79{
80 VM_ASSERT(cr == GET_RACTOR());
81#if RACTOR_CHECK_MODE > 0
82 VM_ASSERT(cr->sync.locked_by != cr->pub.self);
83#endif
84 ractor_lock(cr, file, line);
85}
86
87static void
88ractor_unlock(rb_ractor_t *r, const char *file, int line)
89{
90 ASSERT_ractor_locking(r);
91#if RACTOR_CHECK_MODE > 0
92 r->sync.locked_by = Qnil;
93#endif
94 rb_native_mutex_unlock(&r->sync.lock);
95
96 RUBY_DEBUG_LOG2(file, line, "r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
97}
98
99static void
100ractor_unlock_self(rb_ractor_t *cr, const char *file, int line)
101{
102 VM_ASSERT(cr == GET_RACTOR());
103#if RACTOR_CHECK_MODE > 0
104 VM_ASSERT(cr->sync.locked_by == cr->pub.self);
105#endif
106 ractor_unlock(cr, file, line);
107}
108
109#define RACTOR_LOCK(r) ractor_lock(r, __FILE__, __LINE__)
110#define RACTOR_UNLOCK(r) ractor_unlock(r, __FILE__, __LINE__)
111#define RACTOR_LOCK_SELF(r) ractor_lock_self(r, __FILE__, __LINE__)
112#define RACTOR_UNLOCK_SELF(r) ractor_unlock_self(r, __FILE__, __LINE__)
113
114void
115rb_ractor_lock_self(rb_ractor_t *r)
116{
117 RACTOR_LOCK_SELF(r);
118}
119
120void
121rb_ractor_unlock_self(rb_ractor_t *r)
122{
123 RACTOR_UNLOCK_SELF(r);
124}
125
126// Ractor status
127
128static const char *
129ractor_status_str(enum ractor_status status)
130{
131 switch (status) {
132 case ractor_created: return "created";
133 case ractor_running: return "running";
134 case ractor_blocking: return "blocking";
135 case ractor_terminated: return "terminated";
136 }
137 rb_bug("unreachable");
138}
139
140static void
141ractor_status_set(rb_ractor_t *r, enum ractor_status status)
142{
143 RUBY_DEBUG_LOG("r:%u [%s]->[%s]", r->pub.id, ractor_status_str(r->status_), ractor_status_str(status));
144
145 // check 1
146 if (r->status_ != ractor_created) {
147 VM_ASSERT(r == GET_RACTOR()); // only self-modification is allowed.
148 ASSERT_vm_locking();
149 }
150
151 // check2: transition check. assume it will be vanished on non-debug build.
152 switch (r->status_) {
153 case ractor_created:
154 VM_ASSERT(status == ractor_blocking);
155 break;
156 case ractor_running:
157 VM_ASSERT(status == ractor_blocking||
158 status == ractor_terminated);
159 break;
160 case ractor_blocking:
161 VM_ASSERT(status == ractor_running);
162 break;
163 case ractor_terminated:
164 rb_bug("unreachable");
165 break;
166 }
167
168 r->status_ = status;
169}
170
171static bool
172ractor_status_p(rb_ractor_t *r, enum ractor_status status)
173{
174 return rb_ractor_status_p(r, status);
175}
176
177// Ractor data/mark/free
178
179static struct rb_ractor_basket *ractor_queue_at(rb_ractor_t *r, struct rb_ractor_queue *rq, int i);
180static void ractor_local_storage_mark(rb_ractor_t *r);
181static void ractor_local_storage_free(rb_ractor_t *r);
182
183static void
184ractor_queue_mark(struct rb_ractor_queue *rq)
185{
186 for (int i=0; i<rq->cnt; i++) {
187 struct rb_ractor_basket *b = ractor_queue_at(NULL, rq, i);
188 rb_gc_mark(b->sender);
189
190 switch (b->type.e) {
191 case basket_type_yielding:
192 case basket_type_take_basket:
193 case basket_type_deleted:
194 case basket_type_reserved:
195 // ignore
196 break;
197 default:
198 rb_gc_mark(b->p.send.v);
199 }
200 }
201}
202
203static void
204ractor_mark(void *ptr)
205{
206 rb_ractor_t *r = (rb_ractor_t *)ptr;
207
208 ractor_queue_mark(&r->sync.recv_queue);
209 ractor_queue_mark(&r->sync.takers_queue);
210
211 rb_gc_mark(r->receiving_mutex);
212
213 rb_gc_mark(r->loc);
214 rb_gc_mark(r->name);
215 rb_gc_mark(r->r_stdin);
216 rb_gc_mark(r->r_stdout);
217 rb_gc_mark(r->r_stderr);
218 rb_hook_list_mark(&r->pub.hooks);
219
220 if (r->threads.cnt > 0) {
221 rb_thread_t *th = 0;
222 ccan_list_for_each(&r->threads.set, th, lt_node) {
223 VM_ASSERT(th != NULL);
224 rb_gc_mark(th->self);
225 }
226 }
227
228 ractor_local_storage_mark(r);
229}
230
231static void
232ractor_queue_free(struct rb_ractor_queue *rq)
233{
234 free(rq->baskets);
235}
236
237static void
238ractor_free(void *ptr)
239{
240 rb_ractor_t *r = (rb_ractor_t *)ptr;
241 RUBY_DEBUG_LOG("free r:%d", rb_ractor_id(r));
242 rb_native_mutex_destroy(&r->sync.lock);
243#ifdef RUBY_THREAD_WIN32_H
244 rb_native_cond_destroy(&r->sync.cond);
245#endif
246 ractor_queue_free(&r->sync.recv_queue);
247 ractor_queue_free(&r->sync.takers_queue);
248 ractor_local_storage_free(r);
249 rb_hook_list_free(&r->pub.hooks);
250
251 if (r->newobj_cache) {
252 RUBY_ASSERT(r == ruby_single_main_ractor);
253
254 rb_gc_ractor_cache_free(r->newobj_cache);
255 r->newobj_cache = NULL;
256 }
257
258 ruby_xfree(r);
259}
260
261static size_t
262ractor_queue_memsize(const struct rb_ractor_queue *rq)
263{
264 return sizeof(struct rb_ractor_basket) * rq->size;
265}
266
267static size_t
268ractor_memsize(const void *ptr)
269{
270 rb_ractor_t *r = (rb_ractor_t *)ptr;
271
272 // TODO: more correct?
273 return sizeof(rb_ractor_t) +
274 ractor_queue_memsize(&r->sync.recv_queue) +
275 ractor_queue_memsize(&r->sync.takers_queue);
276}
277
278static const rb_data_type_t ractor_data_type = {
279 "ractor",
280 {
281 ractor_mark,
282 ractor_free,
283 ractor_memsize,
284 NULL, // update
285 },
286 0, 0, RUBY_TYPED_FREE_IMMEDIATELY /* | RUBY_TYPED_WB_PROTECTED */
287};
288
289bool
290rb_ractor_p(VALUE gv)
291{
292 if (rb_typeddata_is_kind_of(gv, &ractor_data_type)) {
293 return true;
294 }
295 else {
296 return false;
297 }
298}
299
300static inline rb_ractor_t *
301RACTOR_PTR(VALUE self)
302{
303 VM_ASSERT(rb_ractor_p(self));
304 rb_ractor_t *r = DATA_PTR(self);
305 return r;
306}
307
308static rb_atomic_t ractor_last_id;
309
310#if RACTOR_CHECK_MODE > 0
311uint32_t
312rb_ractor_current_id(void)
313{
314 if (GET_THREAD()->ractor == NULL) {
315 return 1; // main ractor
316 }
317 else {
318 return rb_ractor_id(GET_RACTOR());
319 }
320}
321#endif
322
323// Ractor queue
324
325static void
326ractor_queue_setup(struct rb_ractor_queue *rq)
327{
328 rq->size = 2;
329 rq->cnt = 0;
330 rq->start = 0;
331 rq->baskets = malloc(sizeof(struct rb_ractor_basket) * rq->size);
332}
333
334static struct rb_ractor_basket *
335ractor_queue_head(rb_ractor_t *r, struct rb_ractor_queue *rq)
336{
337 if (r != NULL) ASSERT_ractor_locking(r);
338 return &rq->baskets[rq->start];
339}
340
341static struct rb_ractor_basket *
342ractor_queue_at(rb_ractor_t *r, struct rb_ractor_queue *rq, int i)
343{
344 if (r != NULL) ASSERT_ractor_locking(r);
345 return &rq->baskets[(rq->start + i) % rq->size];
346}
347
348static void
349ractor_queue_advance(rb_ractor_t *r, struct rb_ractor_queue *rq)
350{
351 ASSERT_ractor_locking(r);
352
353 if (rq->reserved_cnt == 0) {
354 rq->cnt--;
355 rq->start = (rq->start + 1) % rq->size;
356 rq->serial++;
357 }
358 else {
359 ractor_queue_at(r, rq, 0)->type.e = basket_type_deleted;
360 }
361}
362
363static bool
364ractor_queue_skip_p(rb_ractor_t *r, struct rb_ractor_queue *rq, int i)
365{
366 struct rb_ractor_basket *b = ractor_queue_at(r, rq, i);
367 return basket_type_p(b, basket_type_deleted) ||
368 basket_type_p(b, basket_type_reserved);
369}
370
371static void
372ractor_queue_compact(rb_ractor_t *r, struct rb_ractor_queue *rq)
373{
374 ASSERT_ractor_locking(r);
375
376 while (rq->cnt > 0 && basket_type_p(ractor_queue_at(r, rq, 0), basket_type_deleted)) {
377 ractor_queue_advance(r, rq);
378 }
379}
380
381static bool
382ractor_queue_empty_p(rb_ractor_t *r, struct rb_ractor_queue *rq)
383{
384 ASSERT_ractor_locking(r);
385
386 if (rq->cnt == 0) {
387 return true;
388 }
389
390 ractor_queue_compact(r, rq);
391
392 for (int i=0; i<rq->cnt; i++) {
393 if (!ractor_queue_skip_p(r, rq, i)) {
394 return false;
395 }
396 }
397
398 return true;
399}
400
401static bool
402ractor_queue_deq(rb_ractor_t *r, struct rb_ractor_queue *rq, struct rb_ractor_basket *basket)
403{
404 ASSERT_ractor_locking(r);
405
406 for (int i=0; i<rq->cnt; i++) {
407 if (!ractor_queue_skip_p(r, rq, i)) {
408 struct rb_ractor_basket *b = ractor_queue_at(r, rq, i);
409 *basket = *b;
410
411 // remove from queue
412 b->type.e = basket_type_deleted;
413 ractor_queue_compact(r, rq);
414 return true;
415 }
416 }
417
418 return false;
419}
420
421static void
422ractor_queue_enq(rb_ractor_t *r, struct rb_ractor_queue *rq, struct rb_ractor_basket *basket)
423{
424 ASSERT_ractor_locking(r);
425
426 if (rq->size <= rq->cnt) {
427 rq->baskets = realloc(rq->baskets, sizeof(struct rb_ractor_basket) * rq->size * 2);
428 for (int i=rq->size - rq->start; i<rq->cnt; i++) {
429 rq->baskets[i + rq->start] = rq->baskets[i + rq->start - rq->size];
430 }
431 rq->size *= 2;
432 }
433 rq->baskets[(rq->start + rq->cnt++) % rq->size] = *basket;
434 // fprintf(stderr, "%s %p->cnt:%d\n", RUBY_FUNCTION_NAME_STRING, (void *)rq, rq->cnt);
435}
436
437static void
438ractor_queue_delete(rb_ractor_t *r, struct rb_ractor_queue *rq, struct rb_ractor_basket *basket)
439{
440 basket->type.e = basket_type_deleted;
441}
442
443// Ractor basket
444
445static VALUE ractor_reset_belonging(VALUE obj); // in this file
446
447static VALUE
448ractor_basket_value(struct rb_ractor_basket *b)
449{
450 switch (b->type.e) {
451 case basket_type_ref:
452 break;
453 case basket_type_copy:
454 case basket_type_move:
455 case basket_type_will:
456 b->type.e = basket_type_ref;
457 b->p.send.v = ractor_reset_belonging(b->p.send.v);
458 break;
459 default:
460 rb_bug("unreachable");
461 }
462
463 return b->p.send.v;
464}
465
466static VALUE
467ractor_basket_accept(struct rb_ractor_basket *b)
468{
469 VALUE v = ractor_basket_value(b);
470
471 if (b->p.send.exception) {
472 VALUE cause = v;
473 VALUE err = rb_exc_new_cstr(rb_eRactorRemoteError, "thrown by remote Ractor.");
474 rb_ivar_set(err, rb_intern("@ractor"), b->sender);
475 rb_ec_setup_exception(NULL, err, cause);
476 rb_exc_raise(err);
477 }
478
479 return v;
480}
481
482// Ractor synchronizations
483
484#if USE_RUBY_DEBUG_LOG
485static const char *
486wait_status_str(enum rb_ractor_wait_status wait_status)
487{
488 switch ((int)wait_status) {
489 case wait_none: return "none";
490 case wait_receiving: return "receiving";
491 case wait_taking: return "taking";
492 case wait_yielding: return "yielding";
493 case wait_receiving|wait_taking: return "receiving|taking";
494 case wait_receiving|wait_yielding: return "receiving|yielding";
495 case wait_taking|wait_yielding: return "taking|yielding";
496 case wait_receiving|wait_taking|wait_yielding: return "receiving|taking|yielding";
497 }
498 rb_bug("unreachable");
499}
500
501static const char *
502wakeup_status_str(enum rb_ractor_wakeup_status wakeup_status)
503{
504 switch (wakeup_status) {
505 case wakeup_none: return "none";
506 case wakeup_by_send: return "by_send";
507 case wakeup_by_yield: return "by_yield";
508 case wakeup_by_take: return "by_take";
509 case wakeup_by_close: return "by_close";
510 case wakeup_by_interrupt: return "by_interrupt";
511 case wakeup_by_retry: return "by_retry";
512 }
513 rb_bug("unreachable");
514}
515
516static const char *
517basket_type_name(enum rb_ractor_basket_type type)
518{
519 switch (type) {
520 case basket_type_none: return "none";
521 case basket_type_ref: return "ref";
522 case basket_type_copy: return "copy";
523 case basket_type_move: return "move";
524 case basket_type_will: return "will";
525 case basket_type_deleted: return "deleted";
526 case basket_type_reserved: return "reserved";
527 case basket_type_take_basket: return "take_basket";
528 case basket_type_yielding: return "yielding";
529 }
530 VM_ASSERT(0);
531 return NULL;
532}
533#endif // USE_RUBY_DEBUG_LOG
534
535static bool
536ractor_sleeping_by(const rb_ractor_t *r, enum rb_ractor_wait_status wait_status)
537{
538 return (r->sync.wait.status & wait_status) && r->sync.wait.wakeup_status == wakeup_none;
539}
540
541#ifdef RUBY_THREAD_PTHREAD_H
542// thread_*.c
543void rb_ractor_sched_wakeup(rb_ractor_t *r);
544#else
545
546static void
547rb_ractor_sched_wakeup(rb_ractor_t *r)
548{
549 rb_native_cond_broadcast(&r->sync.cond);
550}
551#endif
552
553
554static bool
555ractor_wakeup(rb_ractor_t *r, enum rb_ractor_wait_status wait_status, enum rb_ractor_wakeup_status wakeup_status)
556{
557 ASSERT_ractor_locking(r);
558
559 RUBY_DEBUG_LOG("r:%u wait_by:%s -> wait:%s wakeup:%s",
560 rb_ractor_id(r),
561 wait_status_str(r->sync.wait.status),
562 wait_status_str(wait_status),
563 wakeup_status_str(wakeup_status));
564
565 if (ractor_sleeping_by(r, wait_status)) {
566 r->sync.wait.wakeup_status = wakeup_status;
567 rb_ractor_sched_wakeup(r);
568 return true;
569 }
570 else {
571 return false;
572 }
573}
574
575static void
576ractor_sleep_interrupt(void *ptr)
577{
578 rb_ractor_t *r = ptr;
579
580 RACTOR_LOCK(r);
581 {
582 ractor_wakeup(r, wait_receiving | wait_taking | wait_yielding, wakeup_by_interrupt);
583 }
584 RACTOR_UNLOCK(r);
585}
586
587typedef void (*ractor_sleep_cleanup_function)(rb_ractor_t *cr, void *p);
588
589static void
590ractor_check_ints(rb_execution_context_t *ec, rb_ractor_t *cr, ractor_sleep_cleanup_function cf_func, void *cf_data)
591{
592 if (cr->sync.wait.status != wait_none) {
593 enum rb_ractor_wait_status prev_wait_status = cr->sync.wait.status;
594 cr->sync.wait.status = wait_none;
595 cr->sync.wait.wakeup_status = wakeup_by_interrupt;
596
597 RACTOR_UNLOCK(cr);
598 {
599 if (cf_func) {
600 enum ruby_tag_type state;
601 EC_PUSH_TAG(ec);
602 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
603 rb_ec_check_ints(ec);
604 }
605 EC_POP_TAG();
606
607 if (state) {
608 (*cf_func)(cr, cf_data);
609 EC_JUMP_TAG(ec, state);
610 }
611 }
612 else {
613 rb_ec_check_ints(ec);
614 }
615 }
616
617 // reachable?
618 RACTOR_LOCK(cr);
619 cr->sync.wait.status = prev_wait_status;
620 }
621}
622
623#ifdef RUBY_THREAD_PTHREAD_H
624void rb_ractor_sched_sleep(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf);
625#else
626
627// win32
628static void
629ractor_cond_wait(rb_ractor_t *r)
630{
631#if RACTOR_CHECK_MODE > 0
632 VALUE locked_by = r->sync.locked_by;
633 r->sync.locked_by = Qnil;
634#endif
635 rb_native_cond_wait(&r->sync.cond, &r->sync.lock);
636
637#if RACTOR_CHECK_MODE > 0
638 r->sync.locked_by = locked_by;
639#endif
640}
641
642static void *
643ractor_sleep_wo_gvl(void *ptr)
644{
645 rb_ractor_t *cr = ptr;
646 RACTOR_LOCK_SELF(cr);
647 {
648 VM_ASSERT(cr->sync.wait.status != wait_none);
649 if (cr->sync.wait.wakeup_status == wakeup_none) {
650 ractor_cond_wait(cr);
651 }
652 cr->sync.wait.status = wait_none;
653 }
654 RACTOR_UNLOCK_SELF(cr);
655 return NULL;
656}
657
658static void
659rb_ractor_sched_sleep(rb_execution_context_t *ec, rb_ractor_t *cr, rb_unblock_function_t *ubf)
660{
661 RACTOR_UNLOCK(cr);
662 {
663 rb_nogvl(ractor_sleep_wo_gvl, cr,
664 ubf, cr,
666 }
667 RACTOR_LOCK(cr);
668}
669#endif
670
671static enum rb_ractor_wakeup_status
672ractor_sleep_with_cleanup(rb_execution_context_t *ec, rb_ractor_t *cr, enum rb_ractor_wait_status wait_status,
673 ractor_sleep_cleanup_function cf_func, void *cf_data)
674{
675 enum rb_ractor_wakeup_status wakeup_status;
676 VM_ASSERT(GET_RACTOR() == cr);
677
678 // TODO: multi-threads
679 VM_ASSERT(cr->sync.wait.status == wait_none);
680 VM_ASSERT(wait_status != wait_none);
681 cr->sync.wait.status = wait_status;
682 cr->sync.wait.wakeup_status = wakeup_none;
683
684 // fprintf(stderr, "%s r:%p status:%s, wakeup_status:%s\n", RUBY_FUNCTION_NAME_STRING, (void *)cr,
685 // wait_status_str(cr->sync.wait.status), wakeup_status_str(cr->sync.wait.wakeup_status));
686
687 RUBY_DEBUG_LOG("sleep by %s", wait_status_str(wait_status));
688
689 while (cr->sync.wait.wakeup_status == wakeup_none) {
690 rb_ractor_sched_sleep(ec, cr, ractor_sleep_interrupt);
691 ractor_check_ints(ec, cr, cf_func, cf_data);
692 }
693
694 cr->sync.wait.status = wait_none;
695
696 // TODO: multi-thread
697 wakeup_status = cr->sync.wait.wakeup_status;
698 cr->sync.wait.wakeup_status = wakeup_none;
699
700 RUBY_DEBUG_LOG("wakeup %s", wakeup_status_str(wakeup_status));
701
702 return wakeup_status;
703}
704
705static enum rb_ractor_wakeup_status
706ractor_sleep(rb_execution_context_t *ec, rb_ractor_t *cr, enum rb_ractor_wait_status wait_status)
707{
708 return ractor_sleep_with_cleanup(ec, cr, wait_status, 0, NULL);
709}
710
711// Ractor.receive
712
713static void
714ractor_recursive_receive_if(rb_ractor_t *r)
715{
716 if (r->receiving_mutex && rb_mutex_owned_p(r->receiving_mutex)) {
717 rb_raise(rb_eRactorError, "can not call receive/receive_if recursively");
718 }
719}
720
721static VALUE
722ractor_try_receive(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *rq)
723{
724 struct rb_ractor_basket basket;
725 ractor_recursive_receive_if(cr);
726 bool received = false;
727
728 RACTOR_LOCK_SELF(cr);
729 {
730 RUBY_DEBUG_LOG("rq->cnt:%d", rq->cnt);
731 received = ractor_queue_deq(cr, rq, &basket);
732 }
733 RACTOR_UNLOCK_SELF(cr);
734
735 if (!received) {
736 if (cr->sync.incoming_port_closed) {
737 rb_raise(rb_eRactorClosedError, "The incoming port is already closed");
738 }
739 return Qundef;
740 }
741 else {
742 return ractor_basket_accept(&basket);
743 }
744}
745
746static void
747ractor_wait_receive(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *rq)
748{
749 VM_ASSERT(cr == rb_ec_ractor_ptr(ec));
750 ractor_recursive_receive_if(cr);
751
752 RACTOR_LOCK(cr);
753 {
754 while (ractor_queue_empty_p(cr, rq) && !cr->sync.incoming_port_closed) {
755 ractor_sleep(ec, cr, wait_receiving);
756 }
757 }
758 RACTOR_UNLOCK(cr);
759}
760
761static VALUE
762ractor_receive(rb_execution_context_t *ec, rb_ractor_t *cr)
763{
764 VM_ASSERT(cr == rb_ec_ractor_ptr(ec));
765 VALUE v;
766 struct rb_ractor_queue *rq = &cr->sync.recv_queue;
767
768 while (UNDEF_P(v = ractor_try_receive(ec, cr, rq))) {
769 ractor_wait_receive(ec, cr, rq);
770 }
771
772 return v;
773}
774
775#if 0
776static void
777rq_dump(struct rb_ractor_queue *rq)
778{
779 bool bug = false;
780 for (int i=0; i<rq->cnt; i++) {
781 struct rb_ractor_basket *b = ractor_queue_at(NULL, rq, i);
782 fprintf(stderr, "%d (start:%d) type:%s %p %s\n", i, rq->start, basket_type_name(b->type),
783 (void *)b, RSTRING_PTR(RARRAY_AREF(b->v, 1)));
784 if (basket_type_p(b, basket_type_reserved) bug = true;
785 }
786 if (bug) rb_bug("!!");
787}
788#endif
789
791 rb_ractor_t *cr;
792 struct rb_ractor_queue *rq;
793 VALUE v;
794 int index;
795 bool success;
796};
797
798static void
799ractor_receive_if_lock(rb_ractor_t *cr)
800{
801 VALUE m = cr->receiving_mutex;
802 if (m == Qfalse) {
803 m = cr->receiving_mutex = rb_mutex_new();
804 }
805 rb_mutex_lock(m);
806}
807
808static VALUE
809receive_if_body(VALUE ptr)
810{
811 struct receive_block_data *data = (struct receive_block_data *)ptr;
812
813 ractor_receive_if_lock(data->cr);
814 VALUE block_result = rb_yield(data->v);
815 rb_ractor_t *cr = data->cr;
816
817 RACTOR_LOCK_SELF(cr);
818 {
819 struct rb_ractor_basket *b = ractor_queue_at(cr, data->rq, data->index);
820 VM_ASSERT(basket_type_p(b, basket_type_reserved));
821 data->rq->reserved_cnt--;
822
823 if (RTEST(block_result)) {
824 ractor_queue_delete(cr, data->rq, b);
825 ractor_queue_compact(cr, data->rq);
826 }
827 else {
828 b->type.e = basket_type_ref;
829 }
830 }
831 RACTOR_UNLOCK_SELF(cr);
832
833 data->success = true;
834
835 if (RTEST(block_result)) {
836 return data->v;
837 }
838 else {
839 return Qundef;
840 }
841}
842
843static VALUE
844receive_if_ensure(VALUE v)
845{
846 struct receive_block_data *data = (struct receive_block_data *)v;
847 rb_ractor_t *cr = data->cr;
848
849 if (!data->success) {
850 RACTOR_LOCK_SELF(cr);
851 {
852 struct rb_ractor_basket *b = ractor_queue_at(cr, data->rq, data->index);
853 VM_ASSERT(basket_type_p(b, basket_type_reserved));
854 b->type.e = basket_type_deleted;
855 data->rq->reserved_cnt--;
856 }
857 RACTOR_UNLOCK_SELF(cr);
858 }
859
860 rb_mutex_unlock(cr->receiving_mutex);
861 return Qnil;
862}
863
864static VALUE
865ractor_receive_if(rb_execution_context_t *ec, VALUE crv, VALUE b)
866{
867 if (!RTEST(b)) rb_raise(rb_eArgError, "no block given");
868
869 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
870 unsigned int serial = (unsigned int)-1;
871 int index = 0;
872 struct rb_ractor_queue *rq = &cr->sync.recv_queue;
873
874 while (1) {
875 VALUE v = Qundef;
876
877 ractor_wait_receive(ec, cr, rq);
878
879 RACTOR_LOCK_SELF(cr);
880 {
881 if (serial != rq->serial) {
882 serial = rq->serial;
883 index = 0;
884 }
885
886 // check newer version
887 for (int i=index; i<rq->cnt; i++) {
888 if (!ractor_queue_skip_p(cr, rq, i)) {
889 struct rb_ractor_basket *b = ractor_queue_at(cr, rq, i);
890 v = ractor_basket_value(b);
891 b->type.e = basket_type_reserved;
892 rq->reserved_cnt++;
893 index = i;
894 break;
895 }
896 }
897 }
898 RACTOR_UNLOCK_SELF(cr);
899
900 if (!UNDEF_P(v)) {
901 struct receive_block_data data = {
902 .cr = cr,
903 .rq = rq,
904 .v = v,
905 .index = index,
906 .success = false,
907 };
908
909 VALUE result = rb_ensure(receive_if_body, (VALUE)&data,
910 receive_if_ensure, (VALUE)&data);
911
912 if (!UNDEF_P(result)) return result;
913 index++;
914 }
915
916 RUBY_VM_CHECK_INTS(ec);
917 }
918}
919
920static void
921ractor_send_basket(rb_execution_context_t *ec, rb_ractor_t *r, struct rb_ractor_basket *b)
922{
923 bool closed = false;
924
925 RACTOR_LOCK(r);
926 {
927 if (r->sync.incoming_port_closed) {
928 closed = true;
929 }
930 else {
931 ractor_queue_enq(r, &r->sync.recv_queue, b);
932 ractor_wakeup(r, wait_receiving, wakeup_by_send);
933 }
934 }
935 RACTOR_UNLOCK(r);
936
937 if (closed) {
938 rb_raise(rb_eRactorClosedError, "The incoming-port is already closed");
939 }
940}
941
942// Ractor#send
943
944static VALUE ractor_move(VALUE obj); // in this file
945static VALUE ractor_copy(VALUE obj); // in this file
946
947static void
948ractor_basket_prepare_contents(VALUE obj, VALUE move, volatile VALUE *pobj, enum rb_ractor_basket_type *ptype)
949{
950 VALUE v;
951 enum rb_ractor_basket_type type;
952
953 if (rb_ractor_shareable_p(obj)) {
954 type = basket_type_ref;
955 v = obj;
956 }
957 else if (!RTEST(move)) {
958 v = ractor_copy(obj);
959 type = basket_type_copy;
960 }
961 else {
962 type = basket_type_move;
963 v = ractor_move(obj);
964 }
965
966 *pobj = v;
967 *ptype = type;
968}
969
970static void
971ractor_basket_fill_(rb_ractor_t *cr, struct rb_ractor_basket *basket, VALUE obj, bool exc)
972{
973 VM_ASSERT(cr == GET_RACTOR());
974
975 basket->sender = cr->pub.self;
976 basket->p.send.exception = exc;
977 basket->p.send.v = obj;
978}
979
980static void
981ractor_basket_fill(rb_ractor_t *cr, struct rb_ractor_basket *basket, VALUE obj, VALUE move, bool exc)
982{
983 VALUE v;
984 enum rb_ractor_basket_type type;
985 ractor_basket_prepare_contents(obj, move, &v, &type);
986 ractor_basket_fill_(cr, basket, v, exc);
987 basket->type.e = type;
988}
989
990static void
991ractor_basket_fill_will(rb_ractor_t *cr, struct rb_ractor_basket *basket, VALUE obj, bool exc)
992{
993 ractor_basket_fill_(cr, basket, obj, exc);
994 basket->type.e = basket_type_will;
995}
996
997static VALUE
998ractor_send(rb_execution_context_t *ec, rb_ractor_t *r, VALUE obj, VALUE move)
999{
1000 struct rb_ractor_basket basket;
1001 // TODO: Ractor local GC
1002 ractor_basket_fill(rb_ec_ractor_ptr(ec), &basket, obj, move, false);
1003 ractor_send_basket(ec, r, &basket);
1004 return r->pub.self;
1005}
1006
1007// Ractor#take
1008
1009static bool
1010ractor_take_has_will(rb_ractor_t *r)
1011{
1012 ASSERT_ractor_locking(r);
1013
1014 return basket_type_p(&r->sync.will_basket, basket_type_will);
1015}
1016
1017static bool
1018ractor_take_will(rb_ractor_t *r, struct rb_ractor_basket *b)
1019{
1020 ASSERT_ractor_locking(r);
1021
1022 if (ractor_take_has_will(r)) {
1023 *b = r->sync.will_basket;
1024 r->sync.will_basket.type.e = basket_type_none;
1025 return true;
1026 }
1027 else {
1028 VM_ASSERT(basket_type_p(&r->sync.will_basket, basket_type_none));
1029 return false;
1030 }
1031}
1032
1033static bool
1034ractor_take_will_lock(rb_ractor_t *r, struct rb_ractor_basket *b)
1035{
1036 ASSERT_ractor_unlocking(r);
1037 bool taken;
1038
1039 RACTOR_LOCK(r);
1040 {
1041 taken = ractor_take_will(r, b);
1042 }
1043 RACTOR_UNLOCK(r);
1044
1045 return taken;
1046}
1047
1048static bool
1049ractor_register_take(rb_ractor_t *cr, rb_ractor_t *r, struct rb_ractor_basket *take_basket,
1050 bool is_take, struct rb_ractor_selector_take_config *config, bool ignore_error)
1051{
1052 struct rb_ractor_basket b = {
1053 .type.e = basket_type_take_basket,
1054 .sender = cr->pub.self,
1055 .p = {
1056 .take = {
1057 .basket = take_basket,
1058 .config = config,
1059 },
1060 },
1061 };
1062 bool closed = false;
1063
1064 RACTOR_LOCK(r);
1065 {
1066 if (is_take && ractor_take_will(r, take_basket)) {
1067 RUBY_DEBUG_LOG("take over a will of r:%d", rb_ractor_id(r));
1068 }
1069 else if (!is_take && ractor_take_has_will(r)) {
1070 RUBY_DEBUG_LOG("has_will");
1071 VM_ASSERT(config != NULL);
1072 config->closed = true;
1073 }
1074 else if (r->sync.outgoing_port_closed) {
1075 closed = true;
1076 }
1077 else {
1078 RUBY_DEBUG_LOG("register in r:%d", rb_ractor_id(r));
1079 ractor_queue_enq(r, &r->sync.takers_queue, &b);
1080
1081 if (basket_none_p(take_basket)) {
1082 ractor_wakeup(r, wait_yielding, wakeup_by_take);
1083 }
1084 }
1085 }
1086 RACTOR_UNLOCK(r);
1087
1088 if (closed) {
1089 if (!ignore_error) rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed");
1090 return false;
1091 }
1092 else {
1093 return true;
1094 }
1095}
1096
1097static bool
1098ractor_deregister_take(rb_ractor_t *r, struct rb_ractor_basket *take_basket)
1099{
1100 struct rb_ractor_queue *ts = &r->sync.takers_queue;
1101 bool deleted = false;
1102
1103 RACTOR_LOCK(r);
1104 {
1105 if (r->sync.outgoing_port_closed) {
1106 // ok
1107 }
1108 else {
1109 for (int i=0; i<ts->cnt; i++) {
1110 struct rb_ractor_basket *b = ractor_queue_at(r, ts, i);
1111 if (basket_type_p(b, basket_type_take_basket) && b->p.take.basket == take_basket) {
1112 ractor_queue_delete(r, ts, b);
1113 deleted = true;
1114 }
1115 }
1116 if (deleted) {
1117 ractor_queue_compact(r, ts);
1118 }
1119 }
1120 }
1121 RACTOR_UNLOCK(r);
1122
1123 return deleted;
1124}
1125
1126static VALUE
1127ractor_try_take(rb_ractor_t *cr, rb_ractor_t *r, struct rb_ractor_basket *take_basket)
1128{
1129 bool taken;
1130
1131 RACTOR_LOCK_SELF(cr);
1132 {
1133 if (basket_none_p(take_basket) || basket_type_p(take_basket, basket_type_yielding)) {
1134 taken = false;
1135 }
1136 else {
1137 taken = true;
1138 }
1139 }
1140 RACTOR_UNLOCK_SELF(cr);
1141
1142 if (taken) {
1143 RUBY_DEBUG_LOG("taken");
1144 if (basket_type_p(take_basket, basket_type_deleted)) {
1145 VM_ASSERT(r->sync.outgoing_port_closed);
1146 rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed");
1147 }
1148 return ractor_basket_accept(take_basket);
1149 }
1150 else {
1151 RUBY_DEBUG_LOG("not taken");
1152 return Qundef;
1153 }
1154}
1155
1156
1157#if VM_CHECK_MODE > 0
1158static bool
1159ractor_check_specific_take_basket_lock(rb_ractor_t *r, struct rb_ractor_basket *tb)
1160{
1161 bool ret = false;
1162 struct rb_ractor_queue *ts = &r->sync.takers_queue;
1163
1164 RACTOR_LOCK(r);
1165 {
1166 for (int i=0; i<ts->cnt; i++) {
1167 struct rb_ractor_basket *b = ractor_queue_at(r, ts, i);
1168 if (basket_type_p(b, basket_type_take_basket) && b->p.take.basket == tb) {
1169 ret = true;
1170 break;
1171 }
1172 }
1173 }
1174 RACTOR_UNLOCK(r);
1175
1176 return ret;
1177}
1178#endif
1179
1180static void
1181ractor_take_cleanup(rb_ractor_t *cr, rb_ractor_t *r, struct rb_ractor_basket *tb)
1182{
1183 retry:
1184 if (basket_none_p(tb)) { // not yielded yet
1185 if (!ractor_deregister_take(r, tb)) {
1186 // not in r's takers queue
1187 rb_thread_sleep(0);
1188 goto retry;
1189 }
1190 }
1191 else {
1192 VM_ASSERT(!ractor_check_specific_take_basket_lock(r, tb));
1193 }
1194}
1195
1197 rb_ractor_t *r;
1198 struct rb_ractor_basket *tb;
1199};
1200
1201static void
1202ractor_wait_take_cleanup(rb_ractor_t *cr, void *ptr)
1203{
1204 struct take_wait_take_cleanup_data *data = (struct take_wait_take_cleanup_data *)ptr;
1205 ractor_take_cleanup(cr, data->r, data->tb);
1206}
1207
1208static void
1209ractor_wait_take(rb_execution_context_t *ec, rb_ractor_t *cr, rb_ractor_t *r, struct rb_ractor_basket *take_basket)
1210{
1211 struct take_wait_take_cleanup_data data = {
1212 .r = r,
1213 .tb = take_basket,
1214 };
1215
1216 RACTOR_LOCK_SELF(cr);
1217 {
1218 if (basket_none_p(take_basket) || basket_type_p(take_basket, basket_type_yielding)) {
1219 ractor_sleep_with_cleanup(ec, cr, wait_taking, ractor_wait_take_cleanup, &data);
1220 }
1221 }
1222 RACTOR_UNLOCK_SELF(cr);
1223}
1224
1225static VALUE
1226ractor_take(rb_execution_context_t *ec, rb_ractor_t *r)
1227{
1228 RUBY_DEBUG_LOG("from r:%u", rb_ractor_id(r));
1229 VALUE v;
1230 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
1231
1232 struct rb_ractor_basket take_basket = {
1233 .type.e = basket_type_none,
1234 .sender = 0,
1235 };
1236
1237 ractor_register_take(cr, r, &take_basket, true, NULL, false);
1238
1239 while (UNDEF_P(v = ractor_try_take(cr, r, &take_basket))) {
1240 ractor_wait_take(ec, cr, r, &take_basket);
1241 }
1242
1243 VM_ASSERT(!basket_none_p(&take_basket));
1244 VM_ASSERT(!ractor_check_specific_take_basket_lock(r, &take_basket));
1245
1246 return v;
1247}
1248
1249// Ractor.yield
1250
1251static bool
1252ractor_check_take_basket(rb_ractor_t *cr, struct rb_ractor_queue *rs)
1253{
1254 ASSERT_ractor_locking(cr);
1255
1256 for (int i=0; i<rs->cnt; i++) {
1257 struct rb_ractor_basket *b = ractor_queue_at(cr, rs, i);
1258 if (basket_type_p(b, basket_type_take_basket) &&
1259 basket_none_p(b->p.take.basket)) {
1260 return true;
1261 }
1262 }
1263
1264 return false;
1265}
1266
1267static bool
1268ractor_deq_take_basket(rb_ractor_t *cr, struct rb_ractor_queue *rs, struct rb_ractor_basket *b)
1269{
1270 ASSERT_ractor_unlocking(cr);
1271 struct rb_ractor_basket *first_tb = NULL;
1272 bool found = false;
1273
1274 RACTOR_LOCK_SELF(cr);
1275 {
1276 while (ractor_queue_deq(cr, rs, b)) {
1277 if (basket_type_p(b, basket_type_take_basket)) {
1278 struct rb_ractor_basket *tb = b->p.take.basket;
1279
1280 if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_yielding) == basket_type_none) {
1281 found = true;
1282 break;
1283 }
1284 else {
1285 ractor_queue_enq(cr, rs, b);
1286 if (first_tb == NULL) first_tb = tb;
1287 struct rb_ractor_basket *head = ractor_queue_head(cr, rs);
1288 VM_ASSERT(head != NULL);
1289 if (basket_type_p(head, basket_type_take_basket) && head->p.take.basket == first_tb) {
1290 break; // loop detected
1291 }
1292 }
1293 }
1294 else {
1295 VM_ASSERT(basket_none_p(b));
1296 }
1297 }
1298
1299 if (found && b->p.take.config && !b->p.take.config->oneshot) {
1300 ractor_queue_enq(cr, rs, b);
1301 }
1302 }
1303 RACTOR_UNLOCK_SELF(cr);
1304
1305 return found;
1306}
1307
1308static bool
1309ractor_try_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *ts, volatile VALUE obj, VALUE move, bool exc, bool is_will)
1310{
1311 ASSERT_ractor_unlocking(cr);
1312
1313 struct rb_ractor_basket b;
1314
1315 if (ractor_deq_take_basket(cr, ts, &b)) {
1316 VM_ASSERT(basket_type_p(&b, basket_type_take_basket));
1317 VM_ASSERT(basket_type_p(b.p.take.basket, basket_type_yielding));
1318
1319 rb_ractor_t *tr = RACTOR_PTR(b.sender);
1320 struct rb_ractor_basket *tb = b.p.take.basket;
1321 enum rb_ractor_basket_type type;
1322
1323 RUBY_DEBUG_LOG("basket from r:%u", rb_ractor_id(tr));
1324
1325 if (is_will) {
1326 type = basket_type_will;
1327 }
1328 else {
1329 enum ruby_tag_type state;
1330
1331 // begin
1332 EC_PUSH_TAG(ec);
1333 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
1334 // TODO: Ractor local GC
1335 ractor_basket_prepare_contents(obj, move, &obj, &type);
1336 }
1337 EC_POP_TAG();
1338 // rescue
1339 if (state) {
1340 RACTOR_LOCK_SELF(cr);
1341 {
1342 b.p.take.basket->type.e = basket_type_none;
1343 ractor_queue_enq(cr, ts, &b);
1344 }
1345 RACTOR_UNLOCK_SELF(cr);
1346 EC_JUMP_TAG(ec, state);
1347 }
1348 }
1349
1350 RACTOR_LOCK(tr);
1351 {
1352 VM_ASSERT(basket_type_p(tb, basket_type_yielding));
1353 // fill atomic
1354 RUBY_DEBUG_LOG("fill %sbasket from r:%u", is_will ? "will " : "", rb_ractor_id(tr));
1355 ractor_basket_fill_(cr, tb, obj, exc);
1356 if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_yielding, type) != basket_type_yielding) {
1357 rb_bug("unreachable");
1358 }
1359 ractor_wakeup(tr, wait_taking, wakeup_by_yield);
1360 }
1361 RACTOR_UNLOCK(tr);
1362
1363 return true;
1364 }
1365 else if (cr->sync.outgoing_port_closed) {
1366 rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed");
1367 }
1368 else {
1369 RUBY_DEBUG_LOG("no take basket");
1370 return false;
1371 }
1372}
1373
1374static void
1375ractor_wait_yield(rb_execution_context_t *ec, rb_ractor_t *cr, struct rb_ractor_queue *ts)
1376{
1377 RACTOR_LOCK_SELF(cr);
1378 {
1379 while (!ractor_check_take_basket(cr, ts) && !cr->sync.outgoing_port_closed) {
1380 ractor_sleep(ec, cr, wait_yielding);
1381 }
1382 }
1383 RACTOR_UNLOCK_SELF(cr);
1384}
1385
1386static VALUE
1387ractor_yield(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE obj, VALUE move)
1388{
1389 struct rb_ractor_queue *ts = &cr->sync.takers_queue;
1390
1391 while (!ractor_try_yield(ec, cr, ts, obj, move, false, false)) {
1392 ractor_wait_yield(ec, cr, ts);
1393 }
1394
1395 return Qnil;
1396}
1397
1398// Ractor::Selector
1399
1401 rb_ractor_t *r;
1402 struct rb_ractor_basket take_basket;
1403 st_table *take_ractors; // rb_ractor_t * => (struct rb_ractor_selector_take_config *)
1404};
1405
1406static int
1407ractor_selector_mark_ractors_i(st_data_t key, st_data_t value, st_data_t data)
1408{
1409 const rb_ractor_t *r = (rb_ractor_t *)key;
1410 rb_gc_mark(r->pub.self);
1411 return ST_CONTINUE;
1412}
1413
1414static void
1415ractor_selector_mark(void *ptr)
1416{
1417 struct rb_ractor_selector *s = ptr;
1418
1419 if (s->take_ractors) {
1420 st_foreach(s->take_ractors, ractor_selector_mark_ractors_i, 0);
1421 }
1422
1423 switch (s->take_basket.type.e) {
1424 case basket_type_ref:
1425 case basket_type_copy:
1426 case basket_type_move:
1427 case basket_type_will:
1428 rb_gc_mark(s->take_basket.sender);
1429 rb_gc_mark(s->take_basket.p.send.v);
1430 break;
1431 default:
1432 break;
1433 }
1434}
1435
1436static int
1437ractor_selector_release_i(st_data_t key, st_data_t val, st_data_t data)
1438{
1439 struct rb_ractor_selector *s = (struct rb_ractor_selector *)data;
1441
1442 if (!config->closed) {
1443 ractor_deregister_take((rb_ractor_t *)key, &s->take_basket);
1444 }
1445 free(config);
1446 return ST_CONTINUE;
1447}
1448
1449static void
1450ractor_selector_free(void *ptr)
1451{
1452 struct rb_ractor_selector *s = ptr;
1453 st_foreach(s->take_ractors, ractor_selector_release_i, (st_data_t)s);
1454 st_free_table(s->take_ractors);
1455 ruby_xfree(ptr);
1456}
1457
1458static size_t
1459ractor_selector_memsize(const void *ptr)
1460{
1461 const struct rb_ractor_selector *s = ptr;
1462 return sizeof(struct rb_ractor_selector) +
1463 st_memsize(s->take_ractors) +
1464 s->take_ractors->num_entries * sizeof(struct rb_ractor_selector_take_config);
1465}
1466
1467static const rb_data_type_t ractor_selector_data_type = {
1468 "ractor/selector",
1469 {
1470 ractor_selector_mark,
1471 ractor_selector_free,
1472 ractor_selector_memsize,
1473 NULL, // update
1474 },
1475 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
1476};
1477
1478static struct rb_ractor_selector *
1479RACTOR_SELECTOR_PTR(VALUE selv)
1480{
1481 VM_ASSERT(rb_typeddata_is_kind_of(selv, &ractor_selector_data_type));
1482
1483 return (struct rb_ractor_selector *)DATA_PTR(selv);
1484}
1485
1486// Ractor::Selector.new
1487
1488static VALUE
1489ractor_selector_create(VALUE klass)
1490{
1491 struct rb_ractor_selector *s;
1492 VALUE selv = TypedData_Make_Struct(klass, struct rb_ractor_selector, &ractor_selector_data_type, s);
1493 s->take_basket.type.e = basket_type_reserved;
1494 s->take_ractors = st_init_numtable(); // ractor (ptr) -> take_config
1495 return selv;
1496}
1497
1498// Ractor::Selector#add(r)
1499
1500/*
1501 * call-seq:
1502 * add(ractor) -> ractor
1503 *
1504 * Adds _ractor_ to +self+. Raises an exception if _ractor_ is already added.
1505 * Returns _ractor_.
1506 */
1507static VALUE
1508ractor_selector_add(VALUE selv, VALUE rv)
1509{
1510 if (!rb_ractor_p(rv)) {
1511 rb_raise(rb_eArgError, "Not a ractor object");
1512 }
1513
1514 rb_ractor_t *r = RACTOR_PTR(rv);
1515 struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1516
1517 if (st_lookup(s->take_ractors, (st_data_t)r, NULL)) {
1518 rb_raise(rb_eArgError, "already added");
1519 }
1520
1521 struct rb_ractor_selector_take_config *config = malloc(sizeof(struct rb_ractor_selector_take_config));
1522 VM_ASSERT(config != NULL);
1523 config->closed = false;
1524 config->oneshot = false;
1525
1526 if (ractor_register_take(GET_RACTOR(), r, &s->take_basket, false, config, true)) {
1527 st_insert(s->take_ractors, (st_data_t)r, (st_data_t)config);
1528 }
1529
1530 return rv;
1531}
1532
1533// Ractor::Selector#remove(r)
1534
1535/* call-seq:
1536 * remove(ractor) -> ractor
1537 *
1538 * Removes _ractor_ from +self+. Raises an exception if _ractor_ is not added.
1539 * Returns the removed _ractor_.
1540 */
1541static VALUE
1542ractor_selector_remove(VALUE selv, VALUE rv)
1543{
1544 if (!rb_ractor_p(rv)) {
1545 rb_raise(rb_eArgError, "Not a ractor object");
1546 }
1547
1548 rb_ractor_t *r = RACTOR_PTR(rv);
1549 struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1550
1551 RUBY_DEBUG_LOG("r:%u", rb_ractor_id(r));
1552
1553 if (!st_lookup(s->take_ractors, (st_data_t)r, NULL)) {
1554 rb_raise(rb_eArgError, "not added yet");
1555 }
1556
1557 ractor_deregister_take(r, &s->take_basket);
1558 struct rb_ractor_selector_take_config *config;
1559 st_delete(s->take_ractors, (st_data_t *)&r, (st_data_t *)&config);
1560 free(config);
1561
1562 return rv;
1563}
1564
1565// Ractor::Selector#clear
1566
1568 VALUE selv;
1570};
1571
1572static int
1573ractor_selector_clear_i(st_data_t key, st_data_t val, st_data_t data)
1574{
1575 VALUE selv = (VALUE)data;
1576 rb_ractor_t *r = (rb_ractor_t *)key;
1577 ractor_selector_remove(selv, r->pub.self);
1578 return ST_CONTINUE;
1579}
1580
1581/*
1582 * call-seq:
1583 * clear -> self
1584 *
1585 * Removes all ractors from +self+. Raises +self+.
1586 */
1587static VALUE
1588ractor_selector_clear(VALUE selv)
1589{
1590 struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1591
1592 st_foreach(s->take_ractors, ractor_selector_clear_i, (st_data_t)selv);
1593 st_clear(s->take_ractors);
1594 return selv;
1595}
1596
1597/*
1598 * call-seq:
1599 * empty? -> true or false
1600 *
1601 * Returns +true+ if no ractor is added.
1602 */
1603static VALUE
1604ractor_selector_empty_p(VALUE selv)
1605{
1606 struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1607 return s->take_ractors->num_entries == 0 ? Qtrue : Qfalse;
1608}
1609
1610static int
1611ractor_selector_wait_i(st_data_t key, st_data_t val, st_data_t dat)
1612{
1613 rb_ractor_t *r = (rb_ractor_t *)key;
1614 struct rb_ractor_basket *tb = (struct rb_ractor_basket *)dat;
1615 int ret;
1616
1617 if (!basket_none_p(tb)) {
1618 RUBY_DEBUG_LOG("already taken:%s", basket_type_name(tb->type.e));
1619 return ST_STOP;
1620 }
1621
1622 RACTOR_LOCK(r);
1623 {
1624 if (basket_type_p(&r->sync.will_basket, basket_type_will)) {
1625 RUBY_DEBUG_LOG("r:%u has will", rb_ractor_id(r));
1626
1627 if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_will) == basket_type_none) {
1628 ractor_take_will(r, tb);
1629 ret = ST_STOP;
1630 }
1631 else {
1632 RUBY_DEBUG_LOG("has will, but already taken (%s)", basket_type_name(tb->type.e));
1633 ret = ST_CONTINUE;
1634 }
1635 }
1636 else if (r->sync.outgoing_port_closed) {
1637 RUBY_DEBUG_LOG("r:%u is closed", rb_ractor_id(r));
1638
1639 if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_deleted) == basket_type_none) {
1640 tb->sender = r->pub.self;
1641 ret = ST_STOP;
1642 }
1643 else {
1644 RUBY_DEBUG_LOG("closed, but already taken (%s)", basket_type_name(tb->type.e));
1645 ret = ST_CONTINUE;
1646 }
1647 }
1648 else {
1649 RUBY_DEBUG_LOG("wakeup r:%u", rb_ractor_id(r));
1650 ractor_wakeup(r, wait_yielding, wakeup_by_take);
1651 ret = ST_CONTINUE;
1652 }
1653 }
1654 RACTOR_UNLOCK(r);
1655
1656 return ret;
1657}
1658
1659// Ractor::Selector#wait
1660
1661static void
1662ractor_selector_wait_cleaup(rb_ractor_t *cr, void *ptr)
1663{
1664 struct rb_ractor_basket *tb = (struct rb_ractor_basket *)ptr;
1665
1666 RACTOR_LOCK_SELF(cr);
1667 {
1668 while (basket_type_p(tb, basket_type_yielding)) rb_thread_sleep(0);
1669 // if tb->type is not none, taking is succeeded, but interruption ignore it unfortunately.
1670 tb->type.e = basket_type_reserved;
1671 }
1672 RACTOR_UNLOCK_SELF(cr);
1673}
1674
1675/* :nodoc: */
1676static VALUE
1677ractor_selector__wait(VALUE selv, VALUE do_receivev, VALUE do_yieldv, VALUE yield_value, VALUE move)
1678{
1679 rb_execution_context_t *ec = GET_EC();
1680 struct rb_ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1681 struct rb_ractor_basket *tb = &s->take_basket;
1682 struct rb_ractor_basket taken_basket;
1683 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
1684 bool do_receive = !!RTEST(do_receivev);
1685 bool do_yield = !!RTEST(do_yieldv);
1686 VALUE ret_v, ret_r;
1687 enum rb_ractor_wait_status wait_status;
1688 struct rb_ractor_queue *rq = &cr->sync.recv_queue;
1689 struct rb_ractor_queue *ts = &cr->sync.takers_queue;
1690
1691 RUBY_DEBUG_LOG("start");
1692
1693 retry:
1694 RUBY_DEBUG_LOG("takers:%ld", s->take_ractors->num_entries);
1695
1696 // setup wait_status
1697 wait_status = wait_none;
1698 if (s->take_ractors->num_entries > 0) wait_status |= wait_taking;
1699 if (do_receive) wait_status |= wait_receiving;
1700 if (do_yield) wait_status |= wait_yielding;
1701
1702 RUBY_DEBUG_LOG("wait:%s", wait_status_str(wait_status));
1703
1704 if (wait_status == wait_none) {
1705 rb_raise(rb_eRactorError, "no taking ractors");
1706 }
1707
1708 // check recv_queue
1709 if (do_receive && !UNDEF_P(ret_v = ractor_try_receive(ec, cr, rq))) {
1710 ret_r = ID2SYM(rb_intern("receive"));
1711 goto success;
1712 }
1713
1714 // check takers
1715 if (do_yield && ractor_try_yield(ec, cr, ts, yield_value, move, false, false)) {
1716 ret_v = Qnil;
1717 ret_r = ID2SYM(rb_intern("yield"));
1718 goto success;
1719 }
1720
1721 // check take_basket
1722 VM_ASSERT(basket_type_p(&s->take_basket, basket_type_reserved));
1723 s->take_basket.type.e = basket_type_none;
1724 // kick all take target ractors
1725 st_foreach(s->take_ractors, ractor_selector_wait_i, (st_data_t)tb);
1726
1727 RACTOR_LOCK_SELF(cr);
1728 {
1729 retry_waiting:
1730 while (1) {
1731 if (!basket_none_p(tb)) {
1732 RUBY_DEBUG_LOG("taken:%s from r:%u", basket_type_name(tb->type.e),
1733 tb->sender ? rb_ractor_id(RACTOR_PTR(tb->sender)) : 0);
1734 break;
1735 }
1736 if (do_receive && !ractor_queue_empty_p(cr, rq)) {
1737 RUBY_DEBUG_LOG("can receive (%d)", rq->cnt);
1738 break;
1739 }
1740 if (do_yield && ractor_check_take_basket(cr, ts)) {
1741 RUBY_DEBUG_LOG("can yield");
1742 break;
1743 }
1744
1745 ractor_sleep_with_cleanup(ec, cr, wait_status, ractor_selector_wait_cleaup, tb);
1746 }
1747
1748 taken_basket = *tb;
1749
1750 // ensure
1751 // tb->type.e = basket_type_reserved # do it atomic in the following code
1752 if (taken_basket.type.e == basket_type_yielding ||
1753 RUBY_ATOMIC_CAS(tb->type.atomic, taken_basket.type.e, basket_type_reserved) != taken_basket.type.e) {
1754
1755 if (basket_type_p(tb, basket_type_yielding)) {
1756 RACTOR_UNLOCK_SELF(cr);
1757 {
1758 rb_thread_sleep(0);
1759 }
1760 RACTOR_LOCK_SELF(cr);
1761 }
1762 goto retry_waiting;
1763 }
1764 }
1765 RACTOR_UNLOCK_SELF(cr);
1766
1767 // check the taken result
1768 switch (taken_basket.type.e) {
1769 case basket_type_none:
1770 VM_ASSERT(do_receive || do_yield);
1771 goto retry;
1772 case basket_type_yielding:
1773 rb_bug("unreachable");
1774 case basket_type_deleted: {
1775 ractor_selector_remove(selv, taken_basket.sender);
1776
1777 rb_ractor_t *r = RACTOR_PTR(taken_basket.sender);
1778 if (ractor_take_will_lock(r, &taken_basket)) {
1779 RUBY_DEBUG_LOG("has_will");
1780 }
1781 else {
1782 RUBY_DEBUG_LOG("no will");
1783 // rb_raise(rb_eRactorClosedError, "The outgoing-port is already closed");
1784 // remove and retry wait
1785 goto retry;
1786 }
1787 break;
1788 }
1789 case basket_type_will:
1790 // no more messages
1791 ractor_selector_remove(selv, taken_basket.sender);
1792 break;
1793 default:
1794 break;
1795 }
1796
1797 RUBY_DEBUG_LOG("taken_basket:%s", basket_type_name(taken_basket.type.e));
1798
1799 ret_v = ractor_basket_accept(&taken_basket);
1800 ret_r = taken_basket.sender;
1801 success:
1802 return rb_ary_new_from_args(2, ret_r, ret_v);
1803}
1804
1805/*
1806 * call-seq:
1807 * wait(receive: false, yield_value: undef, move: false) -> [ractor, value]
1808 *
1809 * Waits until any ractor in _selector_ can be active.
1810 */
1811static VALUE
1812ractor_selector_wait(int argc, VALUE *argv, VALUE selector)
1813{
1814 VALUE options;
1815 ID keywords[3];
1816 VALUE values[3];
1817
1818 keywords[0] = rb_intern("receive");
1819 keywords[1] = rb_intern("yield_value");
1820 keywords[2] = rb_intern("move");
1821
1822 rb_scan_args(argc, argv, "0:", &options);
1823 rb_get_kwargs(options, keywords, 0, numberof(values), values);
1824 return ractor_selector__wait(selector,
1825 values[0] == Qundef ? Qfalse : RTEST(values[0]),
1826 values[1] != Qundef, values[1], values[2]);
1827}
1828
1829static VALUE
1830ractor_selector_new(int argc, VALUE *ractors, VALUE klass)
1831{
1832 VALUE selector = ractor_selector_create(klass);
1833
1834 for (int i=0; i<argc; i++) {
1835 ractor_selector_add(selector, ractors[i]);
1836 }
1837
1838 return selector;
1839}
1840
1841static VALUE
1842ractor_select_internal(rb_execution_context_t *ec, VALUE self, VALUE ractors, VALUE do_receive, VALUE do_yield, VALUE yield_value, VALUE move)
1843{
1844 VALUE selector = ractor_selector_new(RARRAY_LENINT(ractors), (VALUE *)RARRAY_CONST_PTR(ractors), rb_cRactorSelector);
1845 VALUE result;
1846 int state;
1847
1848 EC_PUSH_TAG(ec);
1849 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
1850 result = ractor_selector__wait(selector, do_receive, do_yield, yield_value, move);
1851 }
1852 EC_POP_TAG();
1853 if (state != TAG_NONE) {
1854 // ensure
1855 ractor_selector_clear(selector);
1856
1857 // jump
1858 EC_JUMP_TAG(ec, state);
1859 }
1860
1861 RB_GC_GUARD(ractors);
1862 return result;
1863}
1864
1865// Ractor#close_incoming
1866
1867static VALUE
1868ractor_close_incoming(rb_execution_context_t *ec, rb_ractor_t *r)
1869{
1870 VALUE prev;
1871
1872 RACTOR_LOCK(r);
1873 {
1874 if (!r->sync.incoming_port_closed) {
1875 prev = Qfalse;
1876 r->sync.incoming_port_closed = true;
1877 if (ractor_wakeup(r, wait_receiving, wakeup_by_close)) {
1878 VM_ASSERT(ractor_queue_empty_p(r, &r->sync.recv_queue));
1879 RUBY_DEBUG_LOG("cancel receiving");
1880 }
1881 }
1882 else {
1883 prev = Qtrue;
1884 }
1885 }
1886 RACTOR_UNLOCK(r);
1887 return prev;
1888}
1889
1890// Ractor#close_outgoing
1891
1892static VALUE
1893ractor_close_outgoing(rb_execution_context_t *ec, rb_ractor_t *r)
1894{
1895 VALUE prev;
1896
1897 RACTOR_LOCK(r);
1898 {
1899 struct rb_ractor_queue *ts = &r->sync.takers_queue;
1900 rb_ractor_t *tr;
1901 struct rb_ractor_basket b;
1902
1903 if (!r->sync.outgoing_port_closed) {
1904 prev = Qfalse;
1905 r->sync.outgoing_port_closed = true;
1906 }
1907 else {
1908 VM_ASSERT(ractor_queue_empty_p(r, ts));
1909 prev = Qtrue;
1910 }
1911
1912 // wakeup all taking ractors
1913 while (ractor_queue_deq(r, ts, &b)) {
1914 if (basket_type_p(&b, basket_type_take_basket)) {
1915 tr = RACTOR_PTR(b.sender);
1916 struct rb_ractor_basket *tb = b.p.take.basket;
1917
1918 if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_none, basket_type_yielding) == basket_type_none) {
1919 b.p.take.basket->sender = r->pub.self;
1920 if (RUBY_ATOMIC_CAS(tb->type.atomic, basket_type_yielding, basket_type_deleted) != basket_type_yielding) {
1921 rb_bug("unreachable");
1922 }
1923 RUBY_DEBUG_LOG("set delete for r:%u", rb_ractor_id(RACTOR_PTR(b.sender)));
1924 }
1925
1926 if (b.p.take.config) {
1927 b.p.take.config->closed = true;
1928 }
1929
1930 // TODO: deadlock-able?
1931 RACTOR_LOCK(tr);
1932 {
1933 ractor_wakeup(tr, wait_taking, wakeup_by_close);
1934 }
1935 RACTOR_UNLOCK(tr);
1936 }
1937 }
1938
1939 // raising yielding Ractor
1940 ractor_wakeup(r, wait_yielding, wakeup_by_close);
1941
1942 VM_ASSERT(ractor_queue_empty_p(r, ts));
1943 }
1944 RACTOR_UNLOCK(r);
1945 return prev;
1946}
1947
1948// creation/termination
1949
1950static uint32_t
1951ractor_next_id(void)
1952{
1953 uint32_t id;
1954
1955 id = (uint32_t)(RUBY_ATOMIC_FETCH_ADD(ractor_last_id, 1) + 1);
1956
1957 return id;
1958}
1959
1960static void
1961vm_insert_ractor0(rb_vm_t *vm, rb_ractor_t *r, bool single_ractor_mode)
1962{
1963 RUBY_DEBUG_LOG("r:%u ractor.cnt:%u++", r->pub.id, vm->ractor.cnt);
1964 VM_ASSERT(single_ractor_mode || RB_VM_LOCKED_P());
1965
1966 ccan_list_add_tail(&vm->ractor.set, &r->vmlr_node);
1967 vm->ractor.cnt++;
1968
1969 if (r->newobj_cache) {
1970 VM_ASSERT(r == ruby_single_main_ractor);
1971 }
1972 else {
1973 r->newobj_cache = rb_gc_ractor_cache_alloc(r);
1974 }
1975}
1976
1977static void
1978cancel_single_ractor_mode(void)
1979{
1980 // enable multi-ractor mode
1981 RUBY_DEBUG_LOG("enable multi-ractor mode");
1982
1983 ruby_single_main_ractor = NULL;
1984 rb_funcall(rb_cRactor, rb_intern("_activated"), 0);
1985}
1986
1987static void
1988vm_insert_ractor(rb_vm_t *vm, rb_ractor_t *r)
1989{
1990 VM_ASSERT(ractor_status_p(r, ractor_created));
1991
1992 if (rb_multi_ractor_p()) {
1993 RB_VM_LOCK();
1994 {
1995 vm_insert_ractor0(vm, r, false);
1996 vm_ractor_blocking_cnt_inc(vm, r, __FILE__, __LINE__);
1997 }
1998 RB_VM_UNLOCK();
1999 }
2000 else {
2001 if (vm->ractor.cnt == 0) {
2002 // main ractor
2003 vm_insert_ractor0(vm, r, true);
2004 ractor_status_set(r, ractor_blocking);
2005 ractor_status_set(r, ractor_running);
2006 }
2007 else {
2008 cancel_single_ractor_mode();
2009 vm_insert_ractor0(vm, r, true);
2010 vm_ractor_blocking_cnt_inc(vm, r, __FILE__, __LINE__);
2011 }
2012 }
2013}
2014
2015static void
2016vm_remove_ractor(rb_vm_t *vm, rb_ractor_t *cr)
2017{
2018 VM_ASSERT(ractor_status_p(cr, ractor_running));
2019 VM_ASSERT(vm->ractor.cnt > 1);
2020 VM_ASSERT(cr->threads.cnt == 1);
2021
2022 RB_VM_LOCK();
2023 {
2024 RUBY_DEBUG_LOG("ractor.cnt:%u-- terminate_waiting:%d",
2025 vm->ractor.cnt, vm->ractor.sync.terminate_waiting);
2026
2027 VM_ASSERT(vm->ractor.cnt > 0);
2028 ccan_list_del(&cr->vmlr_node);
2029
2030 if (vm->ractor.cnt <= 2 && vm->ractor.sync.terminate_waiting) {
2031 rb_native_cond_signal(&vm->ractor.sync.terminate_cond);
2032 }
2033 vm->ractor.cnt--;
2034
2035 rb_gc_ractor_cache_free(cr->newobj_cache);
2036 cr->newobj_cache = NULL;
2037
2038 ractor_status_set(cr, ractor_terminated);
2039 }
2040 RB_VM_UNLOCK();
2041}
2042
2043static VALUE
2044ractor_alloc(VALUE klass)
2045{
2046 rb_ractor_t *r;
2047 VALUE rv = TypedData_Make_Struct(klass, rb_ractor_t, &ractor_data_type, r);
2049 r->pub.self = rv;
2050 VM_ASSERT(ractor_status_p(r, ractor_created));
2051 return rv;
2052}
2053
2055rb_ractor_main_alloc(void)
2056{
2057 rb_ractor_t *r = ruby_mimcalloc(1, sizeof(rb_ractor_t));
2058 if (r == NULL) {
2059 fprintf(stderr, "[FATAL] failed to allocate memory for main ractor\n");
2060 exit(EXIT_FAILURE);
2061 }
2062 r->pub.id = ++ractor_last_id;
2063 r->loc = Qnil;
2064 r->name = Qnil;
2065 r->pub.self = Qnil;
2066 r->newobj_cache = rb_gc_ractor_cache_alloc(r);
2067 ruby_single_main_ractor = r;
2068
2069 return r;
2070}
2071
2072#if defined(HAVE_WORKING_FORK)
2073void
2074rb_ractor_atfork(rb_vm_t *vm, rb_thread_t *th)
2075{
2076 // initialize as a main ractor
2077 vm->ractor.cnt = 0;
2078 vm->ractor.blocking_cnt = 0;
2079 ruby_single_main_ractor = th->ractor;
2080 th->ractor->status_ = ractor_created;
2081
2082 rb_ractor_living_threads_init(th->ractor);
2083 rb_ractor_living_threads_insert(th->ractor, th);
2084
2085 VM_ASSERT(vm->ractor.blocking_cnt == 0);
2086 VM_ASSERT(vm->ractor.cnt == 1);
2087}
2088#endif
2089
2090void rb_thread_sched_init(struct rb_thread_sched *, bool atfork);
2091
2092void
2093rb_ractor_living_threads_init(rb_ractor_t *r)
2094{
2095 ccan_list_head_init(&r->threads.set);
2096 r->threads.cnt = 0;
2097 r->threads.blocking_cnt = 0;
2098}
2099
2100static void
2101ractor_init(rb_ractor_t *r, VALUE name, VALUE loc)
2102{
2103 ractor_queue_setup(&r->sync.recv_queue);
2104 ractor_queue_setup(&r->sync.takers_queue);
2105 rb_native_mutex_initialize(&r->sync.lock);
2106 rb_native_cond_initialize(&r->barrier_wait_cond);
2107
2108#ifdef RUBY_THREAD_WIN32_H
2109 rb_native_cond_initialize(&r->sync.cond);
2110 rb_native_cond_initialize(&r->barrier_wait_cond);
2111#endif
2112
2113 // thread management
2114 rb_thread_sched_init(&r->threads.sched, false);
2115 rb_ractor_living_threads_init(r);
2116
2117 // naming
2118 if (!NIL_P(name)) {
2119 rb_encoding *enc;
2120 StringValueCStr(name);
2121 enc = rb_enc_get(name);
2122 if (!rb_enc_asciicompat(enc)) {
2123 rb_raise(rb_eArgError, "ASCII incompatible encoding (%s)",
2124 rb_enc_name(enc));
2125 }
2126 name = rb_str_new_frozen(name);
2127 }
2128 r->name = name;
2129 r->loc = loc;
2130}
2131
2132void
2133rb_ractor_main_setup(rb_vm_t *vm, rb_ractor_t *r, rb_thread_t *th)
2134{
2135 r->pub.self = TypedData_Wrap_Struct(rb_cRactor, &ractor_data_type, r);
2136 FL_SET_RAW(r->pub.self, RUBY_FL_SHAREABLE);
2137 ractor_init(r, Qnil, Qnil);
2138 r->threads.main = th;
2139 rb_ractor_living_threads_insert(r, th);
2140}
2141
2142static VALUE
2143ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VALUE args, VALUE block)
2144{
2145 VALUE rv = ractor_alloc(self);
2146 rb_ractor_t *r = RACTOR_PTR(rv);
2147 ractor_init(r, name, loc);
2148
2149 // can block here
2150 r->pub.id = ractor_next_id();
2151 RUBY_DEBUG_LOG("r:%u", r->pub.id);
2152
2153 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
2154 r->verbose = cr->verbose;
2155 r->debug = cr->debug;
2156
2157 rb_yjit_before_ractor_spawn();
2158 rb_thread_create_ractor(r, args, block);
2159
2160 RB_GC_GUARD(rv);
2161 return rv;
2162}
2163
2164static VALUE
2165ractor_create_func(VALUE klass, VALUE loc, VALUE name, VALUE args, rb_block_call_func_t func)
2166{
2167 VALUE block = rb_proc_new(func, Qnil);
2168 return ractor_create(rb_current_ec_noinline(), klass, loc, name, args, block);
2169}
2170
2171static void
2172ractor_yield_atexit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE v, bool exc)
2173{
2174 if (cr->sync.outgoing_port_closed) {
2175 return;
2176 }
2177
2178 ASSERT_ractor_unlocking(cr);
2179
2180 struct rb_ractor_queue *ts = &cr->sync.takers_queue;
2181
2182 retry:
2183 if (ractor_try_yield(ec, cr, ts, v, Qfalse, exc, true)) {
2184 // OK.
2185 }
2186 else {
2187 bool retry = false;
2188 RACTOR_LOCK(cr);
2189 {
2190 if (!ractor_check_take_basket(cr, ts)) {
2191 VM_ASSERT(cr->sync.wait.status == wait_none);
2192 RUBY_DEBUG_LOG("leave a will");
2193 ractor_basket_fill_will(cr, &cr->sync.will_basket, v, exc);
2194 }
2195 else {
2196 RUBY_DEBUG_LOG("rare timing!");
2197 retry = true; // another ractor is waiting for the yield.
2198 }
2199 }
2200 RACTOR_UNLOCK(cr);
2201
2202 if (retry) goto retry;
2203 }
2204}
2205
2206void
2207rb_ractor_atexit(rb_execution_context_t *ec, VALUE result)
2208{
2209 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
2210 ractor_yield_atexit(ec, cr, result, false);
2211}
2212
2213void
2214rb_ractor_atexit_exception(rb_execution_context_t *ec)
2215{
2216 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
2217 ractor_yield_atexit(ec, cr, ec->errinfo, true);
2218}
2219
2220void
2221rb_ractor_teardown(rb_execution_context_t *ec)
2222{
2223 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
2224 ractor_close_incoming(ec, cr);
2225 ractor_close_outgoing(ec, cr);
2226
2227 // sync with rb_ractor_terminate_interrupt_main_thread()
2228 RB_VM_LOCK_ENTER();
2229 {
2230 VM_ASSERT(cr->threads.main != NULL);
2231 cr->threads.main = NULL;
2232 }
2233 RB_VM_LOCK_LEAVE();
2234}
2235
2236void
2237rb_ractor_receive_parameters(rb_execution_context_t *ec, rb_ractor_t *r, int len, VALUE *ptr)
2238{
2239 for (int i=0; i<len; i++) {
2240 ptr[i] = ractor_receive(ec, r);
2241 }
2242}
2243
2244void
2245rb_ractor_send_parameters(rb_execution_context_t *ec, rb_ractor_t *r, VALUE args)
2246{
2247 int len = RARRAY_LENINT(args);
2248 for (int i=0; i<len; i++) {
2249 ractor_send(ec, r, RARRAY_AREF(args, i), false);
2250 }
2251}
2252
2253bool
2254rb_ractor_main_p_(void)
2255{
2256 VM_ASSERT(rb_multi_ractor_p());
2257 rb_execution_context_t *ec = GET_EC();
2258 return rb_ec_ractor_ptr(ec) == rb_ec_vm_ptr(ec)->ractor.main_ractor;
2259}
2260
2261bool
2262rb_obj_is_main_ractor(VALUE gv)
2263{
2264 if (!rb_ractor_p(gv)) return false;
2265 rb_ractor_t *r = DATA_PTR(gv);
2266 return r == GET_VM()->ractor.main_ractor;
2267}
2268
2269int
2270rb_ractor_living_thread_num(const rb_ractor_t *r)
2271{
2272 return r->threads.cnt;
2273}
2274
2275// only for current ractor
2276VALUE
2277rb_ractor_thread_list(void)
2278{
2279 rb_ractor_t *r = GET_RACTOR();
2280 rb_thread_t *th = 0;
2281 VALUE ary = rb_ary_new();
2282
2283 ccan_list_for_each(&r->threads.set, th, lt_node) {
2284 switch (th->status) {
2285 case THREAD_RUNNABLE:
2286 case THREAD_STOPPED:
2287 case THREAD_STOPPED_FOREVER:
2288 rb_ary_push(ary, th->self);
2289 default:
2290 break;
2291 }
2292 }
2293
2294 return ary;
2295}
2296
2297void
2298rb_ractor_living_threads_insert(rb_ractor_t *r, rb_thread_t *th)
2299{
2300 VM_ASSERT(th != NULL);
2301
2302 RACTOR_LOCK(r);
2303 {
2304 RUBY_DEBUG_LOG("r(%d)->threads.cnt:%d++", r->pub.id, r->threads.cnt);
2305 ccan_list_add_tail(&r->threads.set, &th->lt_node);
2306 r->threads.cnt++;
2307 }
2308 RACTOR_UNLOCK(r);
2309
2310 // first thread for a ractor
2311 if (r->threads.cnt == 1) {
2312 VM_ASSERT(ractor_status_p(r, ractor_created));
2313 vm_insert_ractor(th->vm, r);
2314 }
2315}
2316
2317static void
2318vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line)
2319{
2320 ractor_status_set(r, ractor_blocking);
2321
2322 RUBY_DEBUG_LOG2(file, line, "vm->ractor.blocking_cnt:%d++", vm->ractor.blocking_cnt);
2323 vm->ractor.blocking_cnt++;
2324 VM_ASSERT(vm->ractor.blocking_cnt <= vm->ractor.cnt);
2325}
2326
2327void
2328rb_vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
2329{
2330 ASSERT_vm_locking();
2331 VM_ASSERT(GET_RACTOR() == cr);
2332 vm_ractor_blocking_cnt_inc(vm, cr, file, line);
2333}
2334
2335void
2336rb_vm_ractor_blocking_cnt_dec(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
2337{
2338 ASSERT_vm_locking();
2339 VM_ASSERT(GET_RACTOR() == cr);
2340
2341 RUBY_DEBUG_LOG2(file, line, "vm->ractor.blocking_cnt:%d--", vm->ractor.blocking_cnt);
2342 VM_ASSERT(vm->ractor.blocking_cnt > 0);
2343 vm->ractor.blocking_cnt--;
2344
2345 ractor_status_set(cr, ractor_running);
2346}
2347
2348static void
2349ractor_check_blocking(rb_ractor_t *cr, unsigned int remained_thread_cnt, const char *file, int line)
2350{
2351 VM_ASSERT(cr == GET_RACTOR());
2352
2353 RUBY_DEBUG_LOG2(file, line,
2354 "cr->threads.cnt:%u cr->threads.blocking_cnt:%u vm->ractor.cnt:%u vm->ractor.blocking_cnt:%u",
2355 cr->threads.cnt, cr->threads.blocking_cnt,
2356 GET_VM()->ractor.cnt, GET_VM()->ractor.blocking_cnt);
2357
2358 VM_ASSERT(cr->threads.cnt >= cr->threads.blocking_cnt + 1);
2359
2360 if (remained_thread_cnt > 0 &&
2361 // will be block
2362 cr->threads.cnt == cr->threads.blocking_cnt + 1) {
2363 // change ractor status: running -> blocking
2364 rb_vm_t *vm = GET_VM();
2365
2366 RB_VM_LOCK_ENTER();
2367 {
2368 rb_vm_ractor_blocking_cnt_inc(vm, cr, file, line);
2369 }
2370 RB_VM_LOCK_LEAVE();
2371 }
2372}
2373
2374void rb_threadptr_remove(rb_thread_t *th);
2375
2376void
2377rb_ractor_living_threads_remove(rb_ractor_t *cr, rb_thread_t *th)
2378{
2379 VM_ASSERT(cr == GET_RACTOR());
2380 RUBY_DEBUG_LOG("r->threads.cnt:%d--", cr->threads.cnt);
2381 ractor_check_blocking(cr, cr->threads.cnt - 1, __FILE__, __LINE__);
2382
2383 rb_threadptr_remove(th);
2384
2385 if (cr->threads.cnt == 1) {
2386 vm_remove_ractor(th->vm, cr);
2387 }
2388 else {
2389 RACTOR_LOCK(cr);
2390 {
2391 ccan_list_del(&th->lt_node);
2392 cr->threads.cnt--;
2393 }
2394 RACTOR_UNLOCK(cr);
2395 }
2396}
2397
2398void
2399rb_ractor_blocking_threads_inc(rb_ractor_t *cr, const char *file, int line)
2400{
2401 RUBY_DEBUG_LOG2(file, line, "cr->threads.blocking_cnt:%d++", cr->threads.blocking_cnt);
2402
2403 VM_ASSERT(cr->threads.cnt > 0);
2404 VM_ASSERT(cr == GET_RACTOR());
2405
2406 ractor_check_blocking(cr, cr->threads.cnt, __FILE__, __LINE__);
2407 cr->threads.blocking_cnt++;
2408}
2409
2410void
2411rb_ractor_blocking_threads_dec(rb_ractor_t *cr, const char *file, int line)
2412{
2413 RUBY_DEBUG_LOG2(file, line,
2414 "r->threads.blocking_cnt:%d--, r->threads.cnt:%u",
2415 cr->threads.blocking_cnt, cr->threads.cnt);
2416
2417 VM_ASSERT(cr == GET_RACTOR());
2418
2419 if (cr->threads.cnt == cr->threads.blocking_cnt) {
2420 rb_vm_t *vm = GET_VM();
2421
2422 RB_VM_LOCK_ENTER();
2423 {
2424 rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__);
2425 }
2426 RB_VM_LOCK_LEAVE();
2427 }
2428
2429 cr->threads.blocking_cnt--;
2430}
2431
2432void
2433rb_ractor_vm_barrier_interrupt_running_thread(rb_ractor_t *r)
2434{
2435 VM_ASSERT(r != GET_RACTOR());
2436 ASSERT_ractor_unlocking(r);
2437 ASSERT_vm_locking();
2438
2439 RACTOR_LOCK(r);
2440 {
2441 if (ractor_status_p(r, ractor_running)) {
2442 rb_execution_context_t *ec = r->threads.running_ec;
2443 if (ec) {
2444 RUBY_VM_SET_VM_BARRIER_INTERRUPT(ec);
2445 }
2446 }
2447 }
2448 RACTOR_UNLOCK(r);
2449}
2450
2451void
2452rb_ractor_terminate_interrupt_main_thread(rb_ractor_t *r)
2453{
2454 VM_ASSERT(r != GET_RACTOR());
2455 ASSERT_ractor_unlocking(r);
2456 ASSERT_vm_locking();
2457
2458 rb_thread_t *main_th = r->threads.main;
2459 if (main_th) {
2460 if (main_th->status != THREAD_KILLED) {
2461 RUBY_VM_SET_TERMINATE_INTERRUPT(main_th->ec);
2462 rb_threadptr_interrupt(main_th);
2463 }
2464 else {
2465 RUBY_DEBUG_LOG("killed (%p)", (void *)main_th);
2466 }
2467 }
2468}
2469
2470void rb_thread_terminate_all(rb_thread_t *th); // thread.c
2471
2472static void
2473ractor_terminal_interrupt_all(rb_vm_t *vm)
2474{
2475 if (vm->ractor.cnt > 1) {
2476 // send terminate notification to all ractors
2477 rb_ractor_t *r = 0;
2478 ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
2479 if (r != vm->ractor.main_ractor) {
2480 RUBY_DEBUG_LOG("r:%d", rb_ractor_id(r));
2481 rb_ractor_terminate_interrupt_main_thread(r);
2482 }
2483 }
2484 }
2485}
2486
2487void rb_add_running_thread(rb_thread_t *th);
2488void rb_del_running_thread(rb_thread_t *th);
2489
2490void
2491rb_ractor_terminate_all(void)
2492{
2493 rb_vm_t *vm = GET_VM();
2494 rb_ractor_t *cr = vm->ractor.main_ractor;
2495
2496 RUBY_DEBUG_LOG("ractor.cnt:%d", (int)vm->ractor.cnt);
2497
2498 VM_ASSERT(cr == GET_RACTOR()); // only main-ractor's main-thread should kick it.
2499
2500 if (vm->ractor.cnt > 1) {
2501 RB_VM_LOCK();
2502 {
2503 ractor_terminal_interrupt_all(vm); // kill all ractors
2504 }
2505 RB_VM_UNLOCK();
2506 }
2507 rb_thread_terminate_all(GET_THREAD()); // kill other threads in main-ractor and wait
2508
2509 RB_VM_LOCK();
2510 {
2511 while (vm->ractor.cnt > 1) {
2512 RUBY_DEBUG_LOG("terminate_waiting:%d", vm->ractor.sync.terminate_waiting);
2513 vm->ractor.sync.terminate_waiting = true;
2514
2515 // wait for 1sec
2516 rb_vm_ractor_blocking_cnt_inc(vm, cr, __FILE__, __LINE__);
2517 rb_del_running_thread(rb_ec_thread_ptr(cr->threads.running_ec));
2518 rb_vm_cond_timedwait(vm, &vm->ractor.sync.terminate_cond, 1000 /* ms */);
2519 rb_add_running_thread(rb_ec_thread_ptr(cr->threads.running_ec));
2520 rb_vm_ractor_blocking_cnt_dec(vm, cr, __FILE__, __LINE__);
2521
2522 ractor_terminal_interrupt_all(vm);
2523 }
2524 }
2525 RB_VM_UNLOCK();
2526}
2527
2529rb_vm_main_ractor_ec(rb_vm_t *vm)
2530{
2531 /* This code needs to carefully work around two bugs:
2532 * - Bug #20016: When M:N threading is enabled, running_ec is NULL if no thread is
2533 * actually currently running (as opposed to without M:N threading, when
2534 * running_ec will still point to the _last_ thread which ran)
2535 * - Bug #20197: If the main thread is sleeping, setting its postponed job
2536 * interrupt flag is pointless; it won't look at the flag until it stops sleeping
2537 * for some reason. It would be better to set the flag on the running ec, which
2538 * will presumably look at it soon.
2539 *
2540 * Solution: use running_ec if it's set, otherwise fall back to the main thread ec.
2541 * This is still susceptible to some rare race conditions (what if the last thread
2542 * to run just entered a long-running sleep?), but seems like the best balance of
2543 * robustness and complexity.
2544 */
2545 rb_execution_context_t *running_ec = vm->ractor.main_ractor->threads.running_ec;
2546 if (running_ec) { return running_ec; }
2547 return vm->ractor.main_thread->ec;
2548}
2549
2550static VALUE
2551ractor_moved_missing(int argc, VALUE *argv, VALUE self)
2552{
2553 rb_raise(rb_eRactorMovedError, "can not send any methods to a moved object");
2554}
2555
2556#ifndef USE_RACTOR_SELECTOR
2557#define USE_RACTOR_SELECTOR 0
2558#endif
2559
2560RUBY_SYMBOL_EXPORT_BEGIN
2561void rb_init_ractor_selector(void);
2562RUBY_SYMBOL_EXPORT_END
2563
2564/*
2565 * Document-class: Ractor::Selector
2566 * :nodoc: currently
2567 *
2568 * Selects multiple Ractors to be activated.
2569 */
2570void
2571rb_init_ractor_selector(void)
2572{
2573 rb_cRactorSelector = rb_define_class_under(rb_cRactor, "Selector", rb_cObject);
2574 rb_undef_alloc_func(rb_cRactorSelector);
2575
2576 rb_define_singleton_method(rb_cRactorSelector, "new", ractor_selector_new , -1);
2577 rb_define_method(rb_cRactorSelector, "add", ractor_selector_add, 1);
2578 rb_define_method(rb_cRactorSelector, "remove", ractor_selector_remove, 1);
2579 rb_define_method(rb_cRactorSelector, "clear", ractor_selector_clear, 0);
2580 rb_define_method(rb_cRactorSelector, "empty?", ractor_selector_empty_p, 0);
2581 rb_define_method(rb_cRactorSelector, "wait", ractor_selector_wait, -1);
2582 rb_define_method(rb_cRactorSelector, "_wait", ractor_selector__wait, 4);
2583}
2584
2585/*
2586 * Document-class: Ractor::ClosedError
2587 *
2588 * Raised when an attempt is made to send a message to a closed port,
2589 * or to retrieve a message from a closed and empty port.
2590 * Ports may be closed explicitly with Ractor#close_outgoing/close_incoming
2591 * and are closed implicitly when a Ractor terminates.
2592 *
2593 * r = Ractor.new { sleep(500) }
2594 * r.close_outgoing
2595 * r.take # Ractor::ClosedError
2596 *
2597 * ClosedError is a descendant of StopIteration, so the closing of the ractor will break
2598 * the loops without propagating the error:
2599 *
2600 * r = Ractor.new do
2601 * loop do
2602 * msg = receive # raises ClosedError and loop traps it
2603 * puts "Received: #{msg}"
2604 * end
2605 * puts "loop exited"
2606 * end
2607 *
2608 * 3.times{|i| r << i}
2609 * r.close_incoming
2610 * r.take
2611 * puts "Continue successfully"
2612 *
2613 * This will print:
2614 *
2615 * Received: 0
2616 * Received: 1
2617 * Received: 2
2618 * loop exited
2619 * Continue successfully
2620 */
2621
2622/*
2623 * Document-class: Ractor::RemoteError
2624 *
2625 * Raised on attempt to Ractor#take if there was an uncaught exception in the Ractor.
2626 * Its +cause+ will contain the original exception, and +ractor+ is the original ractor
2627 * it was raised in.
2628 *
2629 * r = Ractor.new { raise "Something weird happened" }
2630 *
2631 * begin
2632 * r.take
2633 * rescue => e
2634 * p e # => #<Ractor::RemoteError: thrown by remote Ractor.>
2635 * p e.ractor == r # => true
2636 * p e.cause # => #<RuntimeError: Something weird happened>
2637 * end
2638 *
2639 */
2640
2641/*
2642 * Document-class: Ractor::MovedError
2643 *
2644 * Raised on an attempt to access an object which was moved in Ractor#send or Ractor.yield.
2645 *
2646 * r = Ractor.new { sleep }
2647 *
2648 * ary = [1, 2, 3]
2649 * r.send(ary, move: true)
2650 * ary.inspect
2651 * # Ractor::MovedError (can not send any methods to a moved object)
2652 *
2653 */
2654
2655/*
2656 * Document-class: Ractor::MovedObject
2657 *
2658 * A special object which replaces any value that was moved to another ractor in Ractor#send
2659 * or Ractor.yield. Any attempt to access the object results in Ractor::MovedError.
2660 *
2661 * r = Ractor.new { receive }
2662 *
2663 * ary = [1, 2, 3]
2664 * r.send(ary, move: true)
2665 * p Ractor::MovedObject === ary
2666 * # => true
2667 * ary.inspect
2668 * # Ractor::MovedError (can not send any methods to a moved object)
2669 */
2670
2671// Main docs are in ractor.rb, but without this clause there are weird artifacts
2672// in their rendering.
2673/*
2674 * Document-class: Ractor
2675 *
2676 */
2677
2678void
2679Init_Ractor(void)
2680{
2681 rb_cRactor = rb_define_class("Ractor", rb_cObject);
2683
2684 rb_eRactorError = rb_define_class_under(rb_cRactor, "Error", rb_eRuntimeError);
2685 rb_eRactorIsolationError = rb_define_class_under(rb_cRactor, "IsolationError", rb_eRactorError);
2686 rb_eRactorRemoteError = rb_define_class_under(rb_cRactor, "RemoteError", rb_eRactorError);
2687 rb_eRactorMovedError = rb_define_class_under(rb_cRactor, "MovedError", rb_eRactorError);
2688 rb_eRactorClosedError = rb_define_class_under(rb_cRactor, "ClosedError", rb_eStopIteration);
2689 rb_eRactorUnsafeError = rb_define_class_under(rb_cRactor, "UnsafeError", rb_eRactorError);
2690
2691 rb_cRactorMovedObject = rb_define_class_under(rb_cRactor, "MovedObject", rb_cBasicObject);
2692 rb_undef_alloc_func(rb_cRactorMovedObject);
2693 rb_define_method(rb_cRactorMovedObject, "method_missing", ractor_moved_missing, -1);
2694
2695 // override methods defined in BasicObject
2696 rb_define_method(rb_cRactorMovedObject, "__send__", ractor_moved_missing, -1);
2697 rb_define_method(rb_cRactorMovedObject, "!", ractor_moved_missing, -1);
2698 rb_define_method(rb_cRactorMovedObject, "==", ractor_moved_missing, -1);
2699 rb_define_method(rb_cRactorMovedObject, "!=", ractor_moved_missing, -1);
2700 rb_define_method(rb_cRactorMovedObject, "__id__", ractor_moved_missing, -1);
2701 rb_define_method(rb_cRactorMovedObject, "equal?", ractor_moved_missing, -1);
2702 rb_define_method(rb_cRactorMovedObject, "instance_eval", ractor_moved_missing, -1);
2703 rb_define_method(rb_cRactorMovedObject, "instance_exec", ractor_moved_missing, -1);
2704
2705 // internal
2706
2707#if USE_RACTOR_SELECTOR
2708 rb_init_ractor_selector();
2709#endif
2710}
2711
2712void
2713rb_ractor_dump(void)
2714{
2715 rb_vm_t *vm = GET_VM();
2716 rb_ractor_t *r = 0;
2717
2718 ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
2719 if (r != vm->ractor.main_ractor) {
2720 fprintf(stderr, "r:%u (%s)\n", r->pub.id, ractor_status_str(r->status_));
2721 }
2722 }
2723}
2724
2725VALUE
2727{
2728 if (rb_ractor_main_p()) {
2729 return rb_stdin;
2730 }
2731 else {
2732 rb_ractor_t *cr = GET_RACTOR();
2733 return cr->r_stdin;
2734 }
2735}
2736
2737VALUE
2738rb_ractor_stdout(void)
2739{
2740 if (rb_ractor_main_p()) {
2741 return rb_stdout;
2742 }
2743 else {
2744 rb_ractor_t *cr = GET_RACTOR();
2745 return cr->r_stdout;
2746 }
2747}
2748
2749VALUE
2750rb_ractor_stderr(void)
2751{
2752 if (rb_ractor_main_p()) {
2753 return rb_stderr;
2754 }
2755 else {
2756 rb_ractor_t *cr = GET_RACTOR();
2757 return cr->r_stderr;
2758 }
2759}
2760
2761void
2763{
2764 if (rb_ractor_main_p()) {
2765 rb_stdin = in;
2766 }
2767 else {
2768 rb_ractor_t *cr = GET_RACTOR();
2769 RB_OBJ_WRITE(cr->pub.self, &cr->r_stdin, in);
2770 }
2771}
2772
2773void
2775{
2776 if (rb_ractor_main_p()) {
2777 rb_stdout = out;
2778 }
2779 else {
2780 rb_ractor_t *cr = GET_RACTOR();
2781 RB_OBJ_WRITE(cr->pub.self, &cr->r_stdout, out);
2782 }
2783}
2784
2785void
2787{
2788 if (rb_ractor_main_p()) {
2789 rb_stderr = err;
2790 }
2791 else {
2792 rb_ractor_t *cr = GET_RACTOR();
2793 RB_OBJ_WRITE(cr->pub.self, &cr->r_stderr, err);
2794 }
2795}
2796
2798rb_ractor_hooks(rb_ractor_t *cr)
2799{
2800 return &cr->pub.hooks;
2801}
2802
2804
2805// 2: stop search
2806// 1: skip child
2807// 0: continue
2808
2809enum obj_traverse_iterator_result {
2810 traverse_cont,
2811 traverse_skip,
2812 traverse_stop,
2813};
2814
2815typedef enum obj_traverse_iterator_result (*rb_obj_traverse_enter_func)(VALUE obj);
2816typedef enum obj_traverse_iterator_result (*rb_obj_traverse_leave_func)(VALUE obj);
2817typedef enum obj_traverse_iterator_result (*rb_obj_traverse_final_func)(VALUE obj);
2818
2819static enum obj_traverse_iterator_result null_leave(VALUE obj);
2820
2822 rb_obj_traverse_enter_func enter_func;
2823 rb_obj_traverse_leave_func leave_func;
2824
2825 st_table *rec;
2826 VALUE rec_hash;
2827};
2828
2829
2831 bool stop;
2832 struct obj_traverse_data *data;
2833};
2834
2835static int obj_traverse_i(VALUE obj, struct obj_traverse_data *data);
2836
2837static int
2838obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
2839{
2841
2842 if (obj_traverse_i(key, d->data)) {
2843 d->stop = true;
2844 return ST_STOP;
2845 }
2846
2847 if (obj_traverse_i(val, d->data)) {
2848 d->stop = true;
2849 return ST_STOP;
2850 }
2851
2852 return ST_CONTINUE;
2853}
2854
2855static void
2856obj_traverse_reachable_i(VALUE obj, void *ptr)
2857{
2859
2860 if (obj_traverse_i(obj, d->data)) {
2861 d->stop = true;
2862 }
2863}
2864
2865static struct st_table *
2866obj_traverse_rec(struct obj_traverse_data *data)
2867{
2868 if (UNLIKELY(!data->rec)) {
2869 data->rec_hash = rb_ident_hash_new();
2870 data->rec = RHASH_ST_TABLE(data->rec_hash);
2871 }
2872 return data->rec;
2873}
2874
2875static int
2876obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
2877{
2879
2880 if (obj_traverse_i(val, d->data)) {
2881 d->stop = true;
2882 return ST_STOP;
2883 }
2884
2885 return ST_CONTINUE;
2886}
2887
2888static int
2889obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
2890{
2891 if (RB_SPECIAL_CONST_P(obj)) return 0;
2892
2893 switch (data->enter_func(obj)) {
2894 case traverse_cont: break;
2895 case traverse_skip: return 0; // skip children
2896 case traverse_stop: return 1; // stop search
2897 }
2898
2899 if (UNLIKELY(st_insert(obj_traverse_rec(data), obj, 1))) {
2900 // already traversed
2901 return 0;
2902 }
2903
2904 struct obj_traverse_callback_data d = {
2905 .stop = false,
2906 .data = data,
2907 };
2908 rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
2909 if (d.stop) return 1;
2910
2911 switch (BUILTIN_TYPE(obj)) {
2912 // no child node
2913 case T_STRING:
2914 case T_FLOAT:
2915 case T_BIGNUM:
2916 case T_REGEXP:
2917 case T_FILE:
2918 case T_SYMBOL:
2919 case T_MATCH:
2920 break;
2921
2922 case T_OBJECT:
2923 /* Instance variables already traversed. */
2924 break;
2925
2926 case T_ARRAY:
2927 {
2928 for (int i = 0; i < RARRAY_LENINT(obj); i++) {
2929 VALUE e = rb_ary_entry(obj, i);
2930 if (obj_traverse_i(e, data)) return 1;
2931 }
2932 }
2933 break;
2934
2935 case T_HASH:
2936 {
2937 if (obj_traverse_i(RHASH_IFNONE(obj), data)) return 1;
2938
2939 struct obj_traverse_callback_data d = {
2940 .stop = false,
2941 .data = data,
2942 };
2943 rb_hash_foreach(obj, obj_hash_traverse_i, (VALUE)&d);
2944 if (d.stop) return 1;
2945 }
2946 break;
2947
2948 case T_STRUCT:
2949 {
2950 long len = RSTRUCT_LEN(obj);
2951 const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
2952
2953 for (long i=0; i<len; i++) {
2954 if (obj_traverse_i(ptr[i], data)) return 1;
2955 }
2956 }
2957 break;
2958
2959 case T_RATIONAL:
2960 if (obj_traverse_i(RRATIONAL(obj)->num, data)) return 1;
2961 if (obj_traverse_i(RRATIONAL(obj)->den, data)) return 1;
2962 break;
2963 case T_COMPLEX:
2964 if (obj_traverse_i(RCOMPLEX(obj)->real, data)) return 1;
2965 if (obj_traverse_i(RCOMPLEX(obj)->imag, data)) return 1;
2966 break;
2967
2968 case T_DATA:
2969 case T_IMEMO:
2970 {
2971 struct obj_traverse_callback_data d = {
2972 .stop = false,
2973 .data = data,
2974 };
2975 RB_VM_LOCK_ENTER_NO_BARRIER();
2976 {
2977 rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d);
2978 }
2979 RB_VM_LOCK_LEAVE_NO_BARRIER();
2980 if (d.stop) return 1;
2981 }
2982 break;
2983
2984 // unreachable
2985 case T_CLASS:
2986 case T_MODULE:
2987 case T_ICLASS:
2988 default:
2989 rp(obj);
2990 rb_bug("unreachable");
2991 }
2992
2993 if (data->leave_func(obj) == traverse_stop) {
2994 return 1;
2995 }
2996 else {
2997 return 0;
2998 }
2999}
3000
3002 rb_obj_traverse_final_func final_func;
3003 int stopped;
3004};
3005
3006static int
3007obj_traverse_final_i(st_data_t key, st_data_t val, st_data_t arg)
3008{
3009 struct rb_obj_traverse_final_data *data = (void *)arg;
3010 if (data->final_func(key)) {
3011 data->stopped = 1;
3012 return ST_STOP;
3013 }
3014 return ST_CONTINUE;
3015}
3016
3017// 0: traverse all
3018// 1: stopped
3019static int
3020rb_obj_traverse(VALUE obj,
3021 rb_obj_traverse_enter_func enter_func,
3022 rb_obj_traverse_leave_func leave_func,
3023 rb_obj_traverse_final_func final_func)
3024{
3025 struct obj_traverse_data data = {
3026 .enter_func = enter_func,
3027 .leave_func = leave_func,
3028 .rec = NULL,
3029 };
3030
3031 if (obj_traverse_i(obj, &data)) return 1;
3032 if (final_func && data.rec) {
3033 struct rb_obj_traverse_final_data f = {final_func, 0};
3034 st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
3035 return f.stopped;
3036 }
3037 return 0;
3038}
3039
3040static int
3041allow_frozen_shareable_p(VALUE obj)
3042{
3043 if (!RB_TYPE_P(obj, T_DATA)) {
3044 return true;
3045 }
3046 else if (RTYPEDDATA_P(obj)) {
3047 const rb_data_type_t *type = RTYPEDDATA_TYPE(obj);
3048 if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE) {
3049 return true;
3050 }
3051 }
3052
3053 return false;
3054}
3055
3056static enum obj_traverse_iterator_result
3057make_shareable_check_shareable(VALUE obj)
3058{
3059 VM_ASSERT(!SPECIAL_CONST_P(obj));
3060
3061 if (rb_ractor_shareable_p(obj)) {
3062 return traverse_skip;
3063 }
3064 else if (!allow_frozen_shareable_p(obj)) {
3065 if (rb_obj_is_proc(obj)) {
3066 rb_proc_ractor_make_shareable(obj);
3067 return traverse_cont;
3068 }
3069 else {
3070 rb_raise(rb_eRactorError, "can not make shareable object for %"PRIsVALUE, obj);
3071 }
3072 }
3073
3074 if (RB_TYPE_P(obj, T_IMEMO)) {
3075 return traverse_skip;
3076 }
3077
3078 if (!RB_OBJ_FROZEN_RAW(obj)) {
3079 rb_funcall(obj, idFreeze, 0);
3080
3081 if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
3082 rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
3083 }
3084
3085 if (RB_OBJ_SHAREABLE_P(obj)) {
3086 return traverse_skip;
3087 }
3088 }
3089
3090 return traverse_cont;
3091}
3092
3093static enum obj_traverse_iterator_result
3094mark_shareable(VALUE obj)
3095{
3097 return traverse_cont;
3098}
3099
3100VALUE
3102{
3103 rb_obj_traverse(obj,
3104 make_shareable_check_shareable,
3105 null_leave, mark_shareable);
3106 return obj;
3107}
3108
3109VALUE
3111{
3112 VALUE copy = ractor_copy(obj);
3113 return rb_ractor_make_shareable(copy);
3114}
3115
3116VALUE
3117rb_ractor_ensure_shareable(VALUE obj, VALUE name)
3118{
3119 if (!rb_ractor_shareable_p(obj)) {
3120 VALUE message = rb_sprintf("cannot assign unshareable object to %"PRIsVALUE,
3121 name);
3122 rb_exc_raise(rb_exc_new_str(rb_eRactorIsolationError, message));
3123 }
3124 return obj;
3125}
3126
3127void
3128rb_ractor_ensure_main_ractor(const char *msg)
3129{
3130 if (!rb_ractor_main_p()) {
3131 rb_raise(rb_eRactorIsolationError, "%s", msg);
3132 }
3133}
3134
3135static enum obj_traverse_iterator_result
3136shareable_p_enter(VALUE obj)
3137{
3138 if (RB_OBJ_SHAREABLE_P(obj)) {
3139 return traverse_skip;
3140 }
3141 else if (RB_TYPE_P(obj, T_CLASS) ||
3142 RB_TYPE_P(obj, T_MODULE) ||
3143 RB_TYPE_P(obj, T_ICLASS)) {
3144 // TODO: remove it
3145 mark_shareable(obj);
3146 return traverse_skip;
3147 }
3148 else if (RB_OBJ_FROZEN_RAW(obj) &&
3149 allow_frozen_shareable_p(obj)) {
3150 return traverse_cont;
3151 }
3152
3153 return traverse_stop; // fail
3154}
3155
3156bool
3157rb_ractor_shareable_p_continue(VALUE obj)
3158{
3159 if (rb_obj_traverse(obj,
3160 shareable_p_enter, null_leave,
3161 mark_shareable)) {
3162 return false;
3163 }
3164 else {
3165 return true;
3166 }
3167}
3168
3169#if RACTOR_CHECK_MODE > 0
3170void
3171rb_ractor_setup_belonging(VALUE obj)
3172{
3173 rb_ractor_setup_belonging_to(obj, rb_ractor_current_id());
3174}
3175
3176static enum obj_traverse_iterator_result
3177reset_belonging_enter(VALUE obj)
3178{
3179 if (rb_ractor_shareable_p(obj)) {
3180 return traverse_skip;
3181 }
3182 else {
3183 rb_ractor_setup_belonging(obj);
3184 return traverse_cont;
3185 }
3186}
3187#endif
3188
3189static enum obj_traverse_iterator_result
3190null_leave(VALUE obj)
3191{
3192 return traverse_cont;
3193}
3194
3195static VALUE
3196ractor_reset_belonging(VALUE obj)
3197{
3198#if RACTOR_CHECK_MODE > 0
3199 rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
3200#endif
3201 return obj;
3202}
3203
3204
3206
3207// 2: stop search
3208// 1: skip child
3209// 0: continue
3210
3212static int obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data);
3213typedef enum obj_traverse_iterator_result (*rb_obj_traverse_replace_enter_func)(VALUE obj, struct obj_traverse_replace_data *data);
3214typedef enum obj_traverse_iterator_result (*rb_obj_traverse_replace_leave_func)(VALUE obj, struct obj_traverse_replace_data *data);
3215
3217 rb_obj_traverse_replace_enter_func enter_func;
3218 rb_obj_traverse_replace_leave_func leave_func;
3219
3220 st_table *rec;
3221 VALUE rec_hash;
3222
3223 VALUE replacement;
3224 bool move;
3225};
3226
3228 bool stop;
3229 VALUE src;
3230 struct obj_traverse_replace_data *data;
3231};
3232
3233static int
3234obj_hash_traverse_replace_foreach_i(st_data_t key, st_data_t value, st_data_t argp, int error)
3235{
3236 return ST_REPLACE;
3237}
3238
3239static int
3240obj_hash_traverse_replace_i(st_data_t *key, st_data_t *val, st_data_t ptr, int exists)
3241{
3243 struct obj_traverse_replace_data *data = d->data;
3244
3245 if (obj_traverse_replace_i(*key, data)) {
3246 d->stop = true;
3247 return ST_STOP;
3248 }
3249 else if (*key != data->replacement) {
3250 VALUE v = *key = data->replacement;
3251 RB_OBJ_WRITTEN(d->src, Qundef, v);
3252 }
3253
3254 if (obj_traverse_replace_i(*val, data)) {
3255 d->stop = true;
3256 return ST_STOP;
3257 }
3258 else if (*val != data->replacement) {
3259 VALUE v = *val = data->replacement;
3260 RB_OBJ_WRITTEN(d->src, Qundef, v);
3261 }
3262
3263 return ST_CONTINUE;
3264}
3265
3266static int
3267obj_iv_hash_traverse_replace_foreach_i(st_data_t _key, st_data_t _val, st_data_t _data, int _x)
3268{
3269 return ST_REPLACE;
3270}
3271
3272static int
3273obj_iv_hash_traverse_replace_i(st_data_t * _key, st_data_t * val, st_data_t ptr, int exists)
3274{
3276 struct obj_traverse_replace_data *data = d->data;
3277
3278 if (obj_traverse_replace_i(*(VALUE *)val, data)) {
3279 d->stop = true;
3280 return ST_STOP;
3281 }
3282 else if (*(VALUE *)val != data->replacement) {
3283 VALUE v = *(VALUE *)val = data->replacement;
3284 RB_OBJ_WRITTEN(d->src, Qundef, v);
3285 }
3286
3287 return ST_CONTINUE;
3288}
3289
3290static struct st_table *
3291obj_traverse_replace_rec(struct obj_traverse_replace_data *data)
3292{
3293 if (UNLIKELY(!data->rec)) {
3294 data->rec_hash = rb_ident_hash_new();
3295 data->rec = RHASH_ST_TABLE(data->rec_hash);
3296 }
3297 return data->rec;
3298}
3299
3300static void
3301obj_refer_only_shareables_p_i(VALUE obj, void *ptr)
3302{
3303 int *pcnt = (int *)ptr;
3304
3305 if (!rb_ractor_shareable_p(obj)) {
3306 ++*pcnt;
3307 }
3308}
3309
3310static int
3311obj_refer_only_shareables_p(VALUE obj)
3312{
3313 int cnt = 0;
3314 RB_VM_LOCK_ENTER_NO_BARRIER();
3315 {
3316 rb_objspace_reachable_objects_from(obj, obj_refer_only_shareables_p_i, &cnt);
3317 }
3318 RB_VM_LOCK_LEAVE_NO_BARRIER();
3319 return cnt == 0;
3320}
3321
3322static int
3323obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data)
3324{
3325 st_data_t replacement;
3326
3327 if (RB_SPECIAL_CONST_P(obj)) {
3328 data->replacement = obj;
3329 return 0;
3330 }
3331
3332 switch (data->enter_func(obj, data)) {
3333 case traverse_cont: break;
3334 case traverse_skip: return 0; // skip children
3335 case traverse_stop: return 1; // stop search
3336 }
3337
3338 replacement = (st_data_t)data->replacement;
3339
3340 if (UNLIKELY(st_lookup(obj_traverse_replace_rec(data), (st_data_t)obj, &replacement))) {
3341 data->replacement = (VALUE)replacement;
3342 return 0;
3343 }
3344 else {
3345 st_insert(obj_traverse_replace_rec(data), (st_data_t)obj, replacement);
3346 }
3347
3348 if (!data->move) {
3349 obj = replacement;
3350 }
3351
3352#define CHECK_AND_REPLACE(v) do { \
3353 VALUE _val = (v); \
3354 if (obj_traverse_replace_i(_val, data)) { return 1; } \
3355 else if (data->replacement != _val) { RB_OBJ_WRITE(obj, &v, data->replacement); } \
3356} while (0)
3357
3358 if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
3359 struct gen_ivtbl *ivtbl;
3360 rb_ivar_generic_ivtbl_lookup(obj, &ivtbl);
3361
3362 if (UNLIKELY(rb_shape_obj_too_complex(obj))) {
3364 .stop = false,
3365 .data = data,
3366 .src = obj,
3367 };
3368 rb_st_foreach_with_replace(
3369 ivtbl->as.complex.table,
3370 obj_iv_hash_traverse_replace_foreach_i,
3371 obj_iv_hash_traverse_replace_i,
3372 (st_data_t)&d
3373 );
3374 if (d.stop) return 1;
3375 }
3376 else {
3377 for (uint32_t i = 0; i < ivtbl->as.shape.numiv; i++) {
3378 if (!UNDEF_P(ivtbl->as.shape.ivptr[i])) {
3379 CHECK_AND_REPLACE(ivtbl->as.shape.ivptr[i]);
3380 }
3381 }
3382 }
3383 }
3384
3385 switch (BUILTIN_TYPE(obj)) {
3386 // no child node
3387 case T_FLOAT:
3388 case T_BIGNUM:
3389 case T_REGEXP:
3390 case T_FILE:
3391 case T_SYMBOL:
3392 case T_MATCH:
3393 break;
3394 case T_STRING:
3395 rb_str_make_independent(obj);
3396 break;
3397
3398 case T_OBJECT:
3399 {
3400 if (rb_shape_obj_too_complex(obj)) {
3402 .stop = false,
3403 .data = data,
3404 .src = obj,
3405 };
3406 rb_st_foreach_with_replace(
3407 ROBJECT_IV_HASH(obj),
3408 obj_iv_hash_traverse_replace_foreach_i,
3409 obj_iv_hash_traverse_replace_i,
3410 (st_data_t)&d
3411 );
3412 if (d.stop) return 1;
3413 }
3414 else {
3415 uint32_t len = ROBJECT_IV_COUNT(obj);
3416 VALUE *ptr = ROBJECT_IVPTR(obj);
3417
3418 for (uint32_t i = 0; i < len; i++) {
3419 CHECK_AND_REPLACE(ptr[i]);
3420 }
3421 }
3422 }
3423 break;
3424
3425 case T_ARRAY:
3426 {
3427 rb_ary_cancel_sharing(obj);
3428
3429 for (int i = 0; i < RARRAY_LENINT(obj); i++) {
3430 VALUE e = rb_ary_entry(obj, i);
3431
3432 if (obj_traverse_replace_i(e, data)) {
3433 return 1;
3434 }
3435 else if (e != data->replacement) {
3436 RARRAY_ASET(obj, i, data->replacement);
3437 }
3438 }
3439 RB_GC_GUARD(obj);
3440 }
3441 break;
3442 case T_HASH:
3443 {
3445 .stop = false,
3446 .data = data,
3447 .src = obj,
3448 };
3449 rb_hash_stlike_foreach_with_replace(obj,
3450 obj_hash_traverse_replace_foreach_i,
3451 obj_hash_traverse_replace_i,
3452 (VALUE)&d);
3453 if (d.stop) return 1;
3454 // TODO: rehash here?
3455
3456 VALUE ifnone = RHASH_IFNONE(obj);
3457 if (obj_traverse_replace_i(ifnone, data)) {
3458 return 1;
3459 }
3460 else if (ifnone != data->replacement) {
3461 RHASH_SET_IFNONE(obj, data->replacement);
3462 }
3463 }
3464 break;
3465
3466 case T_STRUCT:
3467 {
3468 long len = RSTRUCT_LEN(obj);
3469 const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
3470
3471 for (long i=0; i<len; i++) {
3472 CHECK_AND_REPLACE(ptr[i]);
3473 }
3474 }
3475 break;
3476
3477 case T_RATIONAL:
3478 CHECK_AND_REPLACE(RRATIONAL(obj)->num);
3479 CHECK_AND_REPLACE(RRATIONAL(obj)->den);
3480 break;
3481 case T_COMPLEX:
3482 CHECK_AND_REPLACE(RCOMPLEX(obj)->real);
3483 CHECK_AND_REPLACE(RCOMPLEX(obj)->imag);
3484 break;
3485
3486 case T_DATA:
3487 if (!data->move && obj_refer_only_shareables_p(obj)) {
3488 break;
3489 }
3490 else {
3491 rb_raise(rb_eRactorError, "can not %s %"PRIsVALUE" object.",
3492 data->move ? "move" : "copy", rb_class_of(obj));
3493 }
3494
3495 case T_IMEMO:
3496 // not supported yet
3497 return 1;
3498
3499 // unreachable
3500 case T_CLASS:
3501 case T_MODULE:
3502 case T_ICLASS:
3503 default:
3504 rp(obj);
3505 rb_bug("unreachable");
3506 }
3507
3508 data->replacement = (VALUE)replacement;
3509
3510 if (data->leave_func(obj, data) == traverse_stop) {
3511 return 1;
3512 }
3513 else {
3514 return 0;
3515 }
3516}
3517
3518// 0: traverse all
3519// 1: stopped
3520static VALUE
3521rb_obj_traverse_replace(VALUE obj,
3522 rb_obj_traverse_replace_enter_func enter_func,
3523 rb_obj_traverse_replace_leave_func leave_func,
3524 bool move)
3525{
3526 struct obj_traverse_replace_data data = {
3527 .enter_func = enter_func,
3528 .leave_func = leave_func,
3529 .rec = NULL,
3530 .replacement = Qundef,
3531 .move = move,
3532 };
3533
3534 if (obj_traverse_replace_i(obj, &data)) {
3535 return Qundef;
3536 }
3537 else {
3538 return data.replacement;
3539 }
3540}
3541
3542static const bool wb_protected_types[RUBY_T_MASK] = {
3553};
3554
3555static enum obj_traverse_iterator_result
3556move_enter(VALUE obj, struct obj_traverse_replace_data *data)
3557{
3558 if (rb_ractor_shareable_p(obj)) {
3559 data->replacement = obj;
3560 return traverse_skip;
3561 }
3562 else {
3563 VALUE type = RB_BUILTIN_TYPE(obj);
3564 type |= wb_protected_types[type] ? FL_WB_PROTECTED : 0;
3565 NEWOBJ_OF(moved, struct RBasic, 0, type, rb_gc_obj_slot_size(obj), 0);
3566 data->replacement = (VALUE)moved;
3567 return traverse_cont;
3568 }
3569}
3570
3571static enum obj_traverse_iterator_result
3572move_leave(VALUE obj, struct obj_traverse_replace_data *data)
3573{
3574 size_t size = rb_gc_obj_slot_size(obj);
3575 memcpy((void *)data->replacement, (void *)obj, size);
3576 FL_UNSET_RAW(data->replacement, FL_SEEN_OBJ_ID);
3577
3578 void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c
3579
3580 if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
3581 rb_replace_generic_ivar(data->replacement, obj);
3582 }
3583
3584 // Avoid mutations using bind_call, etc.
3585 // We keep FL_SEEN_OBJ_ID so GC later clean the obj_id_table.
3586 MEMZERO((char *)obj + sizeof(struct RBasic), char, size - sizeof(struct RBasic));
3587 RBASIC(obj)->flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_SEEN_OBJ_ID);
3588 RBASIC_SET_CLASS_RAW(obj, rb_cRactorMovedObject);
3589 return traverse_cont;
3590}
3591
3592static VALUE
3593ractor_move(VALUE obj)
3594{
3595 VALUE val = rb_obj_traverse_replace(obj, move_enter, move_leave, true);
3596 if (!UNDEF_P(val)) {
3597 return val;
3598 }
3599 else {
3600 rb_raise(rb_eRactorError, "can not move the object");
3601 }
3602}
3603
3604static enum obj_traverse_iterator_result
3605copy_enter(VALUE obj, struct obj_traverse_replace_data *data)
3606{
3607 if (rb_ractor_shareable_p(obj)) {
3608 data->replacement = obj;
3609 return traverse_skip;
3610 }
3611 else {
3612 data->replacement = rb_obj_clone(obj);
3613 return traverse_cont;
3614 }
3615}
3616
3617static enum obj_traverse_iterator_result
3618copy_leave(VALUE obj, struct obj_traverse_replace_data *data)
3619{
3620 return traverse_cont;
3621}
3622
3623static VALUE
3624ractor_copy(VALUE obj)
3625{
3626 VALUE val = rb_obj_traverse_replace(obj, copy_enter, copy_leave, false);
3627 if (!UNDEF_P(val)) {
3628 return val;
3629 }
3630 else {
3631 rb_raise(rb_eRactorError, "can not copy the object");
3632 }
3633}
3634
3635// Ractor local storage
3636
3638 const struct rb_ractor_local_storage_type *type;
3639 void *main_cache;
3640};
3641
3643 int cnt;
3644 int capa;
3646} freed_ractor_local_keys;
3647
3648static int
3649ractor_local_storage_mark_i(st_data_t key, st_data_t val, st_data_t dmy)
3650{
3652 if (k->type->mark) (*k->type->mark)((void *)val);
3653 return ST_CONTINUE;
3654}
3655
3656static enum rb_id_table_iterator_result
3657idkey_local_storage_mark_i(VALUE val, void *dmy)
3658{
3659 rb_gc_mark(val);
3660 return ID_TABLE_CONTINUE;
3661}
3662
3663static void
3664ractor_local_storage_mark(rb_ractor_t *r)
3665{
3666 if (r->local_storage) {
3667 st_foreach(r->local_storage, ractor_local_storage_mark_i, 0);
3668
3669 for (int i=0; i<freed_ractor_local_keys.cnt; i++) {
3670 rb_ractor_local_key_t key = freed_ractor_local_keys.keys[i];
3671 st_data_t val, k = (st_data_t)key;
3672 if (st_delete(r->local_storage, &k, &val) &&
3673 (key = (rb_ractor_local_key_t)k)->type->free) {
3674 (*key->type->free)((void *)val);
3675 }
3676 }
3677 }
3678
3679 if (r->idkey_local_storage) {
3680 rb_id_table_foreach_values(r->idkey_local_storage, idkey_local_storage_mark_i, NULL);
3681 }
3682
3683 rb_gc_mark(r->local_storage_store_lock);
3684}
3685
3686static int
3687ractor_local_storage_free_i(st_data_t key, st_data_t val, st_data_t dmy)
3688{
3690 if (k->type->free) (*k->type->free)((void *)val);
3691 return ST_CONTINUE;
3692}
3693
3694static void
3695ractor_local_storage_free(rb_ractor_t *r)
3696{
3697 if (r->local_storage) {
3698 st_foreach(r->local_storage, ractor_local_storage_free_i, 0);
3699 st_free_table(r->local_storage);
3700 }
3701
3702 if (r->idkey_local_storage) {
3703 rb_id_table_free(r->idkey_local_storage);
3704 }
3705}
3706
3707static void
3708rb_ractor_local_storage_value_mark(void *ptr)
3709{
3710 rb_gc_mark((VALUE)ptr);
3711}
3712
3713static const struct rb_ractor_local_storage_type ractor_local_storage_type_null = {
3714 NULL,
3715 NULL,
3716};
3717
3719 NULL,
3720 ruby_xfree,
3721};
3722
3723static const struct rb_ractor_local_storage_type ractor_local_storage_type_value = {
3724 rb_ractor_local_storage_value_mark,
3725 NULL,
3726};
3727
3730{
3732 key->type = type ? type : &ractor_local_storage_type_null;
3733 key->main_cache = (void *)Qundef;
3734 return key;
3735}
3736
3739{
3740 return rb_ractor_local_storage_ptr_newkey(&ractor_local_storage_type_value);
3741}
3742
3743void
3744rb_ractor_local_storage_delkey(rb_ractor_local_key_t key)
3745{
3746 RB_VM_LOCK_ENTER();
3747 {
3748 if (freed_ractor_local_keys.cnt == freed_ractor_local_keys.capa) {
3749 freed_ractor_local_keys.capa = freed_ractor_local_keys.capa ? freed_ractor_local_keys.capa * 2 : 4;
3750 REALLOC_N(freed_ractor_local_keys.keys, rb_ractor_local_key_t, freed_ractor_local_keys.capa);
3751 }
3752 freed_ractor_local_keys.keys[freed_ractor_local_keys.cnt++] = key;
3753 }
3754 RB_VM_LOCK_LEAVE();
3755}
3756
3757static bool
3758ractor_local_ref(rb_ractor_local_key_t key, void **pret)
3759{
3760 if (rb_ractor_main_p()) {
3761 if (!UNDEF_P((VALUE)key->main_cache)) {
3762 *pret = key->main_cache;
3763 return true;
3764 }
3765 else {
3766 return false;
3767 }
3768 }
3769 else {
3770 rb_ractor_t *cr = GET_RACTOR();
3771
3772 if (cr->local_storage && st_lookup(cr->local_storage, (st_data_t)key, (st_data_t *)pret)) {
3773 return true;
3774 }
3775 else {
3776 return false;
3777 }
3778 }
3779}
3780
3781static void
3782ractor_local_set(rb_ractor_local_key_t key, void *ptr)
3783{
3784 rb_ractor_t *cr = GET_RACTOR();
3785
3786 if (cr->local_storage == NULL) {
3787 cr->local_storage = st_init_numtable();
3788 }
3789
3790 st_insert(cr->local_storage, (st_data_t)key, (st_data_t)ptr);
3791
3792 if (rb_ractor_main_p()) {
3793 key->main_cache = ptr;
3794 }
3795}
3796
3797VALUE
3799{
3800 void *val;
3801 if (ractor_local_ref(key, &val)) {
3802 return (VALUE)val;
3803 }
3804 else {
3805 return Qnil;
3806 }
3807}
3808
3809bool
3811{
3812 if (ractor_local_ref(key, (void **)val)) {
3813 return true;
3814 }
3815 else {
3816 return false;
3817 }
3818}
3819
3820void
3822{
3823 ractor_local_set(key, (void *)val);
3824}
3825
3826void *
3828{
3829 void *ret;
3830 if (ractor_local_ref(key, &ret)) {
3831 return ret;
3832 }
3833 else {
3834 return NULL;
3835 }
3836}
3837
3838void
3840{
3841 ractor_local_set(key, ptr);
3842}
3843
3844#define DEFAULT_KEYS_CAPA 0x10
3845
3846void
3847rb_ractor_finish_marking(void)
3848{
3849 for (int i=0; i<freed_ractor_local_keys.cnt; i++) {
3850 ruby_xfree(freed_ractor_local_keys.keys[i]);
3851 }
3852 freed_ractor_local_keys.cnt = 0;
3853 if (freed_ractor_local_keys.capa > DEFAULT_KEYS_CAPA) {
3854 freed_ractor_local_keys.capa = DEFAULT_KEYS_CAPA;
3855 REALLOC_N(freed_ractor_local_keys.keys, rb_ractor_local_key_t, DEFAULT_KEYS_CAPA);
3856 }
3857}
3858
3859static VALUE
3860ractor_local_value(rb_execution_context_t *ec, VALUE self, VALUE sym)
3861{
3862 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
3863 ID id = rb_check_id(&sym);
3864 struct rb_id_table *tbl = cr->idkey_local_storage;
3865 VALUE val;
3866
3867 if (id && tbl && rb_id_table_lookup(tbl, id, &val)) {
3868 return val;
3869 }
3870 else {
3871 return Qnil;
3872 }
3873}
3874
3875static VALUE
3876ractor_local_value_set(rb_execution_context_t *ec, VALUE self, VALUE sym, VALUE val)
3877{
3878 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
3879 ID id = SYM2ID(rb_to_symbol(sym));
3880 struct rb_id_table *tbl = cr->idkey_local_storage;
3881
3882 if (tbl == NULL) {
3883 tbl = cr->idkey_local_storage = rb_id_table_create(2);
3884 }
3885 rb_id_table_insert(tbl, id, val);
3886 return val;
3887}
3888
3891 struct rb_id_table *tbl;
3892 ID id;
3893 VALUE sym;
3894};
3895
3896static VALUE
3897ractor_local_value_store_i(VALUE ptr)
3898{
3899 VALUE val;
3901
3902 if (rb_id_table_lookup(data->tbl, data->id, &val)) {
3903 // after synchronization, we found already registered entry
3904 }
3905 else {
3906 val = rb_yield(Qnil);
3907 ractor_local_value_set(data->ec, Qnil, data->sym, val);
3908 }
3909 return val;
3910}
3911
3912static VALUE
3913ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE sym)
3914{
3915 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
3916 struct ractor_local_storage_store_data data = {
3917 .ec = ec,
3918 .sym = sym,
3919 .id = SYM2ID(rb_to_symbol(sym)),
3920 .tbl = cr->idkey_local_storage,
3921 };
3922 VALUE val;
3923
3924 if (data.tbl == NULL) {
3925 data.tbl = cr->idkey_local_storage = rb_id_table_create(2);
3926 }
3927 else if (rb_id_table_lookup(data.tbl, data.id, &val)) {
3928 // already set
3929 return val;
3930 }
3931
3932 if (!cr->local_storage_store_lock) {
3933 cr->local_storage_store_lock = rb_mutex_new();
3934 }
3935
3936 return rb_mutex_synchronize(cr->local_storage_store_lock, ractor_local_value_store_i, (VALUE)&data);
3937}
3938
3939// Ractor::Channel (emulate with Ractor)
3940
3942
3943static VALUE
3944ractor_channel_func(RB_BLOCK_CALL_FUNC_ARGLIST(y, c))
3945{
3946 rb_execution_context_t *ec = GET_EC();
3947 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
3948
3949 while (1) {
3950 int state;
3951
3952 EC_PUSH_TAG(ec);
3953 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
3954 VALUE obj = ractor_receive(ec, cr);
3955 ractor_yield(ec, cr, obj, Qfalse);
3956 }
3957 EC_POP_TAG();
3958
3959 if (state) {
3960 // ignore the error
3961 break;
3962 }
3963 }
3964
3965 return Qnil;
3966}
3967
3968static VALUE
3969rb_ractor_channel_new(void)
3970{
3971#if 0
3972 return rb_funcall(rb_const_get(rb_cRactor, rb_intern("Channel")), rb_intern("new"), 0);
3973#else
3974 // class Channel
3975 // def self.new
3976 // Ractor.new do # func body
3977 // while true
3978 // obj = Ractor.receive
3979 // Ractor.yield obj
3980 // end
3981 // rescue Ractor::ClosedError
3982 // nil
3983 // end
3984 // end
3985 // end
3986
3987 return ractor_create_func(rb_cRactor, Qnil, rb_str_new2("Ractor/channel"), rb_ary_new(), ractor_channel_func);
3988#endif
3989}
3990
3991static VALUE
3992rb_ractor_channel_yield(rb_execution_context_t *ec, VALUE vch, VALUE obj)
3993{
3994 VM_ASSERT(ec == rb_current_ec_noinline());
3995 rb_ractor_channel_t *ch = RACTOR_PTR(vch);
3996
3997 ractor_send(ec, (rb_ractor_t *)ch, obj, Qfalse);
3998 return Qnil;
3999}
4000
4001static VALUE
4002rb_ractor_channel_take(rb_execution_context_t *ec, VALUE vch)
4003{
4004 VM_ASSERT(ec == rb_current_ec_noinline());
4005 rb_ractor_channel_t *ch = RACTOR_PTR(vch);
4006
4007 return ractor_take(ec, (rb_ractor_t *)ch);
4008}
4009
4010static VALUE
4011rb_ractor_channel_close(rb_execution_context_t *ec, VALUE vch)
4012{
4013 VM_ASSERT(ec == rb_current_ec_noinline());
4014 rb_ractor_channel_t *ch = RACTOR_PTR(vch);
4015
4016 ractor_close_incoming(ec, (rb_ractor_t *)ch);
4017 return ractor_close_outgoing(ec, (rb_ractor_t *)ch);
4018}
4019
4020// Ractor#require
4021
4023 VALUE ch;
4024 VALUE result;
4025 VALUE exception;
4026
4027 // require
4028 VALUE feature;
4029
4030 // autoload
4031 VALUE module;
4032 ID name;
4033};
4034
4035static VALUE
4036require_body(VALUE data)
4037{
4038 struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
4039
4040 ID require;
4041 CONST_ID(require, "require");
4042 crr->result = rb_funcallv(Qnil, require, 1, &crr->feature);
4043
4044 return Qnil;
4045}
4046
4047static VALUE
4048require_rescue(VALUE data, VALUE errinfo)
4049{
4050 struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
4051 crr->exception = errinfo;
4052 return Qundef;
4053}
4054
4055static VALUE
4056require_result_copy_body(VALUE data)
4057{
4058 struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
4059
4060 if (crr->exception != Qundef) {
4061 VM_ASSERT(crr->result == Qundef);
4062 crr->exception = ractor_copy(crr->exception);
4063 }
4064 else{
4065 VM_ASSERT(crr->result != Qundef);
4066 crr->result = ractor_copy(crr->result);
4067 }
4068
4069 return Qnil;
4070}
4071
4072static VALUE
4073require_result_copy_resuce(VALUE data, VALUE errinfo)
4074{
4075 struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
4076 crr->exception = errinfo; // ractor_move(crr->exception);
4077 return Qnil;
4078}
4079
4080static VALUE
4081ractor_require_protect(struct cross_ractor_require *crr, VALUE (*func)(VALUE))
4082{
4083 // catch any error
4084 rb_rescue2(func, (VALUE)crr,
4085 require_rescue, (VALUE)crr, rb_eException, 0);
4086
4087 rb_rescue2(require_result_copy_body, (VALUE)crr,
4088 require_result_copy_resuce, (VALUE)crr, rb_eException, 0);
4089
4090 rb_ractor_channel_yield(GET_EC(), crr->ch, Qtrue);
4091 return Qnil;
4092
4093}
4094
4095static VALUE
4096ractore_require_func(void *data)
4097{
4098 struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
4099 return ractor_require_protect(crr, require_body);
4100}
4101
4102VALUE
4103rb_ractor_require(VALUE feature)
4104{
4105 // TODO: make feature shareable
4106 struct cross_ractor_require crr = {
4107 .feature = feature, // TODO: ractor
4108 .ch = rb_ractor_channel_new(),
4109 .result = Qundef,
4110 .exception = Qundef,
4111 };
4112
4113 rb_execution_context_t *ec = GET_EC();
4114 rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
4115 rb_ractor_interrupt_exec(main_r, ractore_require_func, &crr, 0);
4116
4117 // wait for require done
4118 rb_ractor_channel_take(ec, crr.ch);
4119 rb_ractor_channel_close(ec, crr.ch);
4120
4121 if (crr.exception != Qundef) {
4122 rb_exc_raise(crr.exception);
4123 }
4124 else {
4125 return crr.result;
4126 }
4127}
4128
4129static VALUE
4130ractor_require(rb_execution_context_t *ec, VALUE self, VALUE feature)
4131{
4132 return rb_ractor_require(feature);
4133}
4134
4135static VALUE
4136autoload_load_body(VALUE data)
4137{
4138 struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
4139 crr->result = rb_autoload_load(crr->module, crr->name);
4140 return Qnil;
4141}
4142
4143static VALUE
4144ractor_autoload_load_func(void *data)
4145{
4146 struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
4147 return ractor_require_protect(crr, autoload_load_body);
4148}
4149
4150VALUE
4151rb_ractor_autoload_load(VALUE module, ID name)
4152{
4153 struct cross_ractor_require crr = {
4154 .module = module,
4155 .name = name,
4156 .ch = rb_ractor_channel_new(),
4157 .result = Qundef,
4158 .exception = Qundef,
4159 };
4160
4161 rb_execution_context_t *ec = GET_EC();
4162 rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
4163 rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, &crr, 0);
4164
4165 // wait for require done
4166 rb_ractor_channel_take(ec, crr.ch);
4167 rb_ractor_channel_close(ec, crr.ch);
4168
4169 if (crr.exception != Qundef) {
4170 rb_exc_raise(crr.exception);
4171 }
4172 else {
4173 return crr.result;
4174 }
4175}
4176
4177#include "ractor.rbinc"
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define RUBY_ATOMIC_CAS(var, oldval, newval)
Atomic compare-and-swap.
Definition atomic.h:140
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:93
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_singleton_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:883
@ RUBY_FL_SHAREABLE
This flag has something to do with Ractor.
Definition fl_type.h:266
VALUE rb_define_class(const char *name, VALUE super)
Defines a top-level class.
Definition class.c:980
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
Defines a class under the namespace of outer.
Definition class.c:1012
int rb_scan_args(int argc, const VALUE *argv, const char *fmt,...)
Retrieves argument from argc and argv to given VALUE references according to the format string.
Definition class.c:2635
int rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values)
Keyword argument deconstructor.
Definition class.c:2424
#define rb_str_new2
Old name of rb_str_new_cstr.
Definition string.h:1675
#define T_COMPLEX
Old name of RUBY_T_COMPLEX.
Definition value_type.h:59
#define T_FILE
Old name of RUBY_T_FILE.
Definition value_type.h:62
#define FL_UNSET_RAW
Old name of RB_FL_UNSET_RAW.
Definition fl_type.h:134
#define FL_EXIVAR
Old name of RUBY_FL_EXIVAR.
Definition fl_type.h:66
#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 ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#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 FL_SEEN_OBJ_ID
Old name of RUBY_FL_SEEN_OBJ_ID.
Definition fl_type.h:65
#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 FL_TEST_RAW
Old name of RB_FL_TEST_RAW.
Definition fl_type.h:132
#define Qtrue
Old name of RUBY_Qtrue.
#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:67
#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:130
#define T_REGEXP
Old name of RUBY_T_REGEXP.
Definition value_type.h:77
void rb_exc_raise(VALUE mesg)
Raises an exception in the current thread.
Definition eval.c:676
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:181
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:23
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:172
VALUE rb_cBasicObject
BasicObject class.
Definition object.c:64
VALUE rb_obj_clone(VALUE obj)
Produces a shallow copy of the given object.
Definition object.c:521
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:1099
#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_obj_is_proc(VALUE recv)
Queries if the given object is a proc.
Definition proc.c:119
#define rb_exc_new_cstr(exc, str)
Identical to rb_exc_new(), except it assumes the passed pointer is a pointer to a C string.
Definition string.h:1670
VALUE rb_str_new_frozen(VALUE str)
Creates a frozen copy of the string, if necessary.
Definition string.c:1831
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.
void rb_unblock_function_t(void *)
This is the type of UBFs.
Definition thread.h:336
VALUE rb_mutex_unlock(VALUE mutex)
Releases the mutex.
VALUE rb_mutex_lock(VALUE mutex)
Attempts to lock the mutex.
void rb_thread_sleep(int sec)
Blocks for the given period of time.
Definition thread.c:1456
VALUE rb_const_get(VALUE space, ID name)
Identical to rb_const_defined(), except it returns the actual defined value.
Definition variable.c:3215
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:1924
VALUE rb_autoload_load(VALUE space, ID name)
Kicks the autoload procedure as if it was "touched".
Definition variable.c:3050
void rb_undef_alloc_func(VALUE klass)
Deletes the allocator function of a class.
Definition vm_method.c:1289
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:12895
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:3718
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:3110
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:3827
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:3839
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:3729
VALUE rb_ractor_stdin(void)
Queries the standard input of the current Ractor that is calling this function.
Definition ractor.c:2726
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:2786
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:3821
bool rb_ractor_local_storage_value_lookup(rb_ractor_local_key_t key, VALUE *val)
Queries the key.
Definition ractor.c:3810
#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:3101
rb_ractor_local_key_t rb_ractor_local_storage_value_newkey(void)
Issues a new key.
Definition ractor.c:3738
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:2774
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:2762
VALUE rb_ractor_local_storage_value(rb_ractor_local_key_t key)
Queries the key.
Definition ractor.c:3798
#define RB_NOGVL_UBF_ASYNC_SAFE
Passing this flag to rb_nogvl() indicates that the passed UBF is async-signal-safe.
Definition thread.h:60
#define RB_NOGVL_INTR_FAIL
Passing this flag to rb_nogvl() prevents it from checking interrupts.
Definition thread.h:48
void * rb_nogvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2, int flags)
Identical to rb_thread_call_without_gvl(), except it additionally takes "flags" that change the behav...
Definition thread.c:1538
#define RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, callback_arg)
Shim for block function parameters.
Definition iterator.h:58
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1354
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.
VALUE rb_ensure(type *q, VALUE w, type *e, VALUE r)
An equivalent of ensure 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 RARRAY_CONST_PTR
Just another name of rb_array_const_ptr.
Definition rarray.h:52
#define RBASIC(obj)
Convenient casting macro.
Definition rbasic.h:40
#define DATA_PTR(obj)
Convenient getter macro.
Definition rdata.h:67
#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_IVPTR(VALUE obj)
Queries the instance variables.
Definition robject.h:126
#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:579
#define TypedData_Wrap_Struct(klass, data_type, sval)
Converts sval, a pointer to your struct, into a Ruby object.
Definition rtypeddata.h:449
#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:497
static const struct rb_data_type_struct * RTYPEDDATA_TYPE(VALUE obj)
Queries for the type of given object.
Definition rtypeddata.h:602
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:63
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:200
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
Definition string.c:8690
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_cond_initialize(rb_nativethread_cond_t *cond)
Fills the passed condition variable with an initial value.
void rb_native_cond_broadcast(rb_nativethread_cond_t *cond)
Signals a condition variable.
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.
void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex)
Waits for the passed condition variable to be signalled.
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