Skip to content

Instantly share code, notes, and snippets.

@kriansa
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kriansa/6de78a3fc6eb3e2013d4 to your computer and use it in GitHub Desktop.
Save kriansa/6de78a3fc6eb3e2013d4 to your computer and use it in GitHub Desktop.
Tiny helper to execute a external command with a timeout.
# Open stdout and stderr streams and start external executable with timeout.
# The timeout is given in seconds.
#
# Block form:
#
# exec_with_timeout(timeout, [env,] cmd... [, opts]) {|stdout, stderr|
# stdout.read
# ...
# }
#
# Non-block form:
#
# stdout, stderr = exec_with_timeout(timeout, [env,] cmd... [, opts])
# ...
# stdout.close # stdout and stderr should be closed explicitly in this form.
# stderr.close
#
# The parameters +cmd...+ is passed to Process.spawn.
# So a commandline string and list of argument strings can be accepted as follows.
#
# exec_with_timeout("echo a") {|stdout, stderr| ... }
# exec_with_timeout("echo", "a") {|stdout, stderr| ... }
# exec_with_timeout(["echo", "argv0"], "a") {|stdout, stderr| ... }
#
# If the last parameter, opts, is a Hash, it is recognized as an option for Process.spawn.
#
# exec_with_timeout("pwd", :chdir=>"/") {|i,o,e,t|
# p o.read.chomp #=> "/"
# }
#
# If the process takes longer than +timeout+, it will be killed and a Timeout::Error
# exception will be raised.
#
def exec_with_timeout(timeout = 5, *args)
err_read, err_write = IO.pipe
out_read, out_write = IO.pipe
pipes = {
out: out_write,
err: err_write,
}
if args.last.is_a?(Hash)
args.last.merge!(pipes)
else
args << pipes
end
pid = Process.spawn(*args)
begin
Timeout.timeout(timeout) do
Process.wait(pid)
# Close the pipes to be able to read
err_write.close
out_write.close
# Exec the block or return the read content
if block_given?
yield out_read, err_read
else
[out_read, err_read]
end
end
rescue Timeout::Error => e
Process.kill('TERM', pid)
Process.wait(pid)
raise e
ensure
# Close the pipes
out_write.close unless out_write.closed?
err_write.close unless err_write.closed?
out_read.close if block_given? and !out_read.closed?
err_read.close if block_given? and !err_read.closed?
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment