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