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