2011-11-28 4 views
21

У меня есть тайм-аут рубина, который вызывает команду системы (Баш), как это ..рубина аутов и системные команды

Timeout::timeout(10) { 
    `my_bash_command -c12 -o text.txt` 
} 

, но я думаю, что даже если рубин нить прерывается, фактическая команда продолжает работать в фон .. это нормально? Как я могу его убить?

+0

Это неправда. Sub-shell, выполняющий команду, должен завершиться, когда родительский рубиновый процесс завершается. Пожалуйста, дайте более конкретный пример. –

+2

@BenLee: родительский процесс не заканчивается, когда истекает время ожидания. –

+0

@ MladenJablanović, в быстром эксперименте. Я создал файл ruby, который ничего не делал: 'require 'timeout'; Таймаут :: таймаут (100) {'sleep 500'}'. Во время запуска я делаю 'ps aux | grep sleep' и посмотреть процесс сна. Затем я отправляю SIGKILL в рубиновый процесс и снова запускаю 'ps aux | grep sleep' и больше не видят дочерний процесс. –

ответ

30

Я думаю, вы должны kill вручную:

require 'timeout' 

puts 'starting process' 
pid = Process.spawn('sleep 20') 
begin 
    Timeout.timeout(5) do 
    puts 'waiting for the process to end' 
    Process.wait(pid) 
    puts 'process finished in time' 
    end 
rescue Timeout::Error 
    puts 'process not finished in time, killing it' 
    Process.kill('TERM', pid) 
end 
+1

Это отличается от примера, потому что вы используете 'Process.spawn'. С помощью этой команды дочерний процесс * не заканчивается, когда основной процесс выполняется. Он также не останавливает выполнение приложения, ожидая возврата подпроцесса; он запускает его параллельно. Но при использовании backticks (или 'exec') основной процесс ожидает возвращения подпроцесса и убивает подпроцесс, если основной процесс завершен. –

+1

Er .. OP не прекращает основной процесс вообще. Проблема заключается в том, что исключение, вызванное 'timeout', завершает дочерний процесс или нет (а это не так). –

+0

Вы правы Я неправильно понял вопрос OP. Изменил мой голос с нисходящего потока на верх. (изначально это не позволило мне изменить его, сказав: «Ваш голос теперь заблокирован, если этот ответ не отредактирован», поэтому я сделал быстрое редактирование, просто добавив один символ пробела, не меняя никакого содержимого). –

10

для того, чтобы должным образом остановить порожденный процесс дерева (не только родительский процесс) следует рассмотреть что-то вроде этого:

def exec_with_timeout(cmd, timeout) 
    pid = Process.spawn(cmd, {[:err,:out] => :close, :pgroup => true}) 
    begin 
    Timeout.timeout(timeout) do 
     Process.waitpid(pid, 0) 
     $?.exitstatus == 0 
    end 
    rescue Timeout::Error 
    Process.kill(15, -Process.getpgid(pid)) 
    false 
    end 
end 

это также позволяет отслеживать состояние процесса

+1

Я думаю, что это будет работать только с ruby ​​1.9 – CantGetANick

+0

Убивать дерево важно, и код, который делает это, вероятно, должен быть в целом ответом на этот вопрос. Два момента относительно вашего решения: Process :: kill docs говорят, что _signal_ должен быть отрицательным, чтобы убить группу процессов (ваш код имеет идентификатор группы процессов как отрицательный). Кроме того, Process :: spawn, похоже, не принимает блоки кода, что делает его менее удобным. Тем не менее, я думаю, вы идете в правильном направлении. – Ray

+0

Я думаю, что Process.kill (15, -Process.getpgid (pid)) == Process.kill (-15, pid), я не помню, где я читал об этом (может быть, это неправильно). Важная вещь здесь: pgroup => true – shurikk

6

Возможно, это поможет кому-то еще надеяться на достижение аналогичных функций тайм-аута, но необходимо собрать вывод из команды оболочки.

Я адаптировал метод @ shurikk для работы с Ruby 2.0 и некоторый код от Fork child process with timeout and capture output для сбора результатов.

def exec_with_timeout(cmd, timeout) 
    begin 
    # stdout, stderr pipes 
    rout, wout = IO.pipe 
    rerr, werr = IO.pipe 
    stdout, stderr = nil 

    pid = Process.spawn(cmd, pgroup: true, :out => wout, :err => werr) 

    Timeout.timeout(timeout) do 
     Process.waitpid(pid) 

     # close write ends so we can read from them 
     wout.close 
     werr.close 

     stdout = rout.readlines.join 
     stderr = rerr.readlines.join 
    end 

    rescue Timeout::Error 
    Process.kill(-9, pid) 
    Process.detach(pid) 
    ensure 
    wout.close unless wout.closed? 
    werr.close unless werr.closed? 
    # dispose the read ends of the pipes 
    rout.close 
    rerr.close 
    end 
    stdout 
end 
+0

Я думаю, что с вариантами 'Open3.capture * 'будет проще. Я предполагаю, что он будет сидеть после себя по таймаутам. – akostadinov

1

Обработка процессов, сигналов и таймеров не очень проста. Вот почему вы можете рассмотреть делегирование этой задачи: используйте команду timeout в новых версиях Linux:

timeout --kill-after 5s 10s my_bash_command -c12 -o text.txt 
+1

в моей системе синтаксис 'timeout ': так, например, 'timeout 10s my_bash_command'. Опция '--kill-after', зависит от количества времени ожидания после отправки сигнала термина. Используя эту опцию, вам все равно нужно указать начальную продолжительность: 'timeout --kill-after = 5s 10s my_bash_command'. – yves

Смежные вопросы