Skip to content

Instantly share code, notes, and snippets.

@mmalinin
Forked from mgarrick/timeout.rb
Created July 9, 2014 21:21
Show Gist options
  • Save mmalinin/dfc8e5e16e7ba3231132 to your computer and use it in GitHub Desktop.
Save mmalinin/dfc8e5e16e7ba3231132 to your computer and use it in GitHub Desktop.
# Runs a specified shell command in a separate thread.
# If it exceeds the given timeout in seconds, kills it.
# Passes stdout, stderr, thread, and a boolean indicating a timeout occurred to the passed in block.
# Uses Kernel.select to wait up to the tick length (in seconds) between
# checks on the command's status
#
# If you've got a cleaner way of doing this, I'd be interested to see it.
# If you think you can do it with Ruby's Timeout module, think again.
def run_with_timeout(*command)
options = command.extract_options!.reverse_merge(timeout: 60, tick: 1, cleanup_sleep: 0.1, buffer_size: 10240)
timeout = options[:timeout]
cleanup_sleep = options[:cleanup_sleep]
tick = options[:tick]
buffer_size = options[:buffer_size]
output = ''
error = ''
# Start task in another thread, which spawns a process
Open3.popen3(*command) do |stdin, stdout, stderr, thread|
# Get the pid of the spawned process
pid = thread[:pid]
start = Time.now
time_remaining = nil
while (time_remaining = (Time.now - start) < timeout) and thread.alive?
# Wait up to `tick` seconds for output/error data
readables, writeables, = Kernel.select([stdout, stderr], nil, nil, tick)
next if readables.blank?
readables.each do |readable|
stream = readable == stdout ? output : error
begin
stream << readable.read_nonblock(buffer_size)
rescue IO::WaitReadable, EOFError => e
# Need to read all of both streams
# Keep going until thread dies
end
end
end
# Give Ruby time to clean up the other thread
sleep cleanup_sleep
if thread.alive?
# We need to kill the process, because killing the thread leaves
# the process alive but detached, annoyingly enough.
Process.kill("TERM", pid)
end
yield output, error, thread, !time_remaining
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment