Strg-C in Rubin erfassen

107

Mir wurde ein langjähriges Legacy-Ruby-Programm übergeben, das zahlreiche Vorkommen hat

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

überall.

Ohne jede mögliche Ausnahme aufzuspüren, die diese behandeln könnten (zumindest nicht sofort), möchte ich sie trotzdem manchmal herunterfahren können CtrlC.

Und ich möchte dies auf eine Weise tun, die nur zum Code beiträgt (damit ich das vorhandene Verhalten nicht beeinträchtige oder eine ansonsten abgefangene Ausnahme während eines Laufs verpasse.)

[ CtrlCist SIGINT oder SystemExit, was SignalException.new("INT")dem Ausnahmebehandlungssystem von Ruby zu entsprechen scheint . class SignalException < Exception, weshalb dieses Problem auftritt.]

Der Code, den ich gerne geschrieben hätte, wäre:

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

BEARBEITEN: Dieser Code funktioniert, solange Sie die Klasse der Ausnahme erhalten, die Sie korrekt abfangen möchten. Das ist entweder SystemExit, Interrupt oder IRB :: Abort wie unten.

Tim Snowhite
quelle

Antworten:

132

Das Problem ist, dass ein Ruby-Programm beim Beenden SystemExit auslöst . Wenn ein Control-C hereinkommt, wird Interrupt ausgelöst . Da sowohl SystemExit als auch Interrupt von Exception abgeleitet sind , stoppt Ihre Ausnahmebehandlung den Exit oder Interrupt in seinen Spuren. Hier ist das Update:

Wo immer du kannst, ändere dich

rescue Exception => e
  # ...
end

zu

rescue StandardError => e
  # ...
end

Wenn Sie nicht zu StandardError wechseln können, lösen Sie die Ausnahme erneut aus:

rescue Exception => e
  # ...
  raise
end

oder zumindest SystemExit und Interrupt erneut auslösen

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

Alle benutzerdefinierten Ausnahmen, die Sie vorgenommen haben, sollten von StandardError abgeleitet werden , nicht von Exception .

Wayne Conrad
quelle
1
Wayne, wären Sie so freundlich, Ihrer Liste auch ein IRB :: Abort-Beispiel hinzuzufügen?
Tim Snowhite
1
@Tim, suchen Sie irb.rb (auf meinem System in /usr/lib/ruby/1.8/irb.rb) und suchen Sie die Hauptschleife (suchen Sie nach @ context.evaluate). Schauen Sie sich die Rettungsklauseln an und ich denke, Sie werden verstehen, warum sich IRB so verhält, wie es ist.
Wayne Conrad
Danke. Das Betrachten der Definition für #signal_handle in irb.rb half mir auch beim Verständnis. Sie haben einen ordentlichen Trick innerhalb der Hauptschleife, auch die Bindung der Ausnahmevariablen. (Verwenden Sie die Rettungsklauseln, um eine bestimmte Ausnahme auszuwählen, und verwenden Sie diese Ausnahme dann außerhalb der Rettungskörper.)
Tim Snowhite
diese funktioniert perfekt:rescue SystemExit, Interrupt raise rescue Exception => e
James Tan
73

Wenn Sie Ihr gesamtes Programm einpacken können, können Sie Folgendes tun:

 trap("SIGINT") { throw :ctrl_c }

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

Grundsätzlich wird CtrlCanstelle der Ausnahmebehandlung catch / throw verwendet. Sofern der vorhandene Code nicht bereits einen catch: ctrl_c enthält, sollte dies in Ordnung sein.

Alternativ können Sie eine trap("SIGINT") { exit! }. exit!Wird sofort beendet, löst es keine Ausnahme aus, sodass der Code sie nicht versehentlich abfangen kann.

Logan Capaldo
quelle
2
Beachten Sie, dass Strg-C im IRB IRB :: Abort und nicht SIGINT sendet. Ansonsten ist die Antwort von @ Logan eine Lösung.
Tim Snowhite
1
@ TimSnowhite für Ruby Interpreter SIGINTfunktioniert gut für mich.
defhlt
1
throw und catch müssen sich im selben Thread befinden, daher funktioniert dies nicht, wenn Sie die Interrupt-Ausnahme in einem anderen Thread abfangen möchten.
Matt Connolly
39

Wenn Sie nicht Ihre gesamte Anwendung in einen begin ... rescueBlock einschließen können (z. B. Thor), können Sie einfach Folgendes abfangen SIGINT:

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

130 ist ein Standard-Exit-Code.

Erik Nomitch
quelle
1
Zu Ihrer Information, 130 ist der richtige Exit-Code für unterbrochene Strg-C-Skripte: 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
Perfekt! Ich habe einen wackeligen Sinatra-Server mit einem ständig laufenden Hintergrund-Thread, und dies sieht so aus, als würde ich den Thread auch auf einem cntrl-c beenden, ohne das Verhalten anderweitig zu ändern.
Narfanator
4

Ich benutze ensuremit großer Wirkung! Dies ist für Dinge, die passieren sollen, wenn Ihre Sachen enden, egal warum sie enden.

nroose
quelle
0

Sauberer Umgang mit Strg-C in Ruby ZeroMQ-Weise:

#!/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

Quelle

noraj
quelle
Schönes Beispiel, aber ich denke, es erhöht die Komplexität, die im OP-Kontext tatsächlich benötigt wird.
Ron Klein