You can clone with
# Runs a specified shell command in a separate thread.
# If it exceeds the given timeout in seconds, kills it.
# Returns any output produced by the command (stdout or stderr) as a String.
# 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, timeout, tick)
output = ''
# Start task in another thread, which spawns a process
stdin, stderrout, thread = Open3.popen2e(command)
# Get the pid of the spawned process
pid = thread[:pid]
start = Time.now
while (Time.now - start) < timeout and thread.alive?
# Wait up to `tick` seconds for output/error data
Kernel.select([stderrout], nil, nil, tick)
# Try to read the data
output << stderrout.read_nonblock(BUFFER_SIZE)
# A read would block, so loop around for another select
# Command has completed, not really an error...
# Give Ruby time to clean up the other thread
# We need to kill the process, because killing the thread leaves
# the process alive but detached, annoyingly enough.
stdin.close if stdin
stderrout.close if stderrout
You saved my life just now, thanks :).
Do you think there is still not another way of doing this ? Using ruby 1.9.3p125 and Timeout is still incompatible with Open3
The advice not to use Timeout with Open3 came from the developers on ruby-core, so I doubt it's going to be fixed any time soon. It would be nice if there was an easier way to handle timeouts when sub-processes are involved, but given the issues involved on various platforms I can see why a general solution is difficult.
Yeah I understand .
But we're in 2012. Handling sub-processes and timeouts should be easier above all with high level languages like Ruby. Maybe ruby-core developers should put a higher priority on this problem.
What should BUFFER_SIZE be defined to?
Almost any value for BUFFER_SIZE > 1000 should be fine, since if there is data to be read in excess of your BUFFER_SIZE, you read a chunk of it then immediately loop back and read more data, so it doesn't seem like this will significantly affect the time spent vis-a-vis the timeout. But still pick a value that will work best for the external application you're calling. If you just want a number, try 4096 (4KiB).
P.S. lpar, thanks for this!