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