Skip to content

Instantly share code, notes, and snippets.

@mgarrick
Forked from lpar/timeout.rb
Created July 13, 2012 23:22
Show Gist options
  • Save mgarrick/3108185 to your computer and use it in GitHub Desktop.
Save mgarrick/3108185 to your computer and use it in GitHub Desktop.
Run a shell command in a separate thread, terminate it after a time limit, return its output
# 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
@cofiem
Copy link

cofiem commented Aug 12, 2014

I've been using this solution for a while, and it's been working pretty well. Unfortunately, I've recently been ending up with no stdout, when I know there has been stdout. Stderr seems to work fine. Any hints for a reason or solution? I'm trying out upping the tick and cleanup_sleep values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment