: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.

This commit is contained in:
Denis Knauf 2013-03-07 14:47:48 +01:00
parent 97438aa9ec
commit ae0409111a
3 changed files with 148 additions and 11 deletions

View file

@ -7,6 +7,7 @@ gem 'ffi-libc'
group :development do
gem "shoulda"
gem "yard"
gem "redcarpet"
gem "rdoc"
gem "bundler"
gem "jeweler"

View file

@ -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

View file

@ -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 <<self
# Stores all timeouts.
#
# @param thread [nil] must be nil! Do not use it yet!
# @return [Hash< key(Integer): [at(Time), backtrace(Array<String>), 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