Avoid a stale TLS execution context in fiber_switch after NT migration

`fiber_current()` reads `GET_EC()`, which the compiler may inline as a
thread-pointer-relative TLS access whose base is cached in a callee-saved
register.  `coroutine_transfer` preserves callee-saved registers across the
stack switch, so when a fiber's coroutine resumes on a different native
thread (M:N scheduler migration), the cached thread pointer is stale and
`fiber_current()` returns another thread's execution context.

`fiber_switch()` calls `fiber_current()` right after the transfer.  With
`-DRUBY_DEBUG=1` this trips `VM_ASSERT(ec == rb_current_ec_noinline())` in
`rb_current_execution_context`; in release builds it silently uses the
wrong (or NULL) EC.  Only reproduces with >= 2 Ractors (a single Ractor
does not migrate the coroutine).

Read the current EC through `rb_current_ec_noinline()` in `fiber_current()`,
which forces a fresh thread-pointer load.  fiber_current() is only used by
the (cold) fiber machinery, so the non-inlined read costs nothing on hot
paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Koichi SasadaandClaude Opus 4.8 committed 2026-07-11 11:01:06 +09:00
1 parent 0e3866868d
commit ca20425fd5
2 files changed
+22 -1
Notes: git 2026-07-11 02:01:35 +00:00

No files matched your search

+19
View File
@@ -42,3 +42,22 @@ assert_normal_exit %q{
assert_normal_exit %q{
Thread.new { Fiber.current.kill }.join
}
# fiber_current() must not read a stale (cached) TLS execution context after a
# fiber's coroutine migrates between native threads under the M:N scheduler.
# Needs >= 2 Ractors so coroutines actually migrate; sleep drives the migration.
assert_equal 'ok', %q{
Warning[:experimental] = false
2.times.map do
Ractor.new do
200.times do
f = Fiber.new { Fiber.yield; 1 }
f.resume
sleep(0.0003) # block -> M:N native-thread migration
f.resume rescue nil
end
:ok
end
end.each(&:value)
:ok
}
+3 -1
View File
@@ -2187,7 +2187,9 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking)
static inline rb_fiber_t*
fiber_current(void)
{
rb_execution_context_t *ec = GET_EC();
/* Called right after a coroutine transfer: an inlined GET_EC() may read a
* TLS pointer cached before the NT migration, so force a fresh load. */
rb_execution_context_t *ec = rb_current_ec_noinline();
return ec->fiber_ptr;
}