diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/-ext-/scheduler/test_interrupt_with_scheduler.rb | 55 | ||||
| -rw-r--r-- | test/fiber/scheduler.rb | 104 |
2 files changed, 112 insertions, 47 deletions
diff --git a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb new file mode 100644 index 0000000000..3f9a7f55a0 --- /dev/null +++ b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +require 'test/unit' +require 'timeout' +require_relative '../../fiber/scheduler' + +class TestSchedulerInterruptHandling < Test::Unit::TestCase + def setup + pend("No fork support") unless Process.respond_to?(:fork) + require '-test-/scheduler' + end + + # Test without Thread.handle_interrupt - should work regardless of fix + def test_without_handle_interrupt_signal_works + IO.pipe do |input, output| + pid = fork do + scheduler = Scheduler.new + Fiber.set_scheduler scheduler + + Signal.trap(:INT) do + ::Thread.current.raise(Interrupt) + end + + Fiber.schedule do + # Yield to the scheduler: + sleep(0) + + output.puts "ready" + Bug::Scheduler.blocking_loop + end + end + + output.close + assert_equal "ready\n", input.gets + + sleep 0.1 # Ensure the child is in the blocking loop + $stderr.puts "Sending interrupt" + Process.kill(:INT, pid) + + reaper = Thread.new do + Process.waitpid2(pid) + end + + unless reaper.join(1) + Process.kill(:KILL, pid) + end + + _, status = reaper.value + + # It should be interrupted (not killed): + assert_not_equal 0, status.exitstatus + assert_equal true, status.signaled? + assert_equal Signal.list["INT"], status.termsig + end + end +end diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 2401cb30d3..60261d69e2 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -65,69 +65,79 @@ class Scheduler end end - def run - # $stderr.puts [__method__, Fiber.current].inspect - + def run_once readable = writable = nil - while @readable.any? or @writable.any? or @waiting.any? or @blocking.any? - # May only handle file descriptors up to 1024... - begin - readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout) - rescue IOError - # Ignore - this can happen if the IO is closed while we are waiting. - end + begin + readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout) + rescue IOError + # Ignore - this can happen if the IO is closed while we are waiting. + end - # puts "readable: #{readable}" if readable&.any? - # puts "writable: #{writable}" if writable&.any? + # puts "readable: #{readable}" if readable&.any? + # puts "writable: #{writable}" if writable&.any? - selected = {} + selected = {} - readable&.each do |io| - if fiber = @readable.delete(io) - @writable.delete(io) if @writable[io] == fiber - selected[fiber] = IO::READABLE - elsif io == @urgent.first - @urgent.first.read_nonblock(1024) - end + readable&.each do |io| + if fiber = @readable.delete(io) + @writable.delete(io) if @writable[io] == fiber + selected[fiber] = IO::READABLE + elsif io == @urgent.first + @urgent.first.read_nonblock(1024) end + end - writable&.each do |io| - if fiber = @writable.delete(io) - @readable.delete(io) if @readable[io] == fiber - selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE - end + writable&.each do |io| + if fiber = @writable.delete(io) + @readable.delete(io) if @readable[io] == fiber + selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE end + end - selected.each do |fiber, events| - fiber.transfer(events) - end + selected.each do |fiber, events| + fiber.transfer(events) + end - if @waiting.any? - time = current_time - waiting, @waiting = @waiting, {} - - waiting.each do |fiber, timeout| - if fiber.alive? - if timeout <= time - fiber.transfer - else - @waiting[fiber] = timeout - end + if @waiting.any? + time = current_time + waiting, @waiting = @waiting, {} + + waiting.each do |fiber, timeout| + if fiber.alive? + if timeout <= time + fiber.transfer + else + @waiting[fiber] = timeout end end end + end - if @ready.any? - ready = nil + if @ready.any? + ready = nil - @lock.synchronize do - ready, @ready = @ready, [] - end + @lock.synchronize do + ready, @ready = @ready, [] + end - ready.each do |fiber| - fiber.transfer if fiber.alive? - end + ready.each do |fiber| + fiber.transfer if fiber.alive? + end + end + end + + def run + # $stderr.puts [__method__, Fiber.current].inspect + + # Use Thread.handle_interrupt like Async::Scheduler does + # This defers signal processing, which is the root cause of the gRPC bug + # See: https://github.com/socketry/async/blob/main/lib/async/scheduler.rb + Thread.handle_interrupt(::SignalException => :never) do + while @readable.any? or @writable.any? or @waiting.any? or @blocking.any? + run_once + + break if Thread.pending_interrupt? end end end |
