mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
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:
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
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user