Вот лучшее, что у меня есть для запуска подпроцессов.Я запускаю много сетевых команд, поэтому мне нужен был способ их тайм-аута, если они возвращаются слишком долго.Это должно быть удобно в любой ситуации, когда вы хотите контролировать свой путь выполнения.
Я адаптировал это из Gist , добавив код для проверки состояния выхода команды в течение 3результаты:
- Успешное завершение (состояние выхода 0)
- Ошибка завершения (состояние выхода не равно нулю) - возникает исключение
- Команда истекла и была убита- вызывает исключение
Также исправлено условие гонки, упрощены параметры, добавлено еще несколько комментариев и добавлен код отладки, чтобы помочь мне понять, что происходит с выходами и сигналами.
Вызовите функцию следующим образом:
output = run_with_timeout("command that might time out", 15)
выход будет содержать объединенные STDOUT и STDERR команды, если она завершится успешно.Если команда не завершится в течение 15 секунд, она будет уничтожена и сгенерировано исключение.
Вот функция (2 константы, которые вам нужно определить вверху):
DEBUG = false # change to true for some debugging info
BUFFER_SIZE = 4096 # in bytes, this should be fine for many applications
def run_with_timeout(command, timeout)
output = ''
tick = 1
begin
# 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
begin
output << stderrout.read_nonblock(BUFFER_SIZE)
puts "we read some data..." if DEBUG
rescue IO::WaitReadable
# No data was ready to be read during the `tick' which is fine
print "." # give feedback each tick that we're waiting
rescue EOFError
# Command has completed, not really an error...
puts "got EOF." if DEBUG
# Wait briefly for the thread to exit...
# We don't want to kill the process if it's about to exit on its
# own. We decide success or failure based on whether the process
# completes successfully.
sleep 1
break
end
end
if thread.alive?
# The timeout has been reached and the process is still running so
# we need to kill the process, because killing the thread leaves
# the process alive but detached.
Process.kill("TERM", pid)
end
ensure
stdin.close if stdin
stderrout.close if stderrout
end
status = thread.value # returns Process::Status when process ends
if DEBUG
puts "thread.alive?: #{thread.alive?}"
puts "status: #{status}"
puts "status.class: #{status.class}"
puts "status.exited?: #{status.exited?}"
puts "status.exitstatus: #{status.exitstatus}"
puts "status.signaled?: #{status.signaled?}"
puts "status.termsig: #{status.termsig}"
puts "status.stopsig: #{status.stopsig}"
puts "status.stopped?: #{status.stopped?}"
puts "status.success?: #{status.success?}"
end
# See how process ended: .success? => true, false or nil if exited? !true
if status.success? == true # process exited (0)
return output
elsif status.success? == false # process exited (non-zero)
raise "command `#{command}' returned non-zero exit status (#{status.exitstatus}), see below output\n#{output}"
elsif status.signaled? # we killed the process (timeout reached)
raise "shell command `#{command}' timed out and was killed (timeout = #{timeout}s): #{status}"
else
raise "process didn't exit and wasn't signaled. We shouldn't get to here."
end
end
Надеюсь, что это полезно.