bcd9129c21 (master): MN threads: reclaim a terminated coroutine via nt->dead_co, not the transfer return value

0 views
Skip to first unread message

Koichi Sasada

unread,
12:36 PM (8 hours ago) 12:36 PM
to ruby...@g.ruby-lang.org
Koichi Sasada 2026-07-09 12:31:53 +0000 (Thu, 09 Jul 2026)

New Revision: bcd9129c21

https://github.com/ruby/ruby/commit/bcd9129c21

Log:
MN threads: reclaim a terminated coroutine via nt->dead_co, not the transfer return value

The nt scheduling loop reclaimed a terminated coroutine thread's context using
the return value of coroutine_transfer() (through thread_sched_switch0),
assuming it was the resuming context (the dead coroutine). That holds for the
amd64 asm backend, but the ucontext backend returns the transfer *target*
instead, so the loop never recognized the terminal transfer: the reclaim always
saw a live thread, and the loop always unlocked the sched lock -- which the
dying thread had already released in coroutine_thread_terminated -- and
double-unlocked it. Benign on glibc, but FreeBSD's pthread_mutex_unlock returns
EPERM (crash), and it corrupts the scheduler (hang) elsewhere.

Have the terminating coroutine record its own context in nt->dead_co (per
native thread) right before its final transfer; the loop reads it after switch0
returns, independent of coroutine_transfer()'s backend-dependent return value.
It is set and read on the same native thread with nothing running in between,
so it cannot be overwritten before it is consumed.

coroutine_transfer0() and thread_sched_switch0() no longer need to return the
resuming context, so revert them to void (the ASan-only local remains).

Reproduced on Linux with --with-coroutine=ucontext (100% hang before, clean
after); amd64 and ucontext both pass bootstraptest thread/ractor/fiber.

Co-Authored-By: Claude Opus 4.8 <nor...@anthropic.com>

Modified files:
thread_pthread.c
thread_pthread.h
thread_pthread_mn.c
Reply all
Reply to author
Forward
0 new messages