2011-11-28 12 views
21

Ho un timeout rubino che chiama un comando di sistema (bash) simili ..rubino timeout e comandi di sistema

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

ma penso che anche se il filo rubino è interrotto, il comando effettivo continua a funzionare in lo sfondo .. è normale? Come posso ucciderlo?

+0

che non è vero. La shell secondaria che esegue il comando dovrebbe terminare quando termina il processo ruby ​​parent. Si prega di dare un esempio più specifico. –

+2

@BenLee: il processo padre non termina quando scade il timeout. –

+0

@ MladenJablanović, in un rapido esperimento lo fa. Ho creato un file rubino che non ha fatto altro che: 'richiede 'timeout'; Timeout :: timeout (100) {'sleep 500'}'. Mentre lo eseguo, eseguo 'ps aux | grep sleep' e vedi il processo del sonno. Quindi invio SIGKILL al processo di ruby, e di nuovo eseguo 'ps aux | grep sleep' e non vede più il processo figlio. –

risposta

30

io penso che si debba 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

Questo è diverso dall'esempio perché si usa 'Process.spawn'. Con quel comando, il processo figlio * non termina quando il processo principale lo fa. Inoltre, non interrompe l'esecuzione dell'applicazione in attesa del ritorno del sottoprocesso; lo esegue in parallelo. Ma quando si usano backtick (o 'exec'), il processo principale attende che il sottoprocesso ritorni e uccide il sottoprocesso se il processo principale è terminato. –

+1

Er .. L'OP non termina affatto il processo principale. Il problema è se un'eccezione sollevata da 'timeout' termina il processo figlio o meno (e non lo fa). –

+0

hai ragione ho frainteso la domanda dell'OP. Modificato il mio voto da downvote a upvot. (inizialmente non mi permetteva di cambiarlo dicendo "Il tuo voto è ora bloccato a meno che questa risposta non venga modificata" quindi ho fatto una modifica rapida aggiungendo semplicemente un singolo carattere di spaziatura, senza modificare alcun contenuto). –

10

al fine di arrestare correttamente albero processo generato (non solo il processo padre) si dovrebbe prendere in considerazione qualcosa di simile:

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 

questo consente inoltre di monitorare lo stato del processo

+1

Penso che funzionerà solo con Ruby 1.9 – CantGetANick

+0

Uccidere l'albero è importante e il codice che lo fa dovrebbe probabilmente essere generalmente la risposta a questa domanda. Due punti relativi alla soluzione: Process :: kill docs dice che _signal_ deve essere negativo per uccidere un gruppo di processi (il codice ha l'ID del gruppo di processi come negativo). Inoltre, Process :: spawn non sembra prendere blocchi di codice, il che lo rende meno conveniente. Comunque, penso che tu stia andando nella giusta direzione. – Ray

+0

Penso che Process.kill (15, -Process.getpgid (pid)) == Process.kill (-15, pid), non ricordo dove ho letto a riguardo (potrebbe essere sbagliato, ovviamente). La cosa importante qui è: pgroup => true – shurikk

6

Forse questo aiuterà qualcun altro a cercare di ottenere funzionalità di timeout simile, ma deve raccogliere il uscita dal comando shell.

Ho adattato il metodo di @ shurikk per lavorare con Ruby 2.0 e un codice da Fork child process with timeout and capture output per raccogliere l'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

Penso che con le varianti di 'Open3.capture *' sarà più facile. Presumo che sarà celean-up dopo se stesso in timeout. – akostadinov

1

Gestione di processi, segnali e timer non è molto facile. È per questo che si potrebbe considerare delegare questo compito: Utilizzare il comando timeout su nuove versioni di Linux:

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

sul mio sistema la sintassi è 'timeout ': così per esempio 'timeout 10s my_bash_command'. L'opzione '--kill-after' è specifica per il tempo di attesa dopo aver inviato il segnale del termine. Usando questa opzione, devi comunque specificare la durata originale: 'timeout --kill-after = 5s 10s my_bash_command'. – yves

Problemi correlati