From ae0409111a13d0f0757cb36762a430300dda11e1 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 7 Mar 2013 14:47:48 +0100 Subject: [PATCH] :doc:. alarm_trap will call raise_if_sb_timed_out and setup. raise_if_sb_timed_out is the old alarm_trap (but alarm_trap should be used like before). setup recognize time outs lesser than one second now. --- Gemfile | 1 + Gemfile.lock | 2 + lib/timeout_interrupt.rb | 156 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 148 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index d2fe395..f30cf21 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem 'ffi-libc' group :development do gem "shoulda" gem "yard" + gem "redcarpet" gem "rdoc" gem "bundler" gem "jeweler" diff --git a/Gemfile.lock b/Gemfile.lock index 80a8c93..627102e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,6 +24,7 @@ GEM rake (10.0.3) rdoc (4.0.0) json (~> 1.4) + redcarpet (2.2.2) shoulda (3.3.2) shoulda-context (~> 1.0.1) shoulda-matchers (~> 1.4.1) @@ -45,6 +46,7 @@ DEPENDENCIES ffi-libc jeweler rdoc + redcarpet shoulda simplecov yard diff --git a/lib/timeout_interrupt.rb b/lib/timeout_interrupt.rb index 191c02b..01d09b3 100644 --- a/lib/timeout_interrupt.rb +++ b/lib/timeout_interrupt.rb @@ -1,40 +1,107 @@ require 'ffi/libc' require 'timeout' +# Provided by ffi-libc-lib and extended by this library, if needed. +# Older version of ffi-libc does not provide {FFI::LibC.alarm} module FFI module LibC + # @!method alarm(seconds) + # Sets an alarm. After `seconds` it will send an ALRM-signal to this process. + # + # Predefined alarm will be reset and will forget. + # @note Older implementations of ffi-libc does not provide {alarm}, but we need it. + # So we detect, if it is not provided and attach it. + # @param seconds [0] Clears alarm. + # @param seconds [Integer] How many seconds should be waited, before ALRM-signal should be send? + # @return (nil) attach_function :alarm, [:uint], :uint unless FFI::LibC.respond_to? :alarm end end +# Helper module for `TimeoutInterrupt` +# @see TimeoutInterrupt module TimeoutInterruptSingleton class <), exception(Exception)] >] def timeouts thread = nil - @timeouts ||= Hash.new {|h,k| h[k] = [] } - thread = Thread.current if thread.kind_of? Thread + @timeouts ||= Hash.new {|h,k| h[k] = {} } + thread = Thread.current unless thread.kind_of? Thread thread ? @timeouts[thread] : @timeouts end + # If there's a timed out timeout, it will raise its exception. + # Can be used for handling ALRM-signal. + # It will prepare the next timeout, too. + # + # The timeout will not removed from timeouts, because it is timed out, yet. + # First, if timeout-scope will be exit, it will be removed. + # + # @return [nil] def alarm_trap sig + raise_if_sb_timed_out + setup + end + + # There is a timed out timeout? It will raise it! + # You need not to check it yourself, it will do it for you. + # + # @return [nil] + def raise_if_sb_timed_out + return if self.timeouts.empty? key, (at, bt, exception) = self.timeouts.min_by {|key,(at,bt,ex)| at } return if Time.now < at raise exception, 'execution expired', bt end + # Prepares the next timeout. Sets the trap and the shortest timeout as alarm. + # + # @return [nil] def setup if timeouts.empty? Signal.trap( 'ALRM') {} FFI::LibC.alarm 0 else - key, (at, bt) = timeouts.min_by {|key,(at,bt)| at } - secs = (at - Time.now) - alarm_trap 14 if 0 > secs + raise_if_sb_timed_out Signal.trap 'ALRM', &method( :alarm_trap) - FFI::LibC.alarm secs.to_i+1 + key, (at, bt) = timeouts.min_by {|key,(at,bt)| at } + FFI::LibC.alarm (at - Time.now).to_i + 1 end + nil end - def timeout seconds = nil, exception = nil + # Creates a timeout and calls your block, which has to finish before timeout occurs. + # + # @param seconds [Integer] In `seconds` Seconds, it should raise a timeout, if not finished. + # @param seconds [nil] Everything will be ignored and + # it will call {setup} for checking and preparing next known timeout. + # @param exception [Exception] which will be raised if timed out. + # @param exception [nil] `TimeoutInterrupt::Error` will be used to raise. + # @param block [Proc] Will be called and should finish its work before it timed out. + # @param block [nil] Nothing will happen, instead it will return a Proc, + # which can be called with a block to use the timeout. + # @return If block given, the returned value of your block. + # Or if not, it will return a Proc, which will expect a Proc if called. + # This Proc has no arguments and will prepare a timeout, like if you had given a block. + # + # You can rescue `Timeout::Error`, instead `TimeoutInterrupt::Error`, it will work too. + # + # It will call your given block, which has `seconds` seconds to end. + # If you want to prepare a timeout, which should be used many times, + # without giving `seconds` and `exception`, you can omit the block, + # so, `TimeoutInterruptSingleton#timeout` will return a `Proc`, which want to have the block. + # + # There is a problem with scoped timeouts. If you rescue a timeout in an other timeout, + # it's possible, that the other timeout will never timeout, because both are timed out at once. + # Than you need to call `TimeoutInterruptSingleton#timeout` without arguments. + # It will prepare the next timeout or it will raise it directy, if timed out. + # + # @see TimeoutInterrupt.timeout + # @see TimeoutInterrupt#timeout + # @raise exception + def timeout seconds = nil, exception = nil, &block return setup if seconds.nil? seconds = seconds.to_i exception ||= TimeoutInterrupt::Error @@ -59,15 +126,82 @@ module TimeoutInterruptSingleton end end +# Can be included, or used directly. +# In both cases, it provides {#timeout}. +# +# @see TimeoutInterruptSingleton module TimeoutInterrupt + # The {TimeoutInterrupt::Error} is the default exception, which will be raised, + # if something will time out. + # Its base-class is {Timeout::Error}, so you can replace {Timeout} by {TimeoutInterrupt} without + # replacing your `rescue Timeout::Error`, but you can. class Error < Timeout::Error end - def self.timeout seconds = nil, exception = nil, &e - TimeoutInterruptSingleton.timeout seconds, exception, &e + # Creates a timeout and calls your block, which has to finish before timeout occurs. + # + # @param seconds [Integer] In `seconds` Seconds, it should raise a timeout, if not finished. + # @param seconds [nil] Everything will be ignored and + # it will call {TimeoutInterruptSingleton.setup} for checking and preparing next known timeout. + # @param exception [Exception] which will be raised if timed out. + # @param exception [nil] `TimeoutInterrupt::Error` will be used to raise. + # @param block [Proc] Will be called and should finish its work before it timed out. + # @param block [nil] Nothing will happen, instead it will return a Proc, + # which can be called with a block to use the timeout. + # @return If block given, the returned value of your block. + # Or if not, it will return a Proc, which will expect a Proc if called. + # This Proc has no arguments and will prepare a timeout, like if you had given a block. + # + # You can rescue `Timeout::Error`, instead `TimeoutInterrupt::Error`, it will work too. + # + # It will call your given block, which has `seconds` seconds to end. + # If you want to prepare a timeout, which should be used many times, + # without giving `seconds` and `exception`, you can omit the block, + # so, `TimeoutInterruptSingleton#timeout` will return a `Proc`, which want to have the block. + # + # There is a problem with scoped timeouts. If you rescue a timeout in an other timeout, + # it's possible, that the other timeout will never timeout, because both are timed out at once. + # Than you need to call `TimeoutInterruptSingleton#timeout` without arguments. + # It will prepare the next timeout or it will raise it directy, if timed out. + # + # @see TimeoutInterrupt#timeout + # @see TimeoutInterruptSingleton.timeout + # @raise exception + def self.timeout seconds = nil, exception = nil, &block + TimeoutInterruptSingleton.timeout seconds, exception, &block end - def timeout seconds = nil, exception = nil, &e - TimeoutInterruptSingleton.timeout seconds, exception, &e + # Creates a timeout and calls your block, which has to finish before timeout occurs. + # + # @param seconds [Integer] In `seconds` Seconds, it should raise a timeout, if not finished. + # @param seconds [nil] Everything will be ignored and + # it will call {TimeoutInterruptSingleton.setup} for checking and preparing next known timeout. + # @param exception [Exception] which will be raised if timed out. + # @param exception [nil] `TimeoutInterrupt::Error` will be used to raise. + # @param block [Proc] Will be called and should finish its work before it timed out. + # @param block [nil] Nothing will happen, instead it will return a Proc, + # which can be called with a block to use the timeout. + # @return If block given, the returned value of your block. + # Or if not, it will return a Proc, which will expect a Proc if called. + # This Proc has no arguments and will prepare a timeout, like if you had given a block. + # + # You can rescue `Timeout::Error`, instead `TimeoutInterrupt::Error`, it will work too. + # + # It will call your given block, which has `seconds` seconds to end. + # If you want to prepare a timeout, which should be used many times, + # without giving `seconds` and `exception`, you can omit the block, + # so, `TimeoutInterruptSingleton#timeout` will return a `Proc`, which want to have the block. + # + # There is a problem with scoped timeouts. If you rescue a timeout in an other timeout, + # it's possible, that the other timeout will never timeout, because both are timed out at once. + # Than you need to call `TimeoutInterruptSingleton#timeout` without arguments. + # It will prepare the next timeout or it will raise it directy, if timed out. + # + # @note This method is useful, if you `include TimeoutInterrupt`. You can call it directly. + # @see TimeoutInterrupt.timeout + # @see TimeoutInterruptSingleton.timeout + # @raise exception + def timeout seconds = nil, exception = nil, &block + TimeoutInterruptSingleton.timeout seconds, exception, &block end end