From 6f57bb4223aa3d01b05b05244d8da30fd53c9cab Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 7 Mar 2013 12:30:07 +0100 Subject: [PATCH] Classes ======= TimeoutInterruptSingleton created for helper methods like setup and so on. TimeoutInterrupt#timeout and TimeoutInterrupt.timeout are stubs for calling timeout of singleton-class. Scopeable ========= `TimeoutInterrupt.timeout` should be scopeable now. `TimeoutInterrupt.timeout` without args will check timeouts and will raise the next timed out timeout, needed for scopes and if many time outs at the same time occurs. --- README.md | 55 +++++++++- lib/timeout_interrupt.rb | 94 ++++++++++------ test/test_ruby-timeout-interrupt.rb | 159 ++++++++++++++++++++-------- 3 files changed, 230 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index b796ae9..2ff58c5 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,62 @@ It uses POSIX's alarm and traps ALRM-signals. Known limitations bacause of alarm and ALRM are, that you can not use alarm or trap ALRM. +Scopes +====== + +If you need scopes with inner and outer time outs, you should know: + +The first timed out Timeout will be raised: + + include TimeoutInterrupt + timeout(1) { # Will be raised + timeout(10) { sleep 2 } # Will not be raised + } + +If you want to know, which was raised, you need custom exceptions: + + class CustomErrorWillBeRaised secs - Signal.trap 'ALRM', &TimeoutInterrupt.method( :alarm_trap) - FFI::LibC.alarm secs +module TimeoutInterruptSingleton + class < secs + Signal.trap 'ALRM', &method( :alarm_trap) + FFI::LibC.alarm secs.to_i+1 + end + end + + def timeout seconds = nil, exception = nil + return setup if seconds.nil? + seconds = seconds.to_i + exception ||= TimeoutInterrupt::Error + raise exception, "Timeout must be longer than '0' seconds." unless 0 < seconds + unless block_given? + return lambda {|&e| + raise exception, "Expect a lambda." unless e + timeout seconds, exception, &e + } + end + at = Time.now + seconds + key, bt = Random.rand( 2**64-1), Kernel.caller + begin + self.timeouts[key] = [at, bt, exception] + setup + yield + ensure + self.timeouts.delete key + setup + end end end end + +module TimeoutInterrupt + class Error < Timeout::Error + end + + def self.timeout seconds = nil, exception = nil, &e + TimeoutInterruptSingleton.timeout seconds, exception, &e + end + + def timeout seconds = nil, exception = nil, &e + TimeoutInterruptSingleton.timeout seconds, exception, &e + end +end diff --git a/test/test_ruby-timeout-interrupt.rb b/test/test_ruby-timeout-interrupt.rb index fe8ffa1..91487ba 100644 --- a/test/test_ruby-timeout-interrupt.rb +++ b/test/test_ruby-timeout-interrupt.rb @@ -11,70 +11,145 @@ class TestRubyTimeoutInterrupt < Test::Unit::TestCase end def assert_no_defined_timeout_yet - assert TimeoutInterrupt.timeouts.empty?, "For testing, no timeout should be defined, yet!" + assert TimeoutInterruptSingleton.timeouts.empty?, "For testing, no timeout should be defined, yet!" end - should "not interrupt a long blocking call with the old Timeout" do - time = Benchmark.realtime do - begin - TimeoutInterrupt.timeout(5) do - Timeout.timeout(1) do + def print_timeouts pre + puts "#{pre}: < #{TimeoutInterruptSingleton.timeouts.map {|k,(a,_b,_e)| "#{k.inspect}: #{a.strftime '%H:%M:%S'} (#{a-Time.now})" }.join ', '} >" + end + + # For testing raising scoped Timeout. + class TimeoutError < Exception + end + # For testing raising scoped TimeoutInterrupt. + class TimeoutInterruptError < Exception + end + + context "Long really blocking calls" do + should "not be interrupted by the old Timeout" do + time = Benchmark.realtime do + assert_nothing_raised TimeoutError, "Unexpected time out. Your Ruby implementation can time out with old Timeout? You need not TimeoutInterrupt. But it is ok. You can ignore this Error. :)" do + assert_raise TimeoutInterruptError, "Ohoh. TimeoutInterrupt should be raised." do + TimeoutInterrupt.timeout 5, TimeoutInterruptError do + Timeout.timeout 1, TimeoutError do + blocking + assert false, "Should be unreachable!" + end + end + end + end + end + assert 3 < time, "Did timeout!" + end + + should "be interrupted by the new TimeoutInterrupt" do + time = Benchmark.realtime do + assert_raise TimeoutInterrupt::Error, "It should be timed out, why it did not raise TimeoutInterrupt::Error?" do + TimeoutInterrupt.timeout 1 do blocking assert false, "Should be unreachable!" end end - rescue Timeout::Error - :ok end + assert 3 > time, "Did not interrupt." end - assert 3 < time, "Did timeout!" end - should "interrupt a long blocking call with the new TimeoutInterrupt" do - time = Benchmark.realtime do - begin - TimeoutInterrupt.timeout(1) do - blocking + should "interrupt scoped timeout, but not time out the outer timeout" do + assert_no_defined_timeout_yet + assert_raise TimeoutInterruptError, "It should be timed out, why it did not raise TimeoutInterruptError?" do + assert_nothing_raised Timeout::Error, "Oh, outer timeout was timed out. Your machine must be slow, or there is a bug" do + TimeoutInterrupt.timeout 10 do + TimeoutInterrupt.timeout 1, TimeoutInterruptError do + Kernel.sleep 2 + end assert false, "Should be unreachable!" end - rescue Timeout::Error - :ok end end - assert 3 > time, "Did not interrupt." - end - - should "interrupt scoped timeout, but not outer timeout" do - assert_no_defined_timeout_yet - begin - TimeoutInterrupt.timeout(10) do - TimeoutInterrupt.timeout(1) do - Kernel.sleep 2 - end - assert false, "Should be unreachable!" - end - rescue Timeout::Error - :ok - end - assert TimeoutInterrupt.timeouts.empty?, "There are timeouts defined, yet!" + assert TimeoutInterruptSingleton.timeouts.empty?, "There are timeouts defined, yet!" end should "clear timeouts, if not timed out, too." do assert_no_defined_timeout_yet TimeoutInterrupt.timeout(10) {} - assert TimeoutInterrupt.timeouts.empty?, "There are timeouts defined, yet!" + assert TimeoutInterruptSingleton.timeouts.empty?, "There are timeouts defined, yet!" end - should "return a Proc if now block given, but do not create a timeout." do - assert_no_defined_timeout_yet - assert TimeoutInterrupt.timeout(10).kind_of?( Proc), "Did not return a Proc." + class CustomException