2011-11-28 10 views
21

que tienen un tiempo de espera de rubí que llama a un comando del sistema (bash) como esto ..rubí tiempos de espera y comandos del sistema

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

pero creo que incluso si el hilo se interrumpe el rubí, el comando real sigue funcionando en el fondo ... ¿es normal? ¿Cómo puedo matarlo?

+0

eso no es verdad. El subcuadro que ejecuta el comando debe terminar cuando finaliza el proceso primario de ruby. Por favor da un ejemplo más específico. –

+2

@BenLee: el proceso principal no termina cuando expira el tiempo de espera. –

+0

@ MladenJablanović, en un experimento rápido que hace. Creé un archivo ruby ​​que no hizo más que: 'require 'timeout'; Timeout :: timeout (100) {'sleep 500'}'. Mientras lo ejecuto, hago 'ps aux | grep sleep' y ver el proceso de dormir. Luego envío SIGKILL al proceso de ruby, y nuevamente ejecuto 'ps aux | grep sleep' y ya no ve el proceso secundario. –

Respuesta

30

Yo creo que hay que kill manualmente:

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

Esto es diferente del ejemplo porque use está usando 'Process.spawn'. Con ese comando, el proceso hijo * no * finaliza cuando lo hace el proceso principal. Tampoco interrumpe la ejecución de la aplicación mientras espera que vuelva el subproceso; lo ejecuta en paralelo. Pero cuando se utilizan los backticks (o 'exec'), el proceso principal espera a que vuelva el subproceso y elimina el subproceso si el proceso principal finaliza. –

+1

Er .. El OP no termina el proceso principal en absoluto. El problema es si una excepción generada por 'timeout' termina el proceso hijo o no (y no lo hace). –

+0

tienes razón, no entiendo bien la pregunta del OP. Cambié mi voto de voto a voto vivo. (inicialmente no me dejaba cambiarlo diciendo "Tu voto ahora está bloqueado a menos que se edite esta respuesta", así que hice una edición rápida simplemente añadiendo un solo carácter en blanco, sin cambiar ningún contenido). –

10

el fin de detener adecuadamente árbol de proceso generado (no sólo el proceso padre) se debe considerar algo como esto:

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 

este también le permite rastrear el estado del proceso

+1

Creo que esto solo funcionará con ruby ​​1.9 – CantGetANick

+0

La eliminación del árbol es importante y el código que lo hace probablemente sea la respuesta a esta pregunta. Dos puntos con respecto a su solución: Process :: kill docs dicen que _signal_ debería ser negativo para matar a un grupo de procesos (su código tiene el ID del grupo de procesos como negativo). Además, Process :: spawn no parece tener bloques de código, lo que lo hace menos conveniente. Aún así, creo que estás yendo en la dirección correcta. – Ray

+0

Creo que Process.kill (15, -Process.getpgid (pid)) == Process.kill (-15, pid), no recuerdo dónde lo leí (podría estar equivocado, por supuesto). Lo importante aquí es: pgroup => verdadero – shurikk

6

Quizás esto ayude a alguien más que busque lograr una funcionalidad de tiempo de espera similar, pero necesita recolectar el salida del comando de shell.

He adaptado el método de @ shurikk para trabajar con Ruby 2.0 y algunos códigos de Fork child process with timeout and capture output para recopilar la salida.

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

Creo que con las variantes 'Open3.capture *' será más fácil. Supongo que celean-up después de sí en los tiempos de espera. – akostadinov

1

El manejo de procesos, señales y temporizadores no es muy fácil. Es por eso que es posible pensar en delegar esta tarea: Utilice el comando timeout en las nuevas versiones de Linux:

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

en mi sistema, la sintaxis es 'timeout ': así por ejemplo 'timeout 10s my_bash_command'. La opción '--kill-after' es específica a la cantidad de tiempo que se debe esperar después de haber enviado el término señal. Al usar esta opción, aún necesita especificar la duración original: 'timeout --kill-after = 5s 10s my_bash_command'. – yves

Cuestiones relacionadas