From 10e53afd4e649db1fbc3367b92ba407d464fc8ca Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 2 Jul 2026 20:53:02 +0000 Subject: [PATCH] ractor: lock the owner when Port#closed? is queried from another Ractor ractor_closed_port_p asserts the owning ractor's lock is held for foreign access and reads sync.ports via st_lookup, but Ractor::Port#closed? (ractor_port_closed_p) called it without the lock. From a foreign Ractor this tripped the assertion and raced the owner's st_insert/st_delete on the ports table. Take the ractor lock for foreign queries, like every other foreign reader. Co-Authored-By: Claude Opus 4.8 (1M context) --- ractor_sync.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ractor_sync.c b/ractor_sync.c index 5eeb6e20db..5dfb86e3bf 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -144,13 +144,27 @@ static VALUE ractor_port_closed_p(rb_execution_context_t *ec, VALUE self) { const struct ractor_port *rp = RACTOR_PORT_PTR(self); + rb_ractor_t *r = rp->r; + bool closed; - if (ractor_closed_port_p(ec, rp->r, rp)) { - return Qtrue; + if (rb_ec_ractor_ptr(ec) == r) { + /* The owner's threads are serialized by the ractor GVL, so the ports + * table can't change under this lookup. */ + closed = ractor_closed_port_p(ec, r, rp); } else { - return Qfalse; + /* A foreign Ractor races the owner's st_insert/st_delete on the ports + * table; take the lock like every other foreign reader. ractor_closed_port_p + * asserts the lock is held for foreign access, and Port#closed? was the + * only path reaching it without the lock. */ + RACTOR_LOCK(r); + { + closed = ractor_closed_port_p(ec, r, rp); + } + RACTOR_UNLOCK(r); } + + return closed ? Qtrue : Qfalse; } static VALUE