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.

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 .


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.


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.


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.


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


Buen ejemplo, pero creo que agrega más complejidad de la que realmente se necesita en el contexto de OP.
— Ron Klein
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.