diff options
| author | nagachika <nagachika@ruby-lang.org> | 2026-05-09 15:09:56 +0900 |
|---|---|---|
| committer | nagachika <nagachika@ruby-lang.org> | 2026-05-09 15:09:56 +0900 |
| commit | 6eeeb07e273c069273bcd930a612a0c960668a11 (patch) | |
| tree | c493e8052b0e69f4b908f44ed6c38b5ac43032f2 | |
| parent | 8a434effcafaa4c2f32170a0003d3c1219110890 (diff) | |
merge revision(s) f315d250b44e75a1a69f4a05b293dcc701377689: [Backport #21947]
[PATCH] [ruby/timeout] Compatibility with Fiber scheduler. (https://github.com/ruby/timeout/pull/97)
[Bug #21947]
https://github.com/ruby/timeout/commit/55d7c84b50
| -rw-r--r-- | lib/timeout.rb | 28 | ||||
| -rw-r--r-- | test/test_timeout.rb | 73 | ||||
| -rw-r--r-- | version.h | 2 |
3 files changed, 89 insertions, 14 deletions
diff --git a/lib/timeout.rb b/lib/timeout.rb index 4fd1fa46da..6218b1969e 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -171,20 +171,22 @@ module Timeout message ||= "execution expired" if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after) - return scheduler.timeout_after(sec, klass || Error, message, &block) - end - - Timeout.ensure_timeout_thread_created - perform = Proc.new do |exc| - request = Request.new(Thread.current, sec, exc, message) - QUEUE_MUTEX.synchronize do - QUEUE << request - CONDVAR.signal + perform = Proc.new do |exc| + scheduler.timeout_after(sec, exc, message, &block) end - begin - return yield(sec) - ensure - request.finished + else + Timeout.ensure_timeout_thread_created + perform = Proc.new do |exc| + request = Request.new(Thread.current, sec, exc, message) + QUEUE_MUTEX.synchronize do + QUEUE << request + CONDVAR.signal + end + begin + return yield(sec) + ensure + request.finished + end end end diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 01156867b0..7809fdd68f 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -274,4 +274,77 @@ class TestTimeout < Test::Unit::TestCase }.join end; end + + if Fiber.respond_to?(:current_scheduler) + # Stubs Fiber.current_scheduler for the duration of the block, then restores it. + def with_mock_scheduler(mock) + original = Fiber.method(:current_scheduler) + Fiber.define_singleton_method(:current_scheduler) { mock } + begin + yield + ensure + Fiber.define_singleton_method(:current_scheduler, original) + end + end + + def test_fiber_scheduler_delegates_to_timeout_after + received = nil + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + received = [sec, exc, msg] + blk.call(sec) + end + + with_mock_scheduler(mock) do + assert_equal :ok, Timeout.timeout(5) { :ok } + end + + assert_equal 5, received[0] + assert_instance_of Timeout::ExitException, received[1], "scheduler should receive an ExitException instance when no klass given" + assert_equal "execution expired", received[2] + end + + def test_fiber_scheduler_delegates_to_timeout_after_with_custom_exception + custom_error = Class.new(StandardError) + received = nil + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + received = [sec, exc, msg] + blk.call(sec) + end + + with_mock_scheduler(mock) do + assert_equal :ok, Timeout.timeout(5, custom_error, "custom message") { :ok } + end + + assert_equal [5, custom_error, "custom message"], received + end + + def test_fiber_scheduler_timeout_raises_timeout_error + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + raise exc # simulate timeout firing + end + + with_mock_scheduler(mock) do + assert_raise(Timeout::Error) do + Timeout.timeout(5) { :should_not_reach } + end + end + end + + def test_fiber_scheduler_timeout_raises_custom_error + custom_error = Class.new(StandardError) + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + raise exc, msg + end + + with_mock_scheduler(mock) do + assert_raise_with_message(custom_error, "custom message") do + Timeout.timeout(5, custom_error, "custom message") { :should_not_reach } + end + end + end + end end @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 9 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 85 +#define RUBY_PATCHLEVEL 86 #include "ruby/version.h" #include "ruby/internal/abi.h" |
