Przechwytywanie Ctrl-c w ruby

107

Przekazano mi długo działający, starszy program rubinowy, który ma wiele wystąpień

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

przez cały czas.

Bez śledzenia każdego możliwego wyjątku, który każdy z nich mógłby obsługiwać (przynajmniej nie od razu), nadal chciałbym móc czasami go wyłączyć CtrlC.

Chciałbym to zrobić w sposób, który tylko dodaje do kodu (więc nie wpływam na istniejące zachowanie ani nie pomijam wychwyconego w inny sposób wyjątku w trakcie wykonywania).

[ CtrlCto SIGINT lub SystemExit, co wydaje się być równoważne z SignalException.new("INT")systemem obsługi wyjątków Rubiego. class SignalException < Exception, dlatego pojawia się ten problem.]

Kod, który chciałbym napisać, wyglądałby tak:

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

EDYCJA: Ten kod działa, o ile uzyskasz klasę wyjątku, który chcesz poprawnie przechwycić. To jest SystemExit, Interrupt lub IRB :: Abort, jak poniżej.

Tim Snowhite
źródło

Odpowiedzi:

132

Problem polega na tym, że kiedy program Ruby się kończy, robi to przez podniesienie SystemExit . Kiedy pojawia się control-C, wywołuje przerwanie . Ponieważ zarówno SystemExit, jak i Interrupt wywodzą się z Exception , twoja obsługa wyjątków zatrzymuje wyjście lub przerwanie w jego ścieżkach. Oto poprawka:

Zmieniaj się, gdzie tylko możesz

rescue Exception => e
  # ...
end

do

rescue StandardError => e
  # ...
end

dla tych, których nie możesz zmienić na StandardError, ponownie zgłoś wyjątek:

rescue Exception => e
  # ...
  raise
end

lub przynajmniej ponownie podbij SystemExit i Interrupt

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

Wszelkie utworzone wyjątki niestandardowe powinny pochodzić od StandardError , a nie Exception .

Wayne Conrad
źródło
1
Wayne, czy byłbyś tak miły i dodał do swojej listy przykład IRB :: Abort?
Tim Snowhite
1
@Tim, znajdź irb.rb (w moim systemie znajduje się w /usr/lib/ruby/1.8/irb.rb) i znajdź główną pętlę (wyszukaj @ context.evaluate). Spójrz na klauzule ratunkowe, a myślę, że zrozumiesz, dlaczego IRB zachowuje się tak, jak się zachowuje.
Wayne Conrad
Dziękuję Ci. Spojrzenie na definicję #signal_handle w irb.rb również pomogło mi w zrozumieniu. Mają fajną sztuczkę w głównej pętli, która jest również wiązaniem zmiennej wyjątku. (Wykorzystanie klauzul ratunkowych jako sposobu na wybranie konkretnego wyjątku, a następnie użycie tego wyjątku poza organami ratowniczymi.)
Tim Snowhite
te działa doskonale:rescue SystemExit, Interrupt raise rescue Exception => e
James Tan
73

Jeśli możesz opakować cały program, możesz zrobić coś takiego:

 trap("SIGINT") { throw :ctrl_c }

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

To zasadniczo CtrlCużywa catch / throw zamiast obsługi wyjątków, więc jeśli istniejący kod nie ma już catch: ctrl_c, powinno być dobrze.

Alternatywnie możesz zrobić trap("SIGINT") { exit! }. exit!kończy działanie natychmiast, nie zgłasza wyjątku, więc kod nie może go przypadkowo przechwycić.

Logan Capaldo
źródło
2
Zauważ, że Ctrl-C w IRB wysyła IRB :: Abort, a nie SIGINT. W przeciwnym razie odpowiedzią @ Logana jest rozwiązanie.
Tim Snowhite
1
@TimSnowhite dla interpretera ruby SIGINTdziała dobrze dla mnie.
defhlt
1
throw i catch muszą znajdować się w tym samym wątku, więc nie zadziała, jeśli chcesz przechwycić wyjątek Interrupt w innym wątku.
Matt Connolly
39

Jeśli nie możesz zawinąć całej aplikacji w begin ... rescueblok (np. Thor), możesz po prostu przechwycić SIGINT:

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

130 to standardowy kod zakończenia.

Erik Nomitch
źródło
1
Do Twojej wiadomości, 130 to poprawny kod zakończenia dla skryptów przerwanych za pomocą 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
Idealny! Mam chory serwer Sinatra z ciągle działającym wątkiem w tle i wygląda na to, że potrzebuję zabić wątek również na cntrl-c, bez zmiany zachowania w inny sposób.
Narfanator
4

Używam ensureze świetnym efektem! Dotyczy to rzeczy, które chcesz, aby się wydarzyły, gdy twoje rzeczy się kończą, bez względu na to, dlaczego się kończą.

nos
źródło
0

Czysta obsługa Ctrl-C w Rubim w sposób 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

Źródło

noraj
źródło
Fajny przykład, ale myślę, że dodaje to więcej złożoności, niż jest to faktycznie potrzebne w kontekście OP.
Ron Klein