mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
`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>
64 lines
1.3 KiB
Ruby
64 lines
1.3 KiB
Ruby
show_limit %q{
|
|
fibers = []
|
|
begin
|
|
fiber = Fiber.new{Fiber.yield}
|
|
fiber.resume
|
|
fibers << fiber
|
|
|
|
raise Exception, "skipping" if fibers.count >= 10_000
|
|
rescue Exception => error
|
|
puts "Fiber count: #{fibers.count} (#{error})"
|
|
break
|
|
end while true
|
|
}
|
|
|
|
assert_equal %q{ok}, %q{
|
|
Fiber.new{
|
|
}.resume
|
|
:ok
|
|
}
|
|
|
|
assert_equal %q{ok}, %q{
|
|
100.times.collect{Fiber.new{}}
|
|
:ok
|
|
}
|
|
|
|
assert_equal %q{ok}, %q{
|
|
fibers = 1000.times.collect{Fiber.new{Fiber.yield}}
|
|
fibers.each(&:resume)
|
|
fibers.each(&:resume)
|
|
:ok
|
|
}
|
|
|
|
assert_normal_exit %q{
|
|
at_exit { Fiber.new{}.resume }
|
|
}
|
|
|
|
assert_normal_exit %q{
|
|
Fiber.new(&Object.method(:class_eval)).resume("foo")
|
|
}, '[ruby-dev:34128]'
|
|
|
|
# [Bug #21400]
|
|
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
|
|
}
|