Ractor: widen the Ractor id to 64 bits

A Ractor id must stay unique for the life of the process, but the 32 bit
counter wraps after ~4e9 Ractors and comes back onto ids that are still in
use.  That is not out of reach: a few thousand Ractor.new/s is days, not
years.  The wrapped value is 0 too, which some callers read as "the main
Ractor".

There is no portable 64 bit atomic, so RUBY_ATOMIC_FETCH_ADD gives way to the
VM lock, which Ractor.new acquires a few frames later anyway.  That lock is a
no-op until there is a second Ractor, which is exactly when the GVL alone
already serializes the counter, so ractor_next_id asserts the same condition
vm_insert_ractor0 does.

rb_serial_t, to match ec->ractor_id and bmethod's defined_ractor_id.  Neither
struct grows: rb_ractor_pub had 4 bytes of padding after id.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Koichi SasadaandClaude Opus 5 committed 2026-09-10 16:02:03 +09:00
1 parent aa7bc48bb1
commit acc5cd03d9
8 files changed
+36 -33
Notes: git 2026-09-10 07:03:36 +00:00

No files matched your search

+1 -1
View File
@@ -5800,7 +5800,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU
else if (rb_ractor_p(obj)) {
rb_ractor_t *r = (void *)DATA_PTR(obj);
if (r) {
APPEND_F("r:%d", r->pub.id);
APPEND_F("r:%"PRI_SERIALT_PREFIX"u", r->pub.id);
}
}
break;
+21 -18
View File
@@ -79,7 +79,7 @@ ASSERT_ractor_locking(rb_ractor_t *r)
static void
ractor_lock(rb_ractor_t *r, const char *file, int line)
{
RUBY_DEBUG_LOG2(file, line, "locking r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
RUBY_DEBUG_LOG2(file, line, "locking r:%"PRI_SERIALT_PREFIX"u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
ASSERT_ractor_unlocking(r);
rb_native_mutex_lock(&r->sync.lock);
@@ -98,7 +98,7 @@ ractor_lock(rb_ractor_t *r, const char *file, int line)
}
#endif
RUBY_DEBUG_LOG2(file, line, "locked r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
RUBY_DEBUG_LOG2(file, line, "locked r:%"PRI_SERIALT_PREFIX"u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
}
static void
@@ -128,7 +128,7 @@ ractor_unlock(rb_ractor_t *r, const char *file, int line)
rb_native_mutex_unlock(&r->sync.lock);
RUBY_DEBUG_LOG2(file, line, "r:%u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
RUBY_DEBUG_LOG2(file, line, "r:%"PRI_SERIALT_PREFIX"u%s", r->pub.id, rb_current_ractor_raw(false) == r ? " (self)" : "");
}
static void
@@ -175,7 +175,7 @@ ractor_status_str(enum ractor_status status)
static void
ractor_status_set(rb_ractor_t *r, enum ractor_status status)
{
RUBY_DEBUG_LOG("r:%u [%s]->[%s]", r->pub.id, ractor_status_str(r->status_), ractor_status_str(status));
RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u [%s]->[%s]", r->pub.id, ractor_status_str(r->status_), ractor_status_str(status));
// check 1
if (r->status_ != ractor_created) {
@@ -424,7 +424,7 @@ static void
ractor_free(void *ptr)
{
rb_ractor_t *r = (rb_ractor_t *)ptr;
RUBY_DEBUG_LOG("free r:%d", rb_ractor_id(r));
RUBY_DEBUG_LOG("free r:%"PRI_SERIALT_PREFIX"u", rb_ractor_id(r));
free_targeted_hooks(&r->pub.targeted_hooks);
rb_thread_sched_destroy(&r->threads.sched);
@@ -520,26 +520,27 @@ RACTOR_PTR(VALUE self)
}
#define MAIN_RACTOR_ID 1
static rb_atomic_t ractor_last_id = MAIN_RACTOR_ID;
static rb_serial_t ractor_last_id = MAIN_RACTOR_ID;
#include "ractor_sync.c"
// creation/termination
static uint32_t
/* Ids are never reused, so they must not wrap either: 64 bits, which rules out an
* atomic (there is no portable 64-bit one). Serialized by the VM lock, or by the
* GVL before there is a second Ractor to take it against -- the same condition
* vm_insert_ractor0 asserts. */
static rb_serial_t
ractor_next_id(void)
{
uint32_t id;
id = (uint32_t)(RUBY_ATOMIC_FETCH_ADD(ractor_last_id, 1) + 1);
return id;
VM_ASSERT(RB_VM_LOCKED_P() || !rb_multi_ractor_p());
return ++ractor_last_id;
}
static void
vm_insert_ractor0(rb_vm_t *vm, rb_ractor_t *r, bool single_ractor_mode)
{
RUBY_DEBUG_LOG("r:%u ractor.cnt:%u++", r->pub.id, vm->ractor.cnt);
RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u ractor.cnt:%u++", r->pub.id, vm->ractor.cnt);
VM_ASSERT(single_ractor_mode || RB_VM_LOCKED_P());
/* Incremental marking only runs in a single-objspace world, and nothing later can
@@ -846,8 +847,10 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL
rb_ractor_t *r = RACTOR_PTR(rv);
ractor_init(r, name, loc);
r->pub.id = ractor_next_id();
RUBY_DEBUG_LOG("r:%u", r->pub.id);
RB_VM_LOCKING() {
r->pub.id = ractor_next_id();
}
RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u", r->pub.id);
rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
r->verbose = cr->verbose;
@@ -973,7 +976,7 @@ rb_ractor_living_threads_insert(rb_ractor_t *r, rb_thread_t *th)
RACTOR_LOCK(r);
{
RUBY_DEBUG_LOG("r(%d)->threads.cnt:%d++", r->pub.id, r->threads.cnt);
RUBY_DEBUG_LOG("r(%"PRI_SERIALT_PREFIX"u)->threads.cnt:%d++", r->pub.id, r->threads.cnt);
ccan_list_add_tail(&r->threads.set, &th->lt_node);
r->threads.cnt++;
}
@@ -1145,7 +1148,7 @@ ractor_terminal_interrupt_all(rb_vm_t *vm)
rb_ractor_t *r = 0;
ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
if (r != vm->ractor.main_ractor) {
RUBY_DEBUG_LOG("r:%d", rb_ractor_id(r));
RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u", rb_ractor_id(r));
rb_ractor_terminate_interrupt_main_thread(r);
}
}
@@ -1362,7 +1365,7 @@ rb_ractor_dump(void)
ccan_list_for_each(&vm->ractor.set, r, vmlr_node) {
if (r != vm->ractor.main_ractor) {
fprintf(stderr, "r:%u (%s)\n", r->pub.id, ractor_status_str(r->status_));
fprintf(stderr, "r:%"PRI_SERIALT_PREFIX"u (%s)\n", r->pub.id, ractor_status_str(r->status_));
}
}
}
+2 -2
View File
@@ -383,7 +383,7 @@ class Ractor
def inspect
loc = __builtin_cexpr! %q{ RACTOR_PTR(self)->loc }
name = __builtin_cexpr! %q{ RACTOR_PTR(self)->name }
id = __builtin_cexpr! %q{ UINT2NUM(rb_ractor_id(RACTOR_PTR(self))) }
id = __builtin_cexpr! %q{ ULL2NUM(rb_ractor_id(RACTOR_PTR(self))) }
status = __builtin_cexpr! %q{
rb_str_new2(RACTOR_PTR(self)->status_ == ractor_terminated ? "terminated" : "running")
}
@@ -866,7 +866,7 @@ class Ractor
# port.inspect -> string
def inspect
"#<Ractor::Port to:\##{
__builtin_cexpr! "SIZET2NUM(rb_ractor_id(ractor_port_ptr_check(self)->r))"
__builtin_cexpr! "ULL2NUM(rb_ractor_id(ractor_port_ptr_check(self)->r))"
} id:#{
__builtin_cexpr! "SIZET2NUM(ractor_port_id(RACTOR_PORT_PTR(self)))"
}>"
+1 -1
View File
@@ -334,7 +334,7 @@ rb_ractor_set_current_ec_(rb_ractor_t *cr, rb_execution_context_t *ec, const cha
void rb_vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line);
void rb_vm_ractor_blocking_cnt_dec(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line);
static inline uint32_t
static inline rb_serial_t
rb_ractor_id(const rb_ractor_t *r)
{
return r->pub.id;
+3 -3
View File
@@ -1359,7 +1359,7 @@ ractor_wakeup_all(rb_ractor_t *r, enum ractor_wakeup_status wakeup_status)
{
ASSERT_ractor_unlocking(r);
RUBY_DEBUG_LOG("r:%u wakeup:%s", rb_ractor_id(r), wakeup_status_str(wakeup_status));
RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u wakeup:%s", rb_ractor_id(r), wakeup_status_str(wakeup_status));
bool wakeup_p = false;
@@ -1622,7 +1622,7 @@ ractor_send_basket(rb_execution_context_t *ec, const struct ractor_port *rp, str
{
bool closed = false;
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);
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);
RACTOR_LOCK(rp->r);
{
@@ -1644,7 +1644,7 @@ ractor_send_basket(rb_execution_context_t *ec, const struct ractor_port *rp, str
ractor_wakeup_all(rp->r, wakeup_by_send);
}
else {
RUBY_DEBUG_LOG("closed:%u@r%u", (unsigned int)ractor_port_id(rp), rb_ractor_id(rp->r));
RUBY_DEBUG_LOG("closed:%u@r%"PRI_SERIALT_PREFIX"u", (unsigned int)ractor_port_id(rp), rb_ractor_id(rp->r));
/* Nothing took the basket: it was not enqueued, so free it whether or not the
* caller wants the error raised. */
+1 -1
View File
@@ -976,7 +976,7 @@ thread_create_core(VALUE thval, struct thread_create_params *params)
RBASIC_CLEAR_CLASS(th->pending_interrupt_mask_stack);
}
RUBY_DEBUG_LOG("r:%u th:%u", rb_ractor_id(th->ractor), rb_th_serial(th));
RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u th:%u", rb_ractor_id(th->ractor), rb_th_serial(th));
rb_ractor_living_threads_insert(th->ractor, th);
+6 -6
View File
@@ -168,7 +168,7 @@ ractor_sched_dump_(const char *file, int line, rb_vm_t *vm)
ccan_list_for_each(&vm->ractor.sched.grq, r, threads.sched.grq_node) {
i++;
if (i>10) rb_bug("!!");
fprintf(stderr, " %d ready:%d\n", i, rb_ractor_id(r));
fprintf(stderr, " %d ready:%"PRI_SERIALT_PREFIX"u\n", i, rb_ractor_id(r));
}
}
@@ -248,7 +248,7 @@ ASSERT_thread_sched_locked(struct rb_thread_sched *sched, rb_thread_t *th)
RBIMPL_ATTR_MAYBE_UNUSED()
static unsigned int
static rb_serial_t
rb_ractor_serial(const rb_ractor_t *r)
{
if (r) {
@@ -290,9 +290,9 @@ ractor_sched_lock_(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
rb_native_mutex_lock(&vm->ractor.sched.lock);
#if VM_CHECK_MODE
RUBY_DEBUG_LOG2(file, line, "cr:%u prev_owner:%u", rb_ractor_serial(cr), rb_ractor_serial(vm->ractor.sched.lock_owner));
RUBY_DEBUG_LOG2(file, line, "cr:%"PRI_SERIALT_PREFIX"u prev_owner:%"PRI_SERIALT_PREFIX"u", rb_ractor_serial(cr), rb_ractor_serial(vm->ractor.sched.lock_owner));
#else
RUBY_DEBUG_LOG2(file, line, "cr:%u", rb_ractor_serial(cr));
RUBY_DEBUG_LOG2(file, line, "cr:%"PRI_SERIALT_PREFIX"u", rb_ractor_serial(cr));
#endif
ractor_sched_set_locked(vm, cr);
@@ -301,7 +301,7 @@ ractor_sched_lock_(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
static void
ractor_sched_unlock_(rb_vm_t *vm, rb_ractor_t *cr, const char *file, int line)
{
RUBY_DEBUG_LOG2(file, line, "cr:%u", rb_ractor_serial(cr));
RUBY_DEBUG_LOG2(file, line, "cr:%"PRI_SERIALT_PREFIX"u", rb_ractor_serial(cr));
ractor_sched_set_unlocked(vm, cr);
rb_native_mutex_unlock(&vm->ractor.sched.lock);
@@ -1323,7 +1323,7 @@ ractor_sched_enq(rb_vm_t *vm, rb_ractor_t *r)
vm->ractor.sched.grq_cnt++;
VM_ASSERT(grq_size(vm, cr) == vm->ractor.sched.grq_cnt);
RUBY_DEBUG_LOG("r:%u th:%u grq_cnt:%u", rb_ractor_id(r), rb_th_serial(sched->running), vm->ractor.sched.grq_cnt);
RUBY_DEBUG_LOG("r:%"PRI_SERIALT_PREFIX"u th:%u grq_cnt:%u", rb_ractor_id(r), rb_th_serial(sched->running), vm->ractor.sched.grq_cnt);
rb_native_cond_signal(&vm->ractor.sched.cond);
+1 -1
View File
@@ -2433,7 +2433,7 @@ rb_exec_event_hook_orig(rb_execution_context_t *ec, rb_hook_list_t *hooks, rb_ev
struct rb_ractor_pub {
VALUE self;
uint32_t id;
rb_serial_t id;
rb_hook_list_t hooks;
st_table targeted_hooks; // also called "local hooks". {ISEQ => hook_list, def => hook_list...}
unsigned int targeted_hooks_cnt; // ex: tp.enabled(target: method(:puts))