Catturare Ctrl-c in ruby


107

Mi è stato approvato un programma ruby ​​legacy di lunga durata, che ha numerose occorrenze di

begin
  #dosomething
rescue Exception => e
  #halt the exception's progress
end

in tutto esso.

Senza rintracciare ogni singola possibile eccezione che ciascuno di essi potrebbe gestire (almeno non immediatamente), mi piacerebbe comunque essere in grado di spegnerlo a volte con CtrlC.

E mi piacerebbe farlo in un modo che si aggiunga solo al codice (quindi non influisco sul comportamento esistente o perdo un'eccezione altrimenti rilevata nel bel mezzo di una corsa).

[ CtrlCè SIGINT, o SystemExit, che sembra essere equivalente al SignalException.new("INT")sistema di gestione delle eccezioni di Ruby. class SignalException < Exception, motivo per cui si presenta questo problema.]

Il codice che vorrei scrivere sarebbe:

begin
  #dosomething
rescue SignalException => e
  raise e
rescue Exception => e
  #halt the exception's progress
end

EDIT: questo codice funziona, purché si ottenga la classe dell'eccezione che si desidera intercettare correttamente. Può essere SystemExit, Interrupt o IRB :: Abort come di seguito.

Risposte:


132

Il problema è che quando un programma Ruby finisce, lo fa alzando SystemExit . Quando entra un control-C, solleva Interrupt . Poiché sia SystemExit che Interrupt derivano da Exception , la gestione delle eccezioni interrompe l'uscita o l'interrupt nelle sue tracce. Ecco la soluzione:

Ovunque puoi, cambia

rescue Exception => e
  # ...
end

per

rescue StandardError => e
  # ...
end

per quelli che non puoi modificare in StandardError, solleva nuovamente l'eccezione:

rescue Exception => e
  # ...
  raise
end

o, per lo meno, rilanciare SystemExit e Interrupt

rescue SystemExit, Interrupt
  raise
rescue Exception => e
  #...
end

Qualsiasi eccezione personalizzata che hai fatto dovrebbe derivare da StandardError , non da Exception .


1
Wayne, saresti così gentile da aggiungere anche un esempio IRB :: Abort alla tua lista?
Tim Snowhite

1
@Tim, vai a trovare irb.rb (sul mio sistema, è in /usr/lib/ruby/1.8/irb.rb) e trova il ciclo principale (cerca @ context.evaluate). Guarda le clausole di salvataggio e penso che capirai perché IRB si sta comportando in quel modo.
Wayne Conrad

grazie. Anche guardare la definizione di #signal_handle in irb.rb ha aiutato la mia comprensione. Hanno anche un bel trucco all'interno del ciclo principale, l'associazione della variabile di eccezione. (Utilizzando le clausole di salvataggio come un modo per individuare un'eccezione specifica, quindi utilizzando tale eccezione al di fuori dei corpi di soccorso.)
Tim Snowhite,

queste opere perfette:rescue SystemExit, Interrupt raise rescue Exception => e
James Tan

73

Se riesci a concludere l'intero programma, puoi fare qualcosa come il seguente:

 trap("SIGINT") { throw :ctrl_c }

 catch :ctrl_c do
 begin
    sleep(10)
 rescue Exception
    puts "Not printed"
 end
 end

Questo fondamentalmente CtrlCusa catch / throw invece della gestione delle eccezioni, quindi a meno che il codice esistente non abbia già un catch: ctrl_c, dovrebbe andare bene.

In alternativa puoi fare un file trap("SIGINT") { exit! }. exit!esce immediatamente, non solleva un'eccezione, quindi il codice non può catturarlo accidentalmente.


2
Nota che Ctrl-C in IRB invia IRB :: Abort, non SIGINT. Altrimenti la risposta di @ Logan è una soluzione.
Tim Snowhite

1
@TimSnowhite per ruby ​​interpreter SIGINTfunziona bene per me.
defhlt

1
throw e catch devono essere sullo stesso thread, quindi questo non funzionerà se si desidera catturare l'eccezione Interrupt su un altro thread.
Matt Connolly

39

Se non riesci a racchiudere l'intera applicazione in un begin ... rescueblocco (ad esempio, Thor) puoi semplicemente intercettare SIGINT:

trap "SIGINT" do
  puts "Exiting"
  exit 130
end

130 è un codice di uscita standard.


1
Cordiali saluti, 130 è il codice di uscita corretto per gli script interrotti Ctrl-C: google.com/search?q=130+exit+code&en= ( 130 | Script terminated by Control-C | Ctl-C | Control-C is fatal error signal 2, (130 = 128 + 2, see above))
Dorian

Perfetto! Ho un server Sinatra instabile con un thread in background costantemente in esecuzione, e questo sembra quello che mi serve per uccidere anche il thread su un cntrl-c, senza altrimenti cambiare il comportamento.
Narfanator

4

Sto usando ensurecon grande effetto! Questo è per le cose che vuoi che accadano quando le tue cose finiscono, non importa perché finiscono.


0

Gestire Ctrl-C in modo pulito in Ruby in modo ZeroMQ:

#!/usr/bin/env ruby

# Shows how to handle Ctrl-C
require 'ffi-rzmq'

context = ZMQ::Context.new(1)
socket = context.socket(ZMQ::REP)
socket.bind("tcp://*:5558")

trap("INT") { puts "Shutting down."; socket.close; context.terminate; exit}

puts "Starting up"

while true do
  message = socket.recv_string
  puts "Message: #{message.inspect}"
  socket.send_string("Message received")
end

fonte


Un bell'esempio, ma penso che aggiunga più complessità di quella effettivamente necessaria nel contesto di OP.
Ron Klein
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.