Capturando Ctrl-c em ruby

107

Eu fui aprovado em um programa de ruby ​​antigo, que tem inúmeras ocorrências de

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

em todo ele.

Sem rastrear todas as exceções possíveis com que cada um deles poderia lidar (pelo menos não imediatamente), eu ainda gostaria de poder desligá-lo às vezes com CtrlC.

E eu gostaria de fazer isso de uma forma que apenas acrescente ao código (para não afetar o comportamento existente, ou perder uma exceção detectada no meio de uma execução).

[ CtrlCé SIGINT, ou SystemExit, que parece ser equivalente ao SignalException.new("INT")sistema de tratamento de exceções do Ruby. class SignalException < Exception, é por isso que esse problema surge.]

O código que eu gostaria de ter escrito seria:

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

EDIT: Este código funciona, contanto que você obtenha a classe da exceção que deseja corrigir com interceptação. Pode ser SystemExit, Interrupt ou IRB :: Abort conforme abaixo.

Tim Snowhite
fonte

Respostas:

132

O problema é que, quando um programa Ruby termina, ele o faz ativando SystemExit . Quando um controle-C entra, ele gera Interrupção . Uma vez que SystemExit e Interrupt derivam de exceção , seu tratamento de exceção está parando a saída ou interrupção em suas trilhas. Aqui está a solução:

Sempre que puder, mude

rescue Exception => e
  # ...
end

para

rescue StandardError => e
  # ...
end

para aqueles que não podem ser alterados para StandardError, aumente novamente a exceção:

rescue Exception => e
  # ...
  raise
end

ou, pelo menos, aumente novamente SystemExit e Interrupt

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

Todas as exceções personalizadas que você fez devem derivar de StandardError , não Exception .

Wayne Conrad
fonte
1
Wayne, você poderia fazer a gentileza de adicionar um exemplo IRB :: Abort à sua lista também?
Tim Snowhite
1
@Tim, vá encontrar irb.rb (no meu sistema, está em /usr/lib/ruby/1.8/irb.rb) e encontre o loop principal (pesquise @ context.evaluate). Veja as cláusulas de resgate e acho que você entenderá por que o IRB está se comportando dessa maneira.
Wayne Conrad
obrigado. Olhar para a definição de #signal_handle no irb.rb também ajudou a minha compreensão. Eles também têm um truque bacana na ligação da variável de exceção do loop principal. (Usando as cláusulas de resgate como uma forma de escolher uma exceção específica e, em seguida, usando essa exceção fora dos corpos de resgate.)
Tim Snowhite
estes funcionam perfeitamente:rescue SystemExit, Interrupt raise rescue Exception => e
James Tan
73

Se você pode embrulhar todo o seu programa, você pode fazer algo como o seguinte:

 trap("SIGINT") { throw :ctrl_c }

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

Basicamente, ele CtrlCusa catch / throw ao invés de manipulação de exceção, então, a menos que o código existente já tenha um catch: ctrl_c nele, ele deve funcionar.

Alternativamente, você pode fazer a trap("SIGINT") { exit! }. exit!sai imediatamente, ele não levanta uma exceção, portanto, o código não pode detectá-lo acidentalmente.

Logan Capaldo
fonte
2
Observe que Ctrl-C no IRB envia IRB :: Abort, não SIGINT. Caso contrário, a resposta de @ Logan é uma solução.
Tim Snowhite
1
@TimSnowhite para interpretador de ruby SIGINTfunciona bem para mim.
defhlt
1
throw e catch devem estar no mesmo thread, então isso não funcionará se você quiser capturar a exceção Interrupt em outro thread.
Matt Connolly
39

Se você não pode agrupar todo o seu aplicativo em um begin ... rescuebloco (por exemplo, Thor), você pode apenas capturar SIGINT:

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

130 é um código de saída padrão.

Erik Nomitch
fonte
1
Para sua informação, 130 é o código de saída correto para scripts interrompidos com 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
Perfeito! Eu tenho um servidor Sinatra instável com um thread de segundo plano em execução constante e isso parece o que eu preciso para encerrar o thread também em um cntrl-c, sem alterar o comportamento de outra forma.
Narfanator
4

Estou usando ensurecom grande efeito! Isso é para coisas que você deseja que aconteçam quando suas coisas terminarem, não importa por que acabem.

nroose
fonte
0

Manuseando Ctrl-C de forma limpa em Ruby da maneira 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

Noraj
fonte
Bom exemplo, mas acho que adiciona mais complexidade do que o necessário no contexto do OP.
Ron Klein,