Ruby 4.1.0dev (2026-09-04 revision 4eaa91451d9394c5dbd5ecbb6f5c678b1820a376)
ractor_sync.c (4eaa91451d9394c5dbd5ecbb6f5c678b1820a376)
1// this file is included by ractor.c
2
3struct ractor_port {
5 st_data_t id_;
6};
7
8static st_data_t
9ractor_port_id(const struct ractor_port *rp)
10{
11 return rp->id_;
12}
13
14static VALUE rb_cRactorPort;
15
16static VALUE ractor_receive(rb_execution_context_t *ec, const struct ractor_port *rp, const rb_hrtime_t *end);
17static VALUE ractor_send(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move);
18static struct ractor_basket *ractor_basket_new_ref(VALUE shareable);
19static void ractor_send_basket(rb_execution_context_t *ec, const struct ractor_port *rp, struct ractor_basket *b, bool raise_on_error);
20static void ractor_add_port(rb_ractor_t *r, st_data_t id);
21
22// The off-heap courier a copy or a move payload travels in. Defined in ractor.c.
23struct rb_ractor_courier *rb_ractor_courier_build_move(VALUE obj, struct rb_ractor_courier **slot);
24VALUE rb_ractor_courier_materialize(struct rb_ractor_courier *c);
25void rb_ractor_courier_free(struct rb_ractor_courier *c);
26static void ractor_off_queue_add(rb_ractor_t *cr, struct ractor_basket *b);
27static void ractor_off_queue_remove(struct ractor_basket *b);
28void rb_ractor_courier_mark(struct rb_ractor_courier *c);
29struct rb_ractor_courier *rb_ractor_courier_build_copy(VALUE obj, struct rb_ractor_courier **slot);
30
31static void ractor_port_note_alive(const struct ractor_port *rp);
32
33static void
34ractor_port_mark(void *ptr)
35{
36 const struct ractor_port *rp = (struct ractor_port *)ptr;
37
38 if (rp->r) {
39 rb_gc_mark(rp->r->pub.self);
40
41 /* Only a mark that covers every objspace can call a port dead. Ask the single
42 * objspace first: it answers without the VM, which a GC worker thread cannot
43 * reach (mmtk marks from several of them). */
44 if (rb_gc_single_objspace_p() || rb_gc_during_global_gc_p()) {
45 ractor_port_note_alive(rp);
46 }
47 }
48}
49
50static const rb_data_type_t ractor_port_data_type = {
51 "ractor/port",
52 {
53 ractor_port_mark,
55 NULL, // memsize
56 NULL, // update
57 },
58 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
59};
60
61static st_data_t
62ractor_genid_for_port(rb_ractor_t *cr)
63{
64 // TODO: enough?
65 return cr->sync.next_port_id++;
66}
67
68static struct ractor_port *
69RACTOR_PORT_PTR(VALUE self)
70{
71 VM_ASSERT(rb_typeddata_is_kind_of(self, &ractor_port_data_type));
72 return RTYPEDDATA_GET_DATA(self);
73}
74
75// r is NULL between Ractor::Port.allocate and ractor_port_init()
76static struct ractor_port *
77ractor_port_ptr_check(VALUE self)
78{
79 struct ractor_port *rp = RACTOR_PORT_PTR(self);
80
81 if (UNLIKELY(rp->r == NULL)) {
82 rb_raise(rb_eTypeError, "uninitialized %"PRIsVALUE, rb_obj_class(self));
83 }
84
85 return rp;
86}
87
88static VALUE
89ractor_port_alloc(VALUE klass)
90{
91 struct ractor_port *rp;
92 VALUE rpv = TypedData_Make_Struct(klass, struct ractor_port, &ractor_port_data_type, rp);
93 rb_obj_freeze(rpv);
94 return rpv;
95}
96
97static VALUE
98ractor_port_init(VALUE rpv, rb_ractor_t *r)
99{
100 struct ractor_port *rp = RACTOR_PORT_PTR(rpv);
101
102 rp->r = r;
103 RB_OBJ_WRITTEN(rpv, Qundef, r->pub.self);
104 rp->id_ = ractor_genid_for_port(r);
105
106 ractor_add_port(r, ractor_port_id(rp));
107
108 rb_obj_freeze(rpv);
109
110 return rpv;
111}
112
113/*
114 * call-seq:
115 * Ractor::Port.new -> new_port
116 *
117 * Returns a new Ractor::Port object.
118 */
119static VALUE
120ractor_port_initialize(VALUE self)
121{
122 return ractor_port_init(self, GET_RACTOR());
123}
124
125/* :nodoc: */
126static VALUE
127ractor_port_initialize_copy(VALUE self, VALUE orig)
128{
129 struct ractor_port *dst = RACTOR_PORT_PTR(self); // uninitialized by definition
130 struct ractor_port *src = ractor_port_ptr_check(orig);
131 dst->r = src->r;
132 RB_OBJ_WRITTEN(self, Qundef, dst->r->pub.self);
133 dst->id_ = ractor_port_id(src);
134
135 return self;
136}
137
138static VALUE
139ractor_port_new(rb_ractor_t *r)
140{
141 VALUE rpv = ractor_port_alloc(rb_cRactorPort);
142 ractor_port_init(rpv, r);
143 return rpv;
144}
145
146static bool
147ractor_port_p(VALUE self)
148{
149 return rb_typeddata_is_kind_of(self, &ractor_port_data_type);
150}
151
152static const rb_hrtime_t *ractor_timeout_deadline(VALUE timeout, rb_hrtime_t *storage);
153
154static VALUE
155ractor_port_receive(rb_execution_context_t *ec, VALUE self, VALUE timeout)
156{
157 const struct ractor_port *rp = ractor_port_ptr_check(self);
158
159 if (rp->r != rb_ec_ractor_ptr(ec)) {
160 rb_raise(rb_eRactorError, "only allowed from the creator Ractor of this port");
161 }
162
163 rb_hrtime_t deadline;
164 const rb_hrtime_t *end = ractor_timeout_deadline(timeout, &deadline);
165
166 VALUE v = ractor_receive(ec, rp, end);
167 RB_GC_GUARD(self);
168
169 // no message before the timeout
170 return UNDEF_P(v) ? Qnil : v;
171}
172
173static VALUE
174ractor_port_send(rb_execution_context_t *ec, VALUE self, VALUE obj, VALUE move)
175{
176 const struct ractor_port *rp = ractor_port_ptr_check(self);
177 ractor_send(ec, rp, obj, RTEST(move));
178 RB_GC_GUARD(self);
179 return self;
180}
181
182static bool ractor_closed_port_p(rb_execution_context_t *ec, rb_ractor_t *r, const struct ractor_port *rp);
183static bool ractor_close_port(rb_execution_context_t *ec, rb_ractor_t *r, const struct ractor_port *rp);
184
185static VALUE
186ractor_port_closed_p(rb_execution_context_t *ec, VALUE self)
187{
188 const struct ractor_port *rp = ractor_port_ptr_check(self);
189 rb_ractor_t *r = rp->r;
190 bool closed;
191
192 if (rb_ec_ractor_ptr(ec) == r) {
193 /* The owner's threads are serialized by the ractor GVL, so the ports
194 * table can't change under this lookup. */
195 closed = ractor_closed_port_p(ec, r, rp);
196 }
197 else {
198 /* A foreign Ractor races the owner's st_insert/st_delete on the ports
199 * table; take the lock like every other foreign reader. ractor_closed_port_p
200 * asserts the lock is held for foreign access, and Port#closed? was the
201 * only path reaching it without the lock. */
202 RACTOR_LOCK(r);
203 {
204 closed = ractor_closed_port_p(ec, r, rp);
205 }
206 RACTOR_UNLOCK(r);
207 }
208
209 return closed ? Qtrue : Qfalse;
210}
211
212static VALUE
213ractor_port_close(rb_execution_context_t *ec, VALUE self)
214{
215 const struct ractor_port *rp = ractor_port_ptr_check(self);
216 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
217
218 if (cr != rp->r) {
219 rb_raise(rb_eRactorError, "closing port by other ractors is not allowed");
220 }
221
222 ractor_close_port(ec, cr, rp);
223 return self;
224}
225
226// ractor-internal
227
228// ractor-internal - ractor_basket
229
230enum ractor_basket_type {
231 // basket is empty
232 basket_type_none,
233
234 // value is available
235 basket_type_ref,
236 basket_type_copy,
237 basket_type_move,
238};
239
241 enum ractor_basket_type type;
242 VALUE sender;
243 st_data_t port_id;
244
245 struct {
246 VALUE v;
247 bool exception;
248 /* True when v held a type the native copier does not support and became a
249 * Marshal byte String. The receiver rebuilds it with Marshal.load instead
250 * of walking it natively. */
251 bool marshaled;
252 /* The off-heap (xmalloc) courier the payload graph was serialized into.
253 * Copy and move both use it; when set, v is unused. */
254 struct rb_ractor_courier *courier;
255 /* The marshaled bytes of a copy payload, off-heap like the courier. When
256 * set, v is unused: an in-flight payload that is not a GC object needs no
257 * in-flight pin, so it never keeps a page of the sender's heap alive. */
258 char *mbuf;
259 size_t mlen;
260 } p; // payload
261
262 struct ccan_list_node node; /* the port queue it waits on */
263 struct ccan_list_node off_queue_node; /* or sync.off_queue_baskets, when on none */
264};
265
266#if 0
267static inline bool
268ractor_basket_type_p(const struct ractor_basket *b, enum ractor_basket_type type)
269{
270 return b->type == type;
271}
272
273static inline bool
274ractor_basket_none_p(const struct ractor_basket *b)
275{
276 return ractor_basket_type_p(b, basket_type_none);
277}
278#endif
279
280static void
281ractor_basket_mark(const struct ractor_basket *b)
282{
283 if (b->p.courier != NULL) {
284 /* The payload became this Ractor's to root the moment the message was enqueued
285 * here: the sender's own roots stop at the send. Before and after the queue the
286 * basket is on its holder's off_queue_baskets instead, so a courier is rooted
287 * from the moment it is allocated to the moment it is freed. */
288 rb_ractor_courier_mark(b->p.courier);
289 }
290 else if (b->p.mbuf == NULL) {
291 /* Marshaled bytes are off-heap and hold nothing to mark. */
292 rb_gc_mark(b->p.v);
293 }
294}
295
296static void
297ractor_basket_free(struct ractor_basket *b)
298{
299 ractor_off_queue_remove(b);
300 ruby_xfree(b->p.mbuf);
301 b->p.mbuf = NULL;
302 b->p.mlen = 0;
303 if (b->p.courier) {
304 /* A courier that was never consumed (a queue being torn down, say). */
305 rb_ractor_courier_free(b->p.courier);
306 b->p.courier = NULL;
307 }
308 SIZED_FREE(b);
309}
310
311static struct ractor_basket *
312ractor_basket_alloc(void)
313{
314 struct ractor_basket *b = ALLOC(struct ractor_basket);
315
316 /* Empty and mark-safe from the start: a basket goes on its holder's in-flight list
317 * before it has a payload, so a GC can walk it while it is still being filled. */
318 b->type = basket_type_none;
319 b->sender = Qnil;
320 b->port_id = 0;
321 b->p.v = Qnil;
322 b->p.exception = false;
323 b->p.marshaled = false;
324 b->p.courier = NULL;
325 b->p.mbuf = NULL;
326 b->p.mlen = 0;
327 ccan_list_node_init(&b->off_queue_node);
328
329 return b;
330}
331
332/* A basket is rooted by whoever holds it: a port queue while it waits there, and its
333 * holder's off-queue list while it is being built or materialized. */
334static void
335ractor_off_queue_add(rb_ractor_t *cr, struct ractor_basket *b)
336{
337 VM_ASSERT(cr == rb_current_ractor_raw(false));
338 ccan_list_add_tail(&cr->sync.off_queue_baskets, &b->off_queue_node);
339}
340
341static void
342ractor_off_queue_remove(struct ractor_basket *b)
343{
344 ccan_list_del_init(&b->off_queue_node);
345}
346
347static void
348ractor_mark_off_queue_baskets(rb_ractor_t *r)
349{
350 struct ractor_basket *b;
351 ccan_list_for_each(&r->sync.off_queue_baskets, b, off_queue_node) {
352 ractor_basket_mark(b);
353 }
354}
355
356// ractor-internal - ractor_queue
357
359 struct ccan_list_head set;
360 bool closed;
361 bool alive; /* its Ractor::Port is still reachable; see the reap below */
362};
363
364static void
365ractor_queue_init(struct ractor_queue *rq)
366{
367 ccan_list_head_init(&rq->set);
368 rq->closed = false;
369 rq->alive = true;
370}
371
372static struct ractor_queue *
373ractor_queue_new(void)
374{
375 struct ractor_queue *rq = ALLOC(struct ractor_queue);
376 ractor_queue_init(rq);
377 return rq;
378}
379
380static void
381ractor_port_note_alive(const struct ractor_port *rp)
382{
383 struct ractor_queue *rq;
384
385 if (rp->r->sync.ports && st_lookup(rp->r->sync.ports, rp->id_, (st_data_t *)&rq)) {
386 /* Several markers can reach the same port at once (mmtk marks from its GC worker
387 * threads), but they all store the same value and the reap reads it once marking
388 * is over. */
389 rq->alive = true;
390 }
391}
392
393static void
394ractor_queue_mark(const struct ractor_queue *rq)
395{
396 const struct ractor_basket *b;
397
398 ccan_list_for_each(&rq->set, b, node) {
399 ractor_basket_mark(b);
400 }
401}
402
403static void
404ractor_queue_free(struct ractor_queue *rq)
405{
406 struct ractor_basket *b, *nxt;
407
408 ccan_list_for_each_safe(&rq->set, b, nxt, node) {
409 ccan_list_del_init(&b->node);
410 ractor_basket_free(b);
411 }
412
413 VM_ASSERT(ccan_list_empty(&rq->set));
414
415 SIZED_FREE(rq);
416}
417
419static size_t
420ractor_queue_size(const struct ractor_queue *rq)
421{
422 size_t size = 0;
423 const struct ractor_basket *b;
424
425 ccan_list_for_each(&rq->set, b, node) {
426 size++;
427 }
428 return size;
429}
430
431static void
432ractor_queue_close(struct ractor_queue *rq)
433{
434 rq->closed = true;
435}
436
437static void
438ractor_queue_move(struct ractor_queue *dst_rq, struct ractor_queue *src_rq)
439{
440 struct ccan_list_head *src = &src_rq->set;
441 struct ccan_list_head *dst = &dst_rq->set;
442
443 dst->n.next = src->n.next;
444 dst->n.prev = src->n.prev;
445 dst->n.next->prev = &dst->n;
446 dst->n.prev->next = &dst->n;
447 ccan_list_head_init(src);
448}
449
450#if 0
451static struct ractor_basket *
452ractor_queue_head(rb_ractor_t *r, struct ractor_queue *rq)
453{
454 return ccan_list_top(&rq->set, struct ractor_basket, node);
455}
456#endif
457
458static bool
459ractor_queue_empty_p(rb_ractor_t *r, const struct ractor_queue *rq)
460{
461 return ccan_list_empty(&rq->set);
462}
463
464static struct ractor_basket *
465ractor_queue_deq(rb_ractor_t *r, struct ractor_queue *rq)
466{
467 VM_ASSERT(GET_RACTOR() == r);
468
469 return ccan_list_pop(&rq->set, struct ractor_basket, node);
470}
471
472static void
473ractor_queue_enq(rb_ractor_t *r, struct ractor_queue *rq, struct ractor_basket *basket)
474{
475 ccan_list_add_tail(&rq->set, &basket->node);
476}
477
478#if 0
479static void
480rq_dump(const struct ractor_queue *rq)
481{
482 int i=0;
483 struct ractor_basket *b;
484 ccan_list_for_each(&rq->set, b, node) {
485 fprintf(stderr, "%d type:%s %p\n", i, basket_type_name(b->type), (void *)b);
486 i++;
487 }
488}
489#endif
490
491static void ractor_delete_port(rb_ractor_t *cr, st_data_t id, bool locked);
492
493static struct ractor_queue *
494ractor_get_queue(rb_ractor_t *cr, st_data_t id, bool locked)
495{
496 VM_ASSERT(cr == GET_RACTOR());
497
498 struct ractor_queue *rq;
499
500 if (cr->sync.ports && st_lookup(cr->sync.ports, id, (st_data_t *)&rq)) {
501 if (rq->closed && ractor_queue_empty_p(cr, rq)) {
502 ractor_delete_port(cr, id, locked);
503 return NULL;
504 }
505 else {
506 return rq;
507 }
508 }
509 else {
510 return NULL;
511 }
512}
513
514// ractor-internal - ports
515
516static void
517ractor_add_port(rb_ractor_t *r, st_data_t id)
518{
519 struct ractor_queue *rq = ractor_queue_new();
520 ASSERT_ractor_unlocking(r);
521
522 RUBY_DEBUG_LOG("id:%u", (unsigned int)id);
523
524 // Rebuilding the table on insertion can run GC by the allocation and the
525 // GC acquires the VM lock, which is prohibited under the ractor lock.
526 st_table *const old_tab = r->sync.ports;
527 bool inserted;
528
529 RACTOR_LOCK(r);
530 {
531 inserted = st_insert_no_rebuild(old_tab, id, (st_data_t)rq) >= 0;
532 }
533 RACTOR_UNLOCK(r);
534
535 if (!inserted) {
536 // The table is full. Rebuild it outside of the ractor lock (mutators
537 // are serialized by the per-ractor GVL) and swap it under the lock
538 // to exclude the readers (other ractors).
539 st_table *const new_tab = st_copy(old_tab);
540 st_insert(new_tab, id, (st_data_t)rq);
541
542 RACTOR_LOCK(r);
543 {
544 VM_ASSERT(r->sync.ports == old_tab);
545 r->sync.ports = new_tab;
546 }
547 RACTOR_UNLOCK(r);
548
549 st_free_table(old_tab);
550 }
551}
552
553static void
554ractor_delete_port_locked(rb_ractor_t *cr, st_data_t id)
555{
556 ASSERT_ractor_locking(cr);
557
558 RUBY_DEBUG_LOG("id:%u", (unsigned int)id);
559
560 struct ractor_queue *rq;
561
562 if (st_delete(cr->sync.ports, &id, (st_data_t *)&rq)) {
563 ractor_queue_free(rq);
564 }
565 else {
566 VM_ASSERT(0);
567 }
568}
569
570static void
571ractor_delete_port(rb_ractor_t *cr, st_data_t id, bool locked)
572{
573 if (locked) {
574 ractor_delete_port_locked(cr, id);
575 }
576 else {
577 RACTOR_LOCK_SELF(cr);
578 {
579 ractor_delete_port_locked(cr, id);
580 }
581 RACTOR_UNLOCK_SELF(cr);
582 }
583}
584
585static const struct ractor_port *
586ractor_default_port(rb_ractor_t *r)
587{
588 return RACTOR_PORT_PTR(r->sync.default_port_value);
589}
590
591static VALUE
592ractor_default_port_value(rb_ractor_t *r)
593{
594 return r->sync.default_port_value;
595}
596
597static bool
598ractor_closed_port_p(rb_execution_context_t *ec, rb_ractor_t *r, const struct ractor_port *rp)
599{
600 VM_ASSERT(rb_ec_ractor_ptr(ec) == rp->r ? 1 : (ASSERT_ractor_locking(rp->r), 1));
601
602 const struct ractor_queue *rq;
603
604 if (rp->r->sync.ports && st_lookup(rp->r->sync.ports, ractor_port_id(rp), (st_data_t *)&rq)) {
605 return rq->closed;
606 }
607 else {
608 return true;
609 }
610}
611
612static void ractor_deliver_incoming_messages(rb_execution_context_t *ec, rb_ractor_t *cr);
613static bool ractor_queue_empty_p(rb_ractor_t *r, const struct ractor_queue *rq);
614
615static bool
616ractor_close_port(rb_execution_context_t *ec, rb_ractor_t *cr, const struct ractor_port *rp)
617{
618 VM_ASSERT(cr == rp->r);
619 struct ractor_queue *rq = NULL;
620
621 RACTOR_LOCK_SELF(cr);
622 {
623 ractor_deliver_incoming_messages(ec, cr); // check incoming messages
624
625 if (st_lookup(rp->r->sync.ports, ractor_port_id(rp), (st_data_t *)&rq)) {
626 ractor_queue_close(rq);
627
628 if (ractor_queue_empty_p(cr, rq)) {
629 // delete from the table
630 ractor_delete_port(cr, ractor_port_id(rp), true);
631 }
632
633 // TODO: free rq
634 }
635 }
636 RACTOR_UNLOCK_SELF(cr);
637
638 return rq != NULL;
639}
640
641/* A port is the only way to receive from its queue, so a queue whose port is gone is
642 * unreachable -- but the table is keyed by id, so no sweep finds it. Mark and sweep the
643 * table itself: ractor_port_mark sets the flag, this clears it for the next cycle. */
644static int
645ractor_reap_dead_ports_i(st_data_t port_id, st_data_t val, st_data_t dat)
646{
647 struct ractor_queue *rq = (struct ractor_queue *)val;
648
649 if (rq->alive) {
650 rq->alive = false;
651 return ST_CONTINUE;
652 }
653 else {
654 ractor_queue_free(rq);
655 return ST_DELETE;
656 }
657}
658
659void
660rb_ractor_reap_dead_ports(rb_ractor_t *r)
661{
662 if (r->sync.ports) {
663 st_foreach(r->sync.ports, ractor_reap_dead_ports_i, 0);
664 }
665}
666
667static int
668ractor_free_all_ports_i(st_data_t port_id, st_data_t val, st_data_t dat)
669{
670 struct ractor_queue *rq = (struct ractor_queue *)val;
671 // rb_ractor_t *cr = (rb_ractor_t *)dat;
672
673 ractor_queue_free(rq);
674 return ST_CONTINUE;
675}
676
677static void
678ractor_free_all_ports(rb_ractor_t *cr)
679{
680 if (cr->sync.ports) {
681 st_foreach(cr->sync.ports, ractor_free_all_ports_i, (st_data_t)cr);
682 st_free_table(cr->sync.ports);
683 cr->sync.ports = NULL;
684 }
685
686 if (cr->sync.recv_queue) {
687 ractor_queue_free(cr->sync.recv_queue);
688 cr->sync.recv_queue = NULL;
689 }
690}
691
692#if defined(HAVE_WORKING_FORK)
693static void
694ractor_sync_terminate_atfork(rb_vm_t *vm, rb_ractor_t *r)
695{
696 ractor_free_all_ports(r);
697 r->sync.legacy = Qnil;
698}
699#endif
700
701// Ractor#monitor
702
704 struct ractor_port port;
705 struct ccan_list_node node;
706};
707
708/* Mark the Ractors monitoring r. ractor_notify_exit sends the exit token through each
709 * entry's port, so the monitoring Ractor's struct must outlive r, and its wrapper is
710 * what keeps it alive. */
711static void
712ractor_mark_monitors(rb_ractor_t *r)
713{
714 const struct ractor_monitor *rm;
715 ccan_list_for_each(&r->sync.monitors, rm, node) {
716 rb_gc_mark(rm->port.r->pub.self);
717 }
718}
719
720static VALUE
721ractor_exit_token(bool exc)
722{
723 if (exc) {
724 RUBY_DEBUG_LOG("aborted");
725 return ID2SYM(idAborted);
726 }
727 else {
728 RUBY_DEBUG_LOG("exited");
729 return ID2SYM(idExited);
730 }
731}
732
733static VALUE
735{
736 rb_ractor_t *r = RACTOR_PTR(self);
737 bool terminated = false;
738 const struct ractor_port *rp = ractor_port_ptr_check(port);
739 struct ractor_monitor *rm = ALLOC(struct ractor_monitor);
740 rm->port = *rp; // copy port information
741
742 RACTOR_LOCK(r);
743 {
744 if (UNDEF_P(r->sync.legacy)) { // not terminated
745 RUBY_DEBUG_LOG("OK/r:%u -> port:%u@r%u", (unsigned int)rb_ractor_id(r), (unsigned int)ractor_port_id(&rm->port), (unsigned int)rb_ractor_id(rm->port.r));
746 ccan_list_add_tail(&r->sync.monitors, &rm->node);
747 }
748 else {
749 RUBY_DEBUG_LOG("NG/r:%u -> port:%u@r%u", (unsigned int)rb_ractor_id(r), (unsigned int)ractor_port_id(&rm->port), (unsigned int)rb_ractor_id(rm->port.r));
750 terminated = true;
751 }
752 }
753 RACTOR_UNLOCK(r);
754
755 if (terminated) {
756 SIZED_FREE(rm);
757 ractor_port_send(ec, port, ractor_exit_token(r->sync.legacy_exc), Qfalse);
758
759 return Qfalse;
760 }
761 else {
762 return Qtrue;
763 }
764}
765
766static VALUE
767ractor_unmonitor(rb_execution_context_t *ec, VALUE self, VALUE port)
768{
769 rb_ractor_t *r = RACTOR_PTR(self);
770 const struct ractor_port *rp = ractor_port_ptr_check(port);
771
772 RACTOR_LOCK(r);
773 {
774 if (UNDEF_P(r->sync.legacy)) { // not terminated
775 struct ractor_monitor *rm, *nxt;
776
777 ccan_list_for_each_safe(&r->sync.monitors, rm, nxt, node) {
778 if (rm->port.r == rp->r && ractor_port_id(&rm->port) == ractor_port_id(rp)) {
779 RUBY_DEBUG_LOG("r:%u -> port:%u@r%u",
780 (unsigned int)rb_ractor_id(r),
781 (unsigned int)ractor_port_id(&rm->port),
782 (unsigned int)rb_ractor_id(rm->port.r));
783 ccan_list_del(&rm->node);
784 SIZED_FREE(rm);
785 }
786 }
787 }
788 }
789 RACTOR_UNLOCK(r);
790
791 return self;
792}
793
794static void
795ractor_notify_exit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE legacy, bool exc)
796{
797 RUBY_DEBUG_LOG("exc:%d", exc);
798 VM_ASSERT(!UNDEF_P(legacy));
799 VM_ASSERT(cr->sync.legacy == Qundef);
800
801 RACTOR_LOCK_SELF(cr);
802 {
803 ractor_free_all_ports(cr);
804
805 cr->sync.legacy = legacy;
806 cr->sync.legacy_exc = exc;
807 }
808 RACTOR_UNLOCK_SELF(cr);
809
810}
811
812/* Sent after the dying thread's post-mortem collection: waking a joiner any earlier makes
813 * ractor_value spin for the whole of that collection. */
814static void
815ractor_send_exit_tokens(rb_execution_context_t *ec, rb_ractor_t *cr)
816{
817 VALUE token = ractor_exit_token(cr->sync.legacy_exc);
818 struct ractor_monitor *rm, *nxt;
819
820 ccan_list_for_each_safe(&cr->sync.monitors, rm, nxt, node)
821 {
822 RUBY_DEBUG_LOG("port:%u@r%u", (unsigned int)ractor_port_id(&rm->port), (unsigned int)rb_ractor_id(rm->port.r));
823
824 ractor_send_basket(ec, &rm->port, ractor_basket_new_ref(token), false);
825
826 ccan_list_del(&rm->node);
827 SIZED_FREE(rm);
828 }
829
830 VM_ASSERT(ccan_list_empty(&cr->sync.monitors));
831}
832
833// ractor-internal - initialize, mark, free, memsize
834
835static int
836ractor_mark_ports_i(st_data_t key, st_data_t val, st_data_t data)
837{
838 // id -> ractor_queue
839 const struct ractor_queue *rq = (struct ractor_queue *)val;
840 ractor_queue_mark(rq);
841 return ST_CONTINUE;
842}
843
844static void
845ractor_sync_mark(rb_ractor_t *r)
846{
847 /* The owner rewrites the queues, the port table and the monitor list under its sync
848 * lock, so only the owner itself or the stopped world may walk them. */
849 const bool world_stopped = rb_gc_during_global_gc_p();
850 VM_ASSERT(world_stopped || r == rb_current_ractor_raw(false));
851
852 rb_gc_mark(r->sync.default_port_value);
853
854 /* Until the value is absorbed this is its only reliable root (Qundef while the
855 * Ractor still runs); after Ractor#value returns it, the Ruby side roots it. */
856 rb_gc_mark(r->sync.legacy);
857
858 /* ractor_sync_init builds the rest, and a root scan reaches the main Ractor before
859 * that: ports is what tells the two apart (the lock and the list heads are still
860 * zeroed, and walking those crashes). Lock out foreign senders while walking them
861 * (self-lock: not recursive, and a held Ractor lock disables malloc-GC, so no GC
862 * nests); a stopped world needs no lock. */
863 if (r->sync.ports) {
864 if (!world_stopped) RACTOR_LOCK_SELF(r);
865 {
866 ractor_queue_mark(r->sync.recv_queue);
867 st_foreach(r->sync.ports, ractor_mark_ports_i, 0);
868 ractor_mark_monitors(r);
869 }
870 if (!world_stopped) RACTOR_UNLOCK_SELF(r);
871
872 /* The baskets on no queue: one being built to send, one being materialized.
873 * Walked in every collection, like the queues. What they hold is shareable, but
874 * "only a global GC frees a shareable" does not hold: pinned_roots_mark, which
875 * roots a shareable from its page bit, is skipped once the process is back to a
876 * single Ractor (rb_gc_single_objspace_p), and then an ordinary local GC frees
877 * one that nothing else names. A payload in flight is named by its basket and
878 * nothing else, so this list has to be a root whenever the queues are. No sync
879 * lock, though: only the owner touches it (the lock above guards the queues,
880 * which a foreign sender writes). */
881 ractor_mark_off_queue_baskets(r);
882 }
883}
884
885static int
886ractor_sync_free_ports_i(st_data_t _key, st_data_t val, st_data_t _args)
887{
888 struct ractor_queue *queue = (struct ractor_queue *)val;
889
890 ractor_queue_free(queue);
891
892 return ST_CONTINUE;
893}
894
895static void
896ractor_sync_free(rb_ractor_t *r)
897{
898 if (r->sync.recv_queue) {
899 ractor_queue_free(r->sync.recv_queue);
900 }
901
902 // maybe NULL
903 if (r->sync.ports) {
904 st_foreach(r->sync.ports, ractor_sync_free_ports_i, 0);
905 st_free_table(r->sync.ports);
906 r->sync.ports = NULL;
907 }
908}
909
910static size_t
911ractor_sync_memsize(const rb_ractor_t *r)
912{
913 if (r->sync.ports) {
914 return st_memsize(r->sync.ports);
915 }
916 else {
917 return 0;
918 }
919}
920
921static void
922ractor_sync_init(rb_ractor_t *r)
923{
924 // lock
925 rb_native_mutex_initialize(&r->sync.lock);
926
927 // monitors
928 ccan_list_head_init(&r->sync.off_queue_baskets);
929 ccan_list_head_init(&r->sync.monitors);
930
931 // waiters
932 ccan_list_head_init(&r->sync.waiters);
933
934 // receiving queue
935 r->sync.recv_queue = ractor_queue_new();
936
937 // ports
938 r->sync.ports = st_init_numtable();
939 /* ractor_setup_default_port creates it only after the Ractor joins
940 * vm->ractor.set, so a global GC cannot free the rootless port in between. */
941 r->sync.default_port_value = Qfalse;
942
943 // legacy
944 r->sync.legacy = Qundef;
945
946 // no receive is rebuilding a payload yet
947
948}
949
950/* Create the default port. Call only after the Ractor joined vm->ractor.set, so the
951 * root scan can mark the shareable port from creation onwards. */
952void
953rb_ractor_setup_default_port(rb_ractor_t *r)
954{
955 VM_ASSERT(r->sync.default_port_value == Qfalse);
956 r->sync.default_port_value = ractor_port_new(r);
957 FL_SET_RAW(r->sync.default_port_value, RUBY_FL_SHAREABLE); // only default ports are shareable
958 rb_gc_obj_became_shareable(r->sync.default_port_value);
959}
960
961// Ractor#value
962
963static rb_ractor_t *
964ractor_set_successor_once(rb_ractor_t *r, rb_ractor_t *cr)
965{
966 if (r->sync.successor == NULL) {
967 rb_ractor_t *successor = ATOMIC_PTR_CAS(r->sync.successor, NULL, cr);
968 return successor == NULL ? cr : successor;
969 }
970
971 return r->sync.successor;
972}
973
974static VALUE
975ractor_make_remote_exception(VALUE cause, VALUE sender)
976{
977 VALUE err = rb_exc_new_cstr(rb_eRactorRemoteError, "thrown by remote Ractor.");
978 rb_ivar_set(err, rb_intern("@ractor"), sender);
979 rb_ec_setup_exception(NULL, err, cause);
980 return err;
981}
982
983static VALUE
984ractor_value(rb_execution_context_t *ec, VALUE self)
985{
986 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
987 rb_ractor_t *r = RACTOR_PTR(self);
988 rb_ractor_t *sr = ractor_set_successor_once(r, cr);
989
990 if (sr == cr) {
991 if (r->sync.legacy_taken) {
992 rb_raise(rb_eRactorError, "The value was already taken");
993 }
994
995 /* The value is returned by reference: inherit the dead Ractor's objspace first,
996 * making it our own object (containment without a copy). Wait for
997 * ractor_terminated: a monitor-port wakeup arrives before the dying thread
998 * finishes teardown (vm_remove_ractor still touches the objspace). */
999 while (!rb_ractor_status_p(r, ractor_terminated)) {
1001 }
1002
1003 /* The wait above yields the GVL, so another thread of this Ractor can take the
1004 * value first: re-check. */
1005 if (r->sync.legacy_taken) {
1006 rb_raise(rb_eRactorError, "The value was already taken");
1007 }
1008
1009 /* Move r's rb_gc_register_mark_object pins to the joiner before the merge
1010 * below sweeps r's objspace, or the objects pinned there lose their root. */
1011 rb_ractor_absorb_registered_marks(GET_RACTOR(), r);
1012
1013 rb_gc_objspace_absorb_into_current(&r->objspace);
1014
1015 /* Keep legacy alive in a C local until it is returned: after the absorb only
1016 * the C struct reaches it, so let the conservative machine-stack mark find it. */
1017 volatile VALUE legacy_keep = r->sync.legacy;
1018
1019 /* A dead Ractor's local storage is unreachable from Ruby (Ractor#[] only works
1020 * from inside), so let the values die and keep ractor_mark and ractor_free from
1021 * walking a stale table later. */
1022 ractor_local_storage_free(r);
1023 r->local_storage = NULL;
1024 r->idkey_local_storage = NULL;
1025
1026 /* The value is returned to the caller and rooted from Ruby afterwards. Drop it
1027 * from the C struct: keeping it would leave a C-only reference into the
1028 * successor's objspace, needing marking and a pin against compaction. */
1029 VALUE legacy = r->sync.legacy;
1030 r->sync.legacy = Qnil;
1031 r->sync.legacy_taken = true;
1032 RB_GC_GUARD(legacy_keep);
1033
1034 if (r->sync.legacy_exc) {
1035 rb_exc_raise(ractor_make_remote_exception(legacy, self));
1036 }
1037 return legacy;
1038 }
1039 else {
1040 rb_raise(rb_eRactorError, "Only the successor ractor can take a value");
1041 }
1042}
1043
1044static VALUE ractor_copy_native_try(VALUE obj); // in ractor.c
1045
1046static VALUE
1047ractor_marshal_dump_body(VALUE obj)
1048{
1049 return rb_marshal_dump(obj, Qnil);
1050}
1051
1052static VALUE
1053ractor_marshal_dump_rescue(VALUE obj, VALUE errinfo)
1054{
1055 rb_raise(rb_eRactorError, "can not copy %"PRIsVALUE" object.", rb_class_of(obj));
1057}
1058
1059static VALUE
1060ractor_prepare_payload(rb_execution_context_t *ec, VALUE obj, enum ractor_basket_type *ptype, bool *pmarshaled,
1061 struct rb_ractor_courier **pcourier)
1062{
1063 switch (*ptype) {
1064 case basket_type_ref:
1065 return obj;
1066 default:
1067 if (rb_ractor_shareable_p(obj)) {
1068 *ptype = basket_type_ref;
1069 return obj;
1070 }
1071 else {
1072 /* Snapshot the object on the sender side without calling the user-visible
1073 * #clone. Both forms are off-heap, so an in-flight payload is never a GC
1074 * object and needs no pin: nothing of the sender's heap stays alive while
1075 * the message waits (design_v2.md 4.5). The courier carries the core
1076 * types; anything else is marshaled here, so its user hooks run on the
1077 * sender, and the dump travels as plain bytes. */
1078 *ptype = basket_type_copy;
1079 if (rb_ractor_courier_build_copy(obj, pcourier) != NULL) return Qundef;
1080
1081 *pmarshaled = true;
1082 return rb_rescue2(ractor_marshal_dump_body, obj,
1083 ractor_marshal_dump_rescue, obj,
1084 rb_eTypeError, (VALUE)0);
1085 }
1086 }
1087}
1088
1089#if RBIMPL_COMPILER_IS(GCC) && defined(__OPTIMIZE__)
1090/* GCC produces false-positive -Wclobbered warnings after inlining
1091 * this function into ractor_basket_new(). */
1092NOINLINE(static void ractor_basket_build_payload(rb_execution_context_t *ec, struct ractor_basket *b, VALUE obj, enum ractor_basket_type type, bool exc));
1093#endif
1094static void
1095ractor_basket_build_payload(rb_execution_context_t *ec, struct ractor_basket *b, VALUE obj,
1096 enum ractor_basket_type type, bool exc)
1097{
1098 b->p.exception = exc;
1099 if (type == basket_type_move) {
1100 /* Serialize the graph into an off-heap courier; the sources become
1101 * RactorMovedObject. While in flight there is no GC object left for the
1102 * sender's GC to mark, sweep or move. The build publishes the courier into
1103 * the basket as soon as it exists. */
1104 rb_ractor_courier_build_move(obj, &b->p.courier);
1105 b->type = type;
1106 b->p.v = Qfalse;
1107 }
1108 else {
1109 bool marshaled = false;
1110 VALUE v = ractor_prepare_payload(ec, obj, &type, &marshaled, &b->p.courier);
1111
1112 if (type == basket_type_copy && marshaled) {
1113 /* Take the dump off-heap: the sender's copy of it is ordinary garbage
1114 * from here, so nothing of its heap is held while the message waits. */
1115 size_t mlen = (size_t)RSTRING_LEN(v);
1116 char *mbuf = ALLOC_N(char, mlen > 0 ? mlen : 1);
1117 b->p.marshaled = marshaled;
1118 b->p.mbuf = mbuf;
1119 b->p.mlen = mlen;
1120 memcpy(mbuf, RSTRING_PTR(v), mlen);
1121 v = Qundef;
1122 }
1123 b->type = type;
1124 b->p.v = v;
1125 }
1126}
1127
1128static struct ractor_basket *
1129ractor_basket_new(rb_execution_context_t *ec, VALUE obj, enum ractor_basket_type type, bool exc)
1130{
1131 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
1132 /* Allocate and list the basket before anything is built into it: from here the
1133 * courier it is about to hold is rooted by this Ractor's in-flight list, even
1134 * half-built, and every raise below frees it through one path. */
1135 struct ractor_basket *b = ractor_basket_alloc();
1136 ractor_off_queue_add(cr, b);
1137
1138 enum ruby_tag_type state;
1139 EC_PUSH_TAG(ec);
1140 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
1141 ractor_basket_build_payload(ec, b, obj, type, exc);
1142 }
1143 EC_POP_TAG();
1144 if (state != TAG_NONE) {
1145 ractor_basket_free(b); /* leaves the list and frees a courier already built */
1146 EC_JUMP_TAG(ec, state);
1147 }
1148
1149 return b;
1150}
1151
1152static VALUE
1153ractor_basket_value(struct ractor_basket *b)
1154{
1155 switch (b->type) {
1156 case basket_type_ref:
1157 break;
1158 case basket_type_copy: {
1159 /* An off-heap copy courier rebuilds exactly like a move one; only the sources
1160 * differ (still alive here, already shells there). */
1161 if (b->p.courier != NULL) goto materialize_courier;
1162 /* The payload is the marshaled bytes. Marshal.load allocates through this
1163 * Ractor's normal newobj and write-barrier paths, and can raise (load hooks and
1164 * autoload run user code, an async interrupt can arrive anywhere), so it runs
1165 * under a TAG. */
1166 rb_execution_context_t *ec = rb_current_ec_noinline();
1167 VALUE result = Qundef;
1168 enum ruby_tag_type state;
1169 EC_PUSH_TAG(ec);
1170 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
1171 /* Rebuild the byte string in this Ractor's objspace. Marshal does not mark
1172 * its source (mark_load_arg) and the basket is off the queue, so this
1173 * frame's stack slot is the String's only root for the load. */
1174 VALUE bin = rb_str_new(b->p.mbuf, (long)b->p.mlen);
1175 result = rb_marshal_load(bin);
1176 RB_GC_GUARD(bin);
1177 }
1178 EC_POP_TAG();
1179 /* rb_copy_generic_ivar left the sender-resident snapshot host and fields_obj in
1180 * this EC's gen_fields_cache; the snapshot is garbage on the sender now, and a
1181 * stale cache hit on a reused address would deref a freed foreign fields_obj.
1182 * Invalidate (the raise path resets it the same way). */
1183 ec->gen_fields_cache.obj = Qundef;
1184 ec->gen_fields_cache.fields_obj = Qundef;
1185 if (state != TAG_NONE) {
1186 /* The basket left the queue and has no other owner, and a raise skips
1187 * accept, so free it here before propagating. */
1188 ractor_basket_free(b);
1189 EC_JUMP_TAG(ec, state);
1190 }
1191 /* keep rooting result from the stack after the frame is popped */
1192 b->p.v = result;
1193 RB_GC_GUARD(result);
1194 break;
1195 }
1196 case basket_type_move:
1197 materialize_courier: {
1198 /* Rebuild the moved graph from the off-heap courier into this Ractor's
1199 * objspace. The sources are already RactorMovedObject (set when the courier
1200 * was built), so move's snapshot semantics hold. The courier is xmalloc'd
1201 * rather than a GC object, so the sender's concurrent local GC never touches
1202 * it; the shareable VALUEs it carries are marked through this basket, which is
1203 * on this Ractor's off-queue list until it is freed.
1204 *
1205 * Rebuilding can raise here too (rb_hash_aset on a moved key with a custom
1206 * #hash runs user code, and an async interrupt can arrive). On a raise the
1207 * courier is still owned by the basket, whose teardown frees it. */
1208 rb_execution_context_t *ec = rb_current_ec_noinline();
1209 struct rb_ractor_courier *courier = b->p.courier;
1210 /* Keep the materialized graph on the machine stack (result): it is the only
1211 * root until it reaches the caller. courier_free below runs a long loop, and
1212 * only the malloc'd basket's p.v holding it would give a concurrent global GC a
1213 * wide window. */
1214 VALUE result = Qundef;
1215 enum ruby_tag_type state;
1216 EC_PUSH_TAG(ec);
1217 if ((state = EC_EXEC_TAG()) == TAG_NONE) {
1218 result = rb_ractor_courier_materialize(courier);
1219 }
1220 EC_POP_TAG();
1221 if (state != TAG_NONE) {
1222 /* An unconsumed courier stays in b->p.courier; basket_free frees it. */
1223 ractor_basket_free(b);
1224 EC_JUMP_TAG(ec, state);
1225 }
1226 rb_ractor_courier_free(courier);
1227 b->p.courier = NULL;
1228 b->p.v = result;
1229 RB_GC_GUARD(result);
1230 break;
1231 }
1232 default:
1233 VM_ASSERT(0); // unreachable
1234 }
1235
1236 VM_ASSERT(!RB_TYPE_P(b->p.v, T_NONE));
1237 return b->p.v;
1238}
1239
1240static VALUE
1241ractor_basket_accept(struct ractor_basket *b)
1242{
1243 VALUE v = ractor_basket_value(b);
1244
1245 if (b->p.exception) {
1246 VALUE err = ractor_make_remote_exception(v, b->sender);
1247 ractor_basket_free(b);
1248 rb_exc_raise(err);
1249 }
1250
1251 ractor_basket_free(b);
1252 return v;
1253}
1254
1255// Ractor blocking by receive
1256
1257#if VM_CHECK_MODE > 0
1258static bool
1259ractor_waiter_included(rb_ractor_t *cr, rb_thread_t *th)
1260{
1261 ASSERT_ractor_locking(cr);
1262
1263 struct ractor_waiter *w;
1264
1265 ccan_list_for_each(&cr->sync.waiters, w, node) {
1266 if (w->th == th) {
1267 return true;
1268 }
1269 }
1270
1271 return false;
1272}
1273#endif
1274
1275#if USE_RUBY_DEBUG_LOG
1276
1277static const char *
1278wakeup_status_str(enum ractor_wakeup_status wakeup_status)
1279{
1280 switch (wakeup_status) {
1281 case wakeup_none: return "none";
1282 case wakeup_by_send: return "by_send";
1283 case wakeup_by_interrupt: return "by_interrupt";
1284 // case wakeup_by_close: return "by_close";
1285 }
1286 rb_bug("unreachable");
1287}
1288
1289static const char *
1290basket_type_name(enum ractor_basket_type type)
1291{
1292 switch (type) {
1293 case basket_type_none: return "none";
1294 case basket_type_ref: return "ref";
1295 case basket_type_copy: return "copy";
1296 case basket_type_move: return "move";
1297 }
1298 VM_ASSERT(0);
1299 return NULL;
1300}
1301
1302#endif // USE_RUBY_DEBUG_LOG
1303
1304static bool
1305ractor_wakeup_all(rb_ractor_t *r, enum ractor_wakeup_status wakeup_status)
1306{
1307 ASSERT_ractor_unlocking(r);
1308
1309 RUBY_DEBUG_LOG("r:%u wakeup:%s", rb_ractor_id(r), wakeup_status_str(wakeup_status));
1310
1311 bool wakeup_p = false;
1312
1313 RACTOR_LOCK(r);
1314 while (1) {
1315 struct ractor_waiter *waiter = ccan_list_pop(&r->sync.waiters, struct ractor_waiter, node);
1316
1317 if (waiter) {
1318 VM_ASSERT(waiter->wakeup_status == wakeup_none);
1319
1320 waiter->wakeup_status = wakeup_status;
1321 rb_ractor_sched_wakeup(r, waiter->th);
1322
1323 wakeup_p = true;
1324 }
1325 else {
1326 break;
1327 }
1328 }
1329 RACTOR_UNLOCK(r);
1330
1331 return wakeup_p;
1332}
1333
1334static void
1335ubf_ractor_wait(void *ptr)
1336{
1337 struct ractor_waiter *waiter = (struct ractor_waiter *)ptr;
1338
1339 rb_thread_t *th = waiter->th;
1340 rb_ractor_t *r = th->ractor;
1341 rb_atomic_t event_serial = waiter->event_serial;
1342
1343 // clear ubf and nobody can kick UBF
1344 th->unblock.func = NULL;
1345 th->unblock.arg = NULL;
1346
1347 rb_native_mutex_unlock(&th->interrupt_lock);
1348 {
1349 RACTOR_LOCK(r);
1350 {
1351 if (RUBY_ATOMIC_LOAD(th->unblock.event_serial) == event_serial && waiter->wakeup_status == wakeup_none) {
1352 RUBY_DEBUG_LOG("waiter:%p", (void *)waiter);
1353
1354 waiter->wakeup_status = wakeup_by_interrupt;
1355 ccan_list_del(&waiter->node);
1356
1357 rb_ractor_sched_wakeup(r, waiter->th);
1358 }
1359 }
1360 RACTOR_UNLOCK(r);
1361 }
1362 rb_native_mutex_lock(&th->interrupt_lock);
1363}
1364
1365// Waits for an event on cr. `end` is an absolute deadline, NULL to wait forever.
1366static enum ractor_wakeup_status
1367ractor_wait(rb_execution_context_t *ec, rb_ractor_t *cr, const rb_hrtime_t *end)
1368{
1369 rb_thread_t *th = rb_ec_thread_ptr(ec);
1370
1371 struct ractor_waiter waiter = {
1372 .wakeup_status = wakeup_none,
1373 .th = th,
1374 .end = end,
1375 };
1376
1377 RUBY_DEBUG_LOG("wait%s", "");
1378
1379 ASSERT_ractor_locking(cr);
1380
1381 VM_ASSERT(GET_RACTOR() == cr);
1382 VM_ASSERT(!ractor_waiter_included(cr, th));
1383
1384 ccan_list_add_tail(&cr->sync.waiters, &waiter.node);
1385
1386 // resume another ready thread and wait for an event
1387 rb_ractor_sched_wait(ec, cr, ubf_ractor_wait, &waiter);
1388
1389 if (waiter.wakeup_status == wakeup_none) {
1390 ccan_list_del(&waiter.node);
1391 }
1392
1393 RUBY_DEBUG_LOG("wakeup_status:%s", wakeup_status_str(waiter.wakeup_status));
1394
1395 RACTOR_UNLOCK_SELF(cr);
1396 {
1397 rb_ec_check_ints(ec);
1398 }
1399 RACTOR_LOCK_SELF(cr);
1400
1401 VM_ASSERT(!ractor_waiter_included(cr, th));
1402 return waiter.wakeup_status;
1403}
1404
1405static void
1406ractor_deliver_incoming_messages(rb_execution_context_t *ec, rb_ractor_t *cr)
1407{
1408 ASSERT_ractor_locking(cr);
1409 struct ractor_queue *recv_q = cr->sync.recv_queue;
1410
1411 struct ractor_basket *b;
1412 while ((b = ractor_queue_deq(cr, recv_q)) != NULL) {
1413 ractor_queue_enq(cr, ractor_get_queue(cr, b->port_id, true), b);
1414 }
1415}
1416
1417static bool
1418ractor_check_received(rb_ractor_t *cr, struct ractor_queue *messages)
1419{
1420 struct ractor_queue *received_queue = cr->sync.recv_queue;
1421 bool received = false;
1422
1423 ASSERT_ractor_locking(cr);
1424
1425 if (ractor_queue_empty_p(cr, received_queue)) {
1426 RUBY_DEBUG_LOG("empty");
1427 }
1428 else {
1429 received = true;
1430
1431 // messages <- incoming
1432 ractor_queue_init(messages);
1433 ractor_queue_move(messages, received_queue);
1434 }
1435
1436 VM_ASSERT(ractor_queue_empty_p(cr, received_queue));
1437
1438 RUBY_DEBUG_LOG("received:%d", received);
1439 return received;
1440}
1441
1442// Returns false if the deadline `end` passed with nothing to deliver. Incoming
1443// messages are delivered even then, so the caller retries its queue once more.
1444static bool
1445ractor_wait_receive(rb_execution_context_t *ec, rb_ractor_t *cr, const rb_hrtime_t *end)
1446{
1447 struct ractor_queue messages;
1448 bool deliverred = false;
1449 bool timedout = false;
1450
1451 RACTOR_LOCK_SELF(cr);
1452 {
1453 if (ractor_check_received(cr, &messages)) {
1454 deliverred = true;
1455 }
1456 else if (!end) {
1457 ractor_wait(ec, cr, NULL); // no timeout: wait until a message arrives
1458 }
1459 else if (*end == 0) {
1460 timedout = true; // `timeout: 0`: over without reading any clock
1461 }
1462 else {
1463 // only a wakeup nobody claimed can be the deadline, so only then look at
1464 // the clock: a send or an interrupt says what woke this thread by itself
1465 timedout = ractor_wait(ec, cr, end) == wakeup_none && rb_hrtime_now() >= *end;
1466 }
1467 }
1468 RACTOR_UNLOCK_SELF(cr);
1469
1470 if (deliverred) {
1471 VM_ASSERT(!ractor_queue_empty_p(cr, &messages));
1472 struct ractor_basket *b;
1473
1474 while ((b = ractor_queue_deq(cr, &messages)) != NULL) {
1475 ractor_queue_enq(cr, ractor_get_queue(cr, b->port_id, false), b);
1476 }
1477 }
1478
1479 return !timedout;
1480}
1481
1482static VALUE
1483ractor_try_receive(rb_execution_context_t *ec, rb_ractor_t *cr, const struct ractor_port *rp)
1484{
1485 struct ractor_queue *rq = ractor_get_queue(cr, ractor_port_id(rp), false);
1486
1487 if (rq == NULL) {
1488 rb_raise(rb_eRactorClosedError, "The port was already closed");
1489 }
1490
1491 struct ractor_basket *b = ractor_queue_deq(cr, rq);
1492 /* Off the queue and not yet freed: this Ractor roots it while it materializes. */
1493 if (b) ractor_off_queue_add(cr, b);
1494
1495 if (rq->closed && ractor_queue_empty_p(cr, rq)) {
1496 ractor_delete_port(cr, ractor_port_id(rp), false);
1497 }
1498
1499 if (b) {
1500 return ractor_basket_accept(b);
1501 }
1502 else {
1503 return Qundef;
1504 }
1505}
1506
1507// Returns Qundef if the deadline passed first. It bounds how long this blocks; it
1508// does not cut delivery off. A message that lands while the timeout is being
1509// reported is still returned, as Thread::Queue#pop(timeout:) does. Either way
1510// nothing is lost: a basket only leaves the queue when it is returned.
1511static VALUE
1512ractor_receive(rb_execution_context_t *ec, const struct ractor_port *rp, const rb_hrtime_t *end)
1513{
1514 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
1515 VM_ASSERT(cr == rp->r);
1516
1517 RUBY_DEBUG_LOG("port:%u", (unsigned int)ractor_port_id(rp));
1518
1519 while (1) {
1520 VALUE v = ractor_try_receive(ec, cr, rp);
1521
1522 if (v != Qundef) {
1523 return v;
1524 }
1525 else if (!ractor_wait_receive(ec, cr, end)) {
1526 return Qundef;
1527 }
1528 }
1529}
1530
1531// A timeout argument becomes an absolute deadline, or 0 for `timeout: 0`, which
1532// every wait reads as "do not wait". Returns NULL when there is no timeout.
1533static const rb_hrtime_t *
1534ractor_timeout_deadline(VALUE timeout, rb_hrtime_t *storage)
1535{
1536 if (NIL_P(timeout)) return NULL;
1537
1538 if (!(FIXNUM_P(timeout) && FIX2LONG(timeout) == 0)) {
1539 struct timeval tv = rb_time_interval(timeout); // raises on a negative timeout
1540 rb_hrtime_t rel = rb_timeval2hrtime(&tv);
1541
1542 if (rel > 0) {
1543 *storage = rb_hrtime_add(rb_hrtime_now(), rel);
1544 return storage;
1545 }
1546 }
1547
1548 *storage = 0;
1549 return storage;
1550}
1551
1552// Ractor#send
1553
1554static void
1555ractor_send_basket(rb_execution_context_t *ec, const struct ractor_port *rp, struct ractor_basket *b, bool raise_on_error)
1556{
1557 bool closed = false;
1558
1559 RUBY_DEBUG_LOG("port:%u@r%u b:%s v:%p", (unsigned int)ractor_port_id(rp), rb_ractor_id(rp->r), basket_type_name(b->type), (void *)b->p.v);
1560
1561 RACTOR_LOCK(rp->r);
1562 {
1563 if (ractor_closed_port_p(ec, rp->r, rp)) {
1564 closed = true;
1565 }
1566 else {
1567 b->port_id = ractor_port_id(rp);
1568 /* The receiver's queue roots it from here; drop it from ours. */
1569 ractor_off_queue_remove(b);
1570 ractor_queue_enq(rp->r, rp->r->sync.recv_queue, b);
1571 }
1572 }
1573 RACTOR_UNLOCK(rp->r);
1574
1575 // NOTE: ref r -> b->p.v is created, but Ractor is unprotected object, so no problem on that.
1576
1577 if (!closed) {
1578 ractor_wakeup_all(rp->r, wakeup_by_send);
1579 }
1580 else {
1581 RUBY_DEBUG_LOG("closed:%u@r%u", (unsigned int)ractor_port_id(rp), rb_ractor_id(rp->r));
1582
1583 /* Nothing took the basket: it was not enqueued, so free it whether or not the
1584 * caller wants the error raised. */
1585 ractor_basket_free(b);
1586
1587 if (raise_on_error) {
1588 rb_raise(rb_eRactorClosedError, "The port was already closed");
1589 }
1590 }
1591}
1592
1593/* A shareable payload needs no preparation, so this skips the tag ractor_basket_new
1594 * pushes. The exit tokens travel this way: they are sent from a thread whose EC has
1595 * already lost its VM stack, and EC_PUSH_TAG reads ec->cfp under ZJIT. */
1596static struct ractor_basket *
1597ractor_basket_new_ref(VALUE shareable)
1598{
1599 struct ractor_basket *b = ractor_basket_alloc();
1600
1601 b->type = basket_type_ref;
1602 b->sender = Qnil;
1603 b->p.v = shareable;
1604 b->p.exception = false;
1605 b->p.marshaled = false;
1606 b->p.courier = NULL;
1607 b->p.mbuf = NULL;
1608 b->p.mlen = 0;
1609
1610 return b;
1611}
1612
1613static VALUE
1614ractor_send0(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move, bool raise_on_error)
1615{
1616 struct ractor_basket *b = ractor_basket_new(ec, obj, RTEST(move) ? basket_type_move : basket_type_none, false);
1617 ractor_send_basket(ec, rp, b, raise_on_error);
1618 RB_GC_GUARD(obj);
1619 return rp->r->pub.self;
1620}
1621
1622static VALUE
1623ractor_send(rb_execution_context_t *ec, const struct ractor_port *rp, VALUE obj, VALUE move)
1624{
1625 return ractor_send0(ec, rp, obj, move, true);
1626}
1627
1628// Ractor::Selector
1629
1631 struct st_table *ports; // rpv -> rp
1632
1633};
1634
1635static int
1636ractor_selector_mark_i(st_data_t key, st_data_t val, st_data_t dmy)
1637{
1638 rb_gc_mark((VALUE)key); // rpv
1639
1640 return ST_CONTINUE;
1641}
1642
1643static void
1644ractor_selector_mark(void *ptr)
1645{
1646 struct ractor_selector *s = ptr;
1647
1648 if (s->ports) {
1649 st_foreach(s->ports, ractor_selector_mark_i, 0);
1650 }
1651}
1652
1653static void
1654ractor_selector_free(void *ptr)
1655{
1656 struct ractor_selector *s = ptr;
1657 st_free_table(s->ports);
1658 SIZED_FREE(s);
1659}
1660
1661static size_t
1662ractor_selector_memsize(const void *ptr)
1663{
1664 const struct ractor_selector *s = ptr;
1665 size_t size = sizeof(struct ractor_selector);
1666 if (s->ports) {
1667 size += st_memsize(s->ports);
1668 }
1669 return size;
1670}
1671
1672static const rb_data_type_t ractor_selector_data_type = {
1673 "ractor/selector",
1674 {
1675 ractor_selector_mark,
1676 ractor_selector_free,
1677 ractor_selector_memsize,
1678 NULL, // update
1679 },
1680 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED,
1681};
1682
1683static struct ractor_selector *
1684RACTOR_SELECTOR_PTR(VALUE selv)
1685{
1686 VM_ASSERT(rb_typeddata_is_kind_of(selv, &ractor_selector_data_type));
1687 return (struct ractor_selector *)DATA_PTR(selv);
1688}
1689
1690// Ractor::Selector.new
1691
1692static VALUE
1693ractor_selector_create(VALUE klass)
1694{
1695 struct ractor_selector *s;
1696 VALUE selv = TypedData_Make_Struct(klass, struct ractor_selector, &ractor_selector_data_type, s);
1697 s->ports = st_init_numtable(); // TODO
1698 return selv;
1699}
1700
1701// Ractor::Selector#add(r)
1702
1703/*
1704 * call-seq:
1705 * add(ractor) -> ractor
1706 *
1707 * Adds _ractor_ to +self+. Raises an exception if _ractor_ is already added.
1708 * Returns _ractor_.
1709 */
1710static VALUE
1711ractor_selector_add(VALUE selv, VALUE rpv)
1712{
1713 if (!ractor_port_p(rpv)) {
1714 rb_raise(rb_eArgError, "Not a Ractor::Port object");
1715 }
1716
1717 struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1718 const struct ractor_port *rp = ractor_port_ptr_check(rpv);
1719
1720 if (st_lookup(s->ports, (st_data_t)rpv, NULL)) {
1721 rb_raise(rb_eArgError, "already added");
1722 }
1723
1724 st_insert(s->ports, (st_data_t)rpv, (st_data_t)rp);
1725 RB_OBJ_WRITTEN(selv, Qundef, rpv);
1726
1727 return selv;
1728}
1729
1730// Ractor::Selector#remove(r)
1731
1732/* call-seq:
1733 * remove(ractor) -> ractor
1734 *
1735 * Removes _ractor_ from +self+. Raises an exception if _ractor_ is not added.
1736 * Returns the removed _ractor_.
1737 */
1738static VALUE
1739ractor_selector_remove(VALUE selv, VALUE rpv)
1740{
1741 if (!ractor_port_p(rpv)) {
1742 rb_raise(rb_eArgError, "Not a Ractor::Port object");
1743 }
1744
1745 struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1746
1747 if (!st_lookup(s->ports, (st_data_t)rpv, NULL)) {
1748 rb_raise(rb_eArgError, "not added yet");
1749 }
1750
1751 st_delete(s->ports, (st_data_t *)&rpv, NULL);
1752
1753 return selv;
1754}
1755
1756// Ractor::Selector#clear
1757
1758/*
1759 * call-seq:
1760 * clear -> self
1761 *
1762 * Removes all ractors from +self+. Raises +self+.
1763 */
1764static VALUE
1765ractor_selector_clear(VALUE selv)
1766{
1767 struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1768 st_clear(s->ports);
1769 return selv;
1770}
1771
1772/*
1773 * call-seq:
1774 * empty? -> true or false
1775 *
1776 * Returns +true+ if no ractor is added.
1777 */
1778static VALUE
1779ractor_selector_empty_p(VALUE selv)
1780{
1781 struct ractor_selector *s = RACTOR_SELECTOR_PTR(selv);
1782 return s->ports->num_entries == 0 ? Qtrue : Qfalse;
1783}
1784
1785// Ractor::Selector#wait
1786
1788 rb_ractor_t *cr;
1790 bool found;
1791 VALUE v;
1792 VALUE rpv;
1793};
1794
1795static int
1796ractor_selector_wait_i(st_data_t key, st_data_t val, st_data_t data)
1797{
1798 struct ractor_selector_wait_data *p = (struct ractor_selector_wait_data *)data;
1799 const struct ractor_port *rp = (const struct ractor_port *)val;
1800
1801 VALUE v = ractor_try_receive(p->ec, p->cr, rp);
1802
1803 if (v != Qundef) {
1804 p->found = true;
1805 p->v = v;
1806 p->rpv = (VALUE)key;
1807 return ST_STOP;
1808 }
1809 else {
1810 return ST_CONTINUE;
1811 }
1812}
1813
1814static VALUE
1815ractor_selector__wait(rb_execution_context_t *ec, VALUE selector, const rb_hrtime_t *end)
1816{
1817 rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
1818 struct ractor_selector *s = RACTOR_SELECTOR_PTR(selector);
1819
1820 struct ractor_selector_wait_data data = {
1821 .ec = ec,
1822 .cr = cr,
1823 .found = false,
1824 };
1825
1826 while (1) {
1827 st_foreach(s->ports, ractor_selector_wait_i, (st_data_t)&data);
1828
1829 if (data.found) {
1830 return rb_ary_new_from_args(2, data.rpv, data.v);
1831 }
1832 else if (!ractor_wait_receive(ec, cr, end)) {
1833 return Qnil;
1834 }
1835 }
1836}
1837
1838/*
1839 * call-seq:
1840 * wait(receive: false, yield_value: undef, move: false) -> [ractor, value]
1841 *
1842 * Waits until any ractor in _selector_ can be active.
1843 */
1844static VALUE
1845ractor_selector_wait(VALUE selector)
1846{
1847 return ractor_selector__wait(GET_EC(), selector, NULL);
1848}
1849
1850static VALUE
1851ractor_selector_new(int argc, VALUE *ractors, VALUE klass)
1852{
1853 VALUE selector = ractor_selector_create(klass);
1854
1855 for (int i=0; i<argc; i++) {
1856 ractor_selector_add(selector, ractors[i]);
1857 }
1858
1859 return selector;
1860}
1861
1862static VALUE
1863ractor_select_internal(rb_execution_context_t *ec, VALUE self, VALUE ports, VALUE timeout)
1864{
1865 rb_hrtime_t deadline;
1866 const rb_hrtime_t *end = ractor_timeout_deadline(timeout, &deadline);
1867
1868 VALUE selector = ractor_selector_new(RARRAY_LENINT(ports), (VALUE *)RARRAY_CONST_PTR(ports), rb_cRactorSelector);
1869 VALUE result = ractor_selector__wait(ec, selector, end);
1870
1871 RB_GC_GUARD(selector);
1872 RB_GC_GUARD(ports);
1873 return result;
1874}
1875
1876#ifndef USE_RACTOR_SELECTOR
1877#define USE_RACTOR_SELECTOR 0
1878#endif
1879
1880RUBY_SYMBOL_EXPORT_BEGIN
1881void rb_init_ractor_selector(void);
1882RUBY_SYMBOL_EXPORT_END
1883
1884/*
1885 * Document-class: Ractor::Selector
1886 * :nodoc: currently
1887 *
1888 * Selects multiple Ractors to be activated.
1889 */
1890void
1891rb_init_ractor_selector(void)
1892{
1893 rb_cRactorSelector = rb_define_class_under(rb_cRactor, "Selector", rb_cObject);
1894 rb_undef_alloc_func(rb_cRactorSelector);
1895
1896 rb_define_singleton_method(rb_cRactorSelector, "new", ractor_selector_new , -1);
1897 rb_define_method(rb_cRactorSelector, "add", ractor_selector_add, 1);
1898 rb_define_method(rb_cRactorSelector, "remove", ractor_selector_remove, 1);
1899 rb_define_method(rb_cRactorSelector, "clear", ractor_selector_clear, 0);
1900 rb_define_method(rb_cRactorSelector, "empty?", ractor_selector_empty_p, 0);
1901 rb_define_method(rb_cRactorSelector, "wait", ractor_selector_wait, 0);
1902}
1903
1904static void
1905Init_RactorPort(void)
1906{
1907 rb_cRactorPort = rb_define_class_under(rb_cRactor, "Port", rb_cObject);
1908 rb_define_alloc_func(rb_cRactorPort, ractor_port_alloc);
1909 rb_define_method(rb_cRactorPort, "initialize", ractor_port_initialize, 0);
1910 rb_define_method(rb_cRactorPort, "initialize_copy", ractor_port_initialize_copy, 1);
1911
1912#if USE_RACTOR_SELECTOR
1913 rb_init_ractor_selector();
1914#endif
1915}
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
Definition atomic.h:69
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
Definition atomic.h:175
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
@ RUBY_FL_SHAREABLE
This flag has something to do with Ractor.
Definition fl_type.h:253
#define ALLOC
Old name of RB_ALLOC.
Definition memory.h:400
#define Qundef
Old name of RUBY_Qundef.
#define ID2SYM
Old name of RB_ID2SYM.
Definition symbol.h:44
#define UNREACHABLE_RETURN
Old name of RBIMPL_UNREACHABLE_RETURN.
Definition assume.h:29
#define T_NONE
Old name of RUBY_T_NONE.
Definition value_type.h:74
#define ALLOC_N
Old name of RB_ALLOC_N.
Definition memory.h:399
#define Qtrue
Old name of RUBY_Qtrue.
#define Qnil
Old name of RUBY_Qnil.
#define Qfalse
Old name of RUBY_Qfalse.
#define FIX2LONG
Old name of RB_FIX2LONG.
Definition long.h:46
#define NIL_P
Old name of RB_NIL_P.
#define FIXNUM_P
Old name of RB_FIXNUM_P.
#define FL_SET_RAW
Old name of RB_FL_SET_RAW.
Definition fl_type.h:126
VALUE rb_rescue2(VALUE(*b_proc)(VALUE), VALUE data1, VALUE(*r_proc)(VALUE, VALUE), VALUE data2,...)
An equivalent of rescue clause.
Definition eval.c:1061
void rb_exc_raise(VALUE mesg)
Raises an exception in the current thread.
Definition eval.c:675
VALUE rb_eTypeError
TypeError exception.
Definition error.c:1431
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_cRactor
Ractor class.
Definition ractor.c:36
static VALUE rb_class_of(VALUE obj)
Object to class mapping function.
Definition globals.h:174
VALUE rb_obj_class(VALUE obj)
Queries the class of an object.
Definition object.c:234
VALUE rb_obj_freeze(VALUE obj)
Just calls rb_obj_freeze_inline() inside.
Definition object.c:1308
#define RB_OBJ_WRITTEN(old, oldv, young)
Identical to RB_OBJ_WRITE(), except it doesn't write any values, but only a WB declaration.
Definition gc.h:468
#define rb_str_new(str, len)
Allocates an instance of rb_cString.
Definition string.h:1499
#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:1671
void rb_thread_schedule(void)
Tries to switch to another thread.
Definition thread.c:1687
struct timeval rb_time_interval(VALUE num)
Creates a "time interval".
Definition time.c:2970
VALUE rb_ivar_set(VALUE obj, ID name, VALUE val)
Identical to rb_iv_set(), except it accepts the name as an ID instead of a C string.
Definition variable.c:2059
void rb_undef_alloc_func(VALUE klass)
Deletes the allocator function of a class.
Definition vm_method.c:1807
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
static bool rb_ractor_shareable_p(VALUE obj)
Queries if multiple Ractors can share the passed object or not.
Definition ractor.h:249
VALUE rb_marshal_dump(VALUE obj, VALUE port)
Serialises the given object and all its referring objects, to write them down to the passed port.
Definition marshal.c:2665
VALUE rb_marshal_load(VALUE port)
Deserialises a previous output of rb_marshal_dump() into a network of objects.
Definition marshal.c:2671
#define RBIMPL_ATTR_MAYBE_UNUSED()
Wraps (or simulates) [[maybe_unused]]
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
Definition memory.h:167
VALUE type(ANYARGS)
ANYARGS-ed function type.
static int RARRAY_LENINT(VALUE ary)
Identical to rb_array_len(), except it differs for the return type.
Definition rarray.h:280
#define RARRAY_CONST_PTR
Just another name of rb_array_const_ptr.
Definition rarray.h:51
#define RUBY_TYPED_DEFAULT_FREE
This is a value you can set to rb_data_type_struct::dfree.
Definition rtypeddata.h:81
#define DATA_PTR(obj)
Convenient casting macro for backward compatibility.
Definition rtypeddata.h:435
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:604
#define RTEST
This is an old name of RB_TEST.
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:238
Definition st.h:79
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_lock.
void rb_native_mutex_initialize(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_initialize.
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
Just another name of rb_nativethread_lock_unlock.
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376