Use ec->interrupt_mask to prevent interrupts.#14588
Conversation
efbc4b2 to
950dbe9
Compare
|
Example failure: |
|
(Original analysis) The Root CauseWhen Why This Is Critical
The Assertion#ifdef RUBY_DEBUG
rb_execution_context_t *ec = GET_EC();
if (ec->interrupt_flag) {
rb_bug("rb_fiber_scheduler_unblock called with interrupt flags set");
}
#endifThis assertion detects the race condition by catching cases where Safe Usage PatternThe correct approach is to use EC_PUSH_TAG(ec);
int state = EC_EXEC_TAG();
if (state == TAG_NONE) {
// Normal execution - may raise exceptions
some_blocking_operation();
}
EC_POP_TAG(); // Exception state is now controlled
// SAFE ZONE - no new interrupts processed
if (fiber_needs_unblocking) {
rb_fiber_scheduler_unblock(fiber); // ✅ Safe here
}
if (state != TAG_NONE) {
EC_JUMP_TAG(ec, state); // Re-raise exception
}Why EC_TAG Context Is SafeThe ensure block after
ImpactWithout this protection, Ruby applications using fiber schedulers can experience:
Solution RequirementsCode calling
This ensures that fiber unblocking is an atomic, all-or-nothing operation that cannot be interrupted mid-execution. |
|
In addition to the above analysis it turns out that it's insufficient - signals delivered during unblock can also cause interrupts, that's what this fix handles. |
Ractors can send signals at any time, so the previous debug assertion
can fail if a Ractor sends a signal.
```ruby
require 'async/scheduler'
scheduler = Async::Scheduler.new
Fiber.set_scheduler(scheduler)
Signal.trap(:INT) do
end
q = Thread::Queue.new
Thread.new do
loop do
Ractor.new do
Process.kill(:INT, $$)
end.value
end
end
Fiber.schedule do
Fiber.schedule do
1.upto(1000000) do |i|
sleep 0.01
q.pop
q.push(1)
puts "1 iter push/pop"
end
end
Fiber.schedule do
1.upto(1000000) do |i|
sleep 0.01
q.push(i)
q.pop
puts "1 iter push/pop#2"
end
end
end
```
c721a95 to
dfe9072
Compare
Disallow pending interrupts to be checked during `FiberScheduler#unblock`. Ractors can send signals at any time, so the previous debug assertion can fail if a Ractor sends a signal. Co-authored-by: Luke Gruber <luke.gruber@shopify.com>
The following program can segfault without this change:
The segfault is deliberate due to
rb_bugbut without that, the program could probably hang indefinitely. I tried to write a test for this but it's too hard.cc @luke-gruber