Capturando Ctrl-c en ruby

107

Me aprobaron un programa ruby ​​heredado de larga duración, que tiene numerosas apariciones de

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

a lo largo de ella.

Sin rastrear todas y cada una de las posibles excepciones que cada uno podría manejar (al menos no de inmediato), aún me gustaría poder cerrarlo a veces con CtrlC.

Y me gustaría hacerlo de una manera que solo se agregue al código (para que no afecte el comportamiento existente o pierda una excepción detectada en medio de una ejecución).

[ CtrlCes SIGINT, o SystemSalir, que parece ser equivalente al SignalException.new("INT")sistema de manejo de excepciones de Ruby. class SignalException < Exception, por eso surge este problema.]

El código que me gustaría haber escrito sería:

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

EDITAR: Este código funciona, siempre que obtenga la clase de la excepción que desea capturar correctamente. Eso es SystemSalir, Interrupción o IRB :: Abort como se muestra a continuación.

Tim Snowhite
fuente

Respuestas:

132

El problema es que cuando finaliza un programa de Ruby, lo hace activando SystemSalir . Cuando entra un control-C, genera Interrupción . Dado que tanto la salida del sistema como la interrupción se derivan de la excepción , su manejo de excepciones es detener la salida o la interrupción en seco. Aquí está la solución:

Donde puedas, cambia

rescue Exception => e
  # ...
end

a

rescue StandardError => e
  # ...
end

para aquellos que no puede cambiar a StandardError, vuelva a generar la excepción:

rescue Exception => e
  # ...
  raise
end

o, como mínimo, vuelva a subir SystemSalir e Interrupción

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

Cualquier excepción personalizada que haya realizado debe derivar de StandardError , no de Exception .

Wayne Conrad
fuente
1
Wayne, ¿sería tan amable de agregar un ejemplo de IRB :: Abortar a su lista también?
Tim Snowhite
1
@Tim, busque irb.rb (en mi sistema, está en /usr/lib/ruby/1.8/irb.rb) y busque el bucle principal (busque @ context.evaluate). Mire las cláusulas de rescate y creo que comprenderá por qué IRB se está comportando como lo hace.
Wayne Conrad
gracias. Mirar la definición de #signal_handle en irb.rb también ayudó a mi comprensión. También tienen un buen truco dentro del bucle principal: el enlace de la variable de excepción. (Usar las cláusulas de rescate como una forma de seleccionar una excepción específica, luego usar esa excepción fuera de los cuerpos de rescate).
Tim Snowhite
estos trabajos son perfectos:rescue SystemExit, Interrupt raise rescue Exception => e
James Tan
73

Si puede envolver todo su programa, puede hacer algo como lo siguiente:

 trap("SIGINT") { throw :ctrl_c }

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

Esto básicamente tiene CtrlCuso catch / throw en lugar de manejo de excepciones, por lo que a menos que el código existente ya tenga un catch: ctrl_c, debería estar bien.

Alternativamente, puede hacer un trap("SIGINT") { exit! }. exit!sale inmediatamente, no genera una excepción por lo que el código no puede atraparlo accidentalmente.

Logan Capaldo
fuente
2
Tenga en cuenta que Ctrl-C en IRB envía IRB :: Abort, no SIGINT. De lo contrario, la respuesta de @ Logan es una solución.
Tim Snowhite
1
@TimSnowhite para el intérprete de ruby SIGINTfunciona bien para mí.
defhlt
1
throw y catch deben estar en el mismo hilo, por lo que esto no funcionará si desea capturar la excepción de interrupción en otro hilo.
Matt Connolly
39

Si no puede envolver toda su aplicación en un begin ... rescuebloque (por ejemplo, Thor), puede simplemente atrapar SIGINT:

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

130 es un código de salida estándar.

Erik Nomitch
fuente
1
Para su información, 130 es el código de salida correcto para las secuencias de comandos interrumpidas 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
¡Perfecto! Tengo un servidor Sinatra inestable con un subproceso en segundo plano que se ejecuta constantemente, y esto parece lo que necesito para matar el subproceso también en un cntrl-c, sin cambiar el comportamiento de otro modo.
Narfanator
4

¡Estoy usando ensurecon gran efecto! Esto es para las cosas que desea que sucedan cuando sus cosas terminen, sin importar por qué terminen.

nroose
fuente
0

Manejar Ctrl-C limpiamente en Ruby al estilo 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

Fuente

Noraj
fuente
Buen ejemplo, pero creo que agrega más complejidad de la que realmente se necesita en el contexto de OP.
Ron Klein