1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
require 'timeout'
module Timeout
# A forking timeout implementation that relies on
# signals to interrupt blocking I/O instead of passing
# that code to run in a separate process.
#
# A process is fork()ed, sleeps for _sec_,
# then sends a ALRM signal to the Process.ppid
# process. ALRM is used to avoid conflicts with sleep()
#
# This overwrites any signal
def Timeout.alarm(sec, exception=Timeout::Error, &block)
return block.call if sec == nil or sec.zero?
# Trap an alarm in case it comes before we're ready
orig_alrm = trap(:ALRM, 'IGNORE')
# Setup a fallback in case of a race condition of an
# alarm before we set the other trap
trap(:ALRM) do
# Don't leave zombies
Process.wait2()
# Restore the original handler
trap('ALRM', orig_alrm)
# Now raise an exception!
raise exception, 'execution expired'
end
# Spawn the sleeper
pid = Process.fork {
begin
# Sleep x seconds then send SIGALRM
sleep(sec)
# Send alarm!
Process.kill(:ALRM, Process.ppid)
end
exit! 0
}
# Setup the real handler
trap(:ALRM) do
# Make sure we clean up any zombies
Process.waitpid(pid)
# Restore the original handler
trap(:ALRM, orig_alrm)
# Now raise an exception!
raise exception, 'execution expired'
end
begin
# Run the code!
return block.call
ensure
# Restore old alarm handler since we're done
trap(:ALRM, orig_alrm)
# Make sure the process is dead
# This may be run twice (trap occurs during execution) so ignore ESRCH
Process.kill(:TERM, pid) rescue Errno::ESRCH
# Don't leave zombies
Process.waitpid(pid) rescue Errno::ECHILD
end
end
end # Timeout
if __FILE__ == $0
require 'time'
Timeout.alarm(2) do
loop do
p Time.now
end
end
end
|