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