diff options
Diffstat (limited to 'lib/timeout.rb')
| -rw-r--r-- | lib/timeout.rb | 194 |
1 files changed, 101 insertions, 93 deletions
diff --git a/lib/timeout.rb b/lib/timeout.rb index 5a99c28092..a33bb4ce65 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -1,122 +1,130 @@ -#-- -# = timeout.rb +# frozen_string_literal: false +# Timeout long-running blocks # -# execution timeout +# == Synopsis # -# = Copyright -# -# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc. -# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan -# -#++ -# -# = Description +# require 'timeout' +# status = Timeout::timeout(5) { +# # Something that should be interrupted if it takes more than 5 seconds... +# } # -# A way of performing a potentially long-running operation in a thread, and -# terminating it's execution if it hasn't finished within fixed amount of -# time. +# == Description # -# Previous versions of timeout didn't use a module for namespace. This version -# provides both Timeout.timeout, and a backwards-compatible #timeout. +# Timeout provides a way to auto-terminate a potentially long-running +# operation if it hasn't finished in a fixed amount of time. # -# = Synopsis +# Previous versions didn't use a module for namespacing, however +# #timeout is provided for backwards compatibility. You +# should prefer Timeout.timeout instead. # -# require 'timeout' -# status = Timeout::timeout(5) { -# # Something that should be interrupted if it takes too much time... -# } +# == Copyright # +# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc. +# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Timeout + # Raised by Timeout.timeout when the block times out. + class Error < RuntimeError + attr_reader :thread - ## - # Raised by Timeout#timeout when the block times out. + def self.catch(*args) + exc = new(*args) + exc.instance_variable_set(:@thread, Thread.current) + ::Kernel.catch(exc) {yield exc} + end - class Error < Interrupt - end - class ExitException < ::Exception # :nodoc: + def exception(*) + # TODO: use Fiber.current to see if self can be thrown + if self.thread == Thread.current + bt = caller + begin + throw(self, bt) + rescue UncaughtThrowError + end + end + self + end end + # :stopdoc: THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0 + private_constant :THIS_FILE, :CALLER_OFFSET + # :startdoc: - ## - # Executes the method's block. If the block execution terminates before +sec+ - # seconds has passed, it returns true. If not, it terminates the execution - # and raises +exception+ (which defaults to Timeout::Error). + # Perform an operation in a block, raising an error if it takes longer than + # +sec+ seconds to complete. # - # Note that this is both a method of module Timeout, so you can 'include - # Timeout' into your classes so they have a #timeout method, as well as a - # module method, so you can call it directly as Timeout.timeout(). - - def timeout(sec, klass = nil) - return yield if sec == nil or sec.zero? - raise ThreadError, "timeout within critical session" if Thread.critical - exception = klass || Class.new(ExitException) - begin - x = Thread.current - y = Thread.start { - sleep sec - x.raise exception, "execution expired" if x.alive? - } - yield sec - # return true - rescue exception => e - rej = /\A#{Regexp.quote(__FILE__)}:#{__LINE__-4}\z/o - (bt = e.backtrace).reject! {|m| rej =~ m} - level = -caller(CALLER_OFFSET).size - while THIS_FILE =~ bt[level] - bt.delete_at(level) - level += 1 + # +sec+:: Number of seconds to wait for the block to terminate. Any number + # may be used, including Floats to specify fractional seconds. A + # value of 0 or +nil+ will execute the block without any timeout. + # +klass+:: Exception Class to raise if the block fails to terminate + # in +sec+ seconds. Omitting will use the default, Timeout::Error + # +message+:: Error message to raise with Exception Class. + # Omitting will use the default, "execution expired" + # + # Returns the result of the block *if* the block completed before + # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. + # + # The exception thrown to terminate the given block cannot be rescued inside + # the block unless +klass+ is given explicitly. + # + # Note that this is both a method of module Timeout, so you can <tt>include + # Timeout</tt> into your classes so they have a #timeout method, as well as + # a module method, so you can call it directly as Timeout.timeout(). + def timeout(sec, klass = nil, message = nil) #:yield: +sec+ + return yield(sec) if sec == nil or sec.zero? + message ||= "execution expired".freeze + from = "from #{caller_locations(1, 1)[0]}" if $DEBUG + e = Error + bl = proc do |exception| + begin + x = Thread.current + y = Thread.start { + Thread.current.name = from + begin + sleep sec + rescue => e + x.raise e + else + x.raise exception, message + end + } + return yield(sec) + ensure + if y + y.kill + y.join # make sure y is dead. + end + end + end + if klass + begin + bl.call(klass) + rescue klass => e + bt = e.backtrace end - raise if klass # if exception class is specified, it - # would be expected outside. - raise Error, e.message, e.backtrace - ensure - y.kill if y and y.alive? + else + bt = Error.catch(message, &bl) + end + level = -caller(CALLER_OFFSET).size-2 + while THIS_FILE =~ bt[level] + bt.delete_at(level) end + raise(e, message, bt) end module_function :timeout - end -## -# Identical to: -# -# Timeout::timeout(n, e, &block). -# -# Defined for backwards compatibility with earlier versions of timeout.rb, see -# Timeout#timeout. - -def timeout(n, e = nil, &block) # :nodoc: - Timeout::timeout(n, e, &block) +def timeout(*args, &block) + warn "Object##{__method__} is deprecated, use Timeout.timeout instead.", uplevel: 1 + Timeout.timeout(*args, &block) end -## # Another name for Timeout::Error, defined for backwards compatibility with # earlier versions of timeout.rb. - -TimeoutError = Timeout::Error # :nodoc: - -if __FILE__ == $0 - p timeout(5) { - 45 - } - p timeout(5, TimeoutError) { - 45 - } - p timeout(nil) { - 54 - } - p timeout(0) { - 54 - } - p timeout(5) { - loop { - p 10 - sleep 1 - } - } +TimeoutError = Timeout::Error +class Object + deprecate_constant :TimeoutError end - |
