1# Timeout long-running blocks 2# 3# == Synopsis 4# 5# require 'timeout' 6# status = Timeout::timeout(5) { 7# # Something that should be interrupted if it takes more than 5 seconds... 8# } 9# 10# == Description 11# 12# Timeout provides a way to auto-terminate a potentially long-running 13# operation if it hasn't finished in a fixed amount of time. 14# 15# Previous versions didn't use a module for namespacing, however 16# #timeout is provided for backwards compatibility. You 17# should prefer Timeout#timeout instead. 18# 19# == Copyright 20# 21# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc. 22# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan 23 24module Timeout 25 # Raised by Timeout#timeout when the block times out. 26 class Error < RuntimeError 27 end 28 class ExitException < ::Exception # :nodoc: 29 end 30 31 # :stopdoc: 32 THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o 33 CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0 34 # :startdoc: 35 36 # Perform an operation in a block, raising an error if it takes longer than 37 # +sec+ seconds to complete. 38 # 39 # +sec+:: Number of seconds to wait for the block to terminate. Any number 40 # may be used, including Floats to specify fractional seconds. A 41 # value of 0 or +nil+ will execute the block without any timeout. 42 # +klass+:: Exception Class to raise if the block fails to terminate 43 # in +sec+ seconds. Omitting will use the default, Timeout::Error 44 # 45 # Returns the result of the block *if* the block completed before 46 # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. 47 # 48 # Note that this is both a method of module Timeout, so you can <tt>include 49 # Timeout</tt> into your classes so they have a #timeout method, as well as 50 # a module method, so you can call it directly as Timeout.timeout(). 51 def timeout(sec, klass = nil) #:yield: +sec+ 52 return yield(sec) if sec == nil or sec.zero? 53 exception = klass || Class.new(ExitException) 54 begin 55 begin 56 x = Thread.current 57 y = Thread.start { 58 begin 59 sleep sec 60 rescue => e 61 x.raise e 62 else 63 x.raise exception, "execution expired" 64 end 65 } 66 return yield(sec) 67 ensure 68 if y 69 y.kill 70 y.join # make sure y is dead. 71 end 72 end 73 rescue exception => e 74 rej = /\A#{Regexp.quote(__FILE__)}:#{__LINE__-4}\z/o 75 (bt = e.backtrace).reject! {|m| rej =~ m} 76 level = -caller(CALLER_OFFSET).size 77 while THIS_FILE =~ bt[level] 78 bt.delete_at(level) 79 level += 1 80 end 81 raise if klass # if exception class is specified, it 82 # would be expected outside. 83 raise Error, e.message, e.backtrace 84 end 85 end 86 87 module_function :timeout 88end 89 90# Identical to: 91# 92# Timeout::timeout(n, e, &block). 93# 94# This method is deprecated and provided only for backwards compatibility. 95# You should use Timeout#timeout instead. 96def timeout(n, e = nil, &block) 97 Timeout::timeout(n, e, &block) 98end 99 100# Another name for Timeout::Error, defined for backwards compatibility with 101# earlier versions of timeout.rb. 102TimeoutError = Timeout::Error 103