Захват Ctrl-c в рубине

107

Мне передали давно работающую устаревшую программу Ruby, в которой много раз встречается

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

повсюду.

Не отслеживая каждое возможное исключение, которое каждое из них может обрабатывать (по крайней мере, не сразу), я все равно хотел бы иметь возможность время от времени отключать его с помощью CtrlC.

И я хотел бы сделать это таким образом, чтобы только добавить к коду (чтобы я не влиял на существующее поведение и не пропускал иначе обнаруженное исключение в середине выполнения).

[ CtrlC- это SIGINT или SystemExit, что похоже на SignalException.new("INT")систему обработки исключений Ruby. class SignalException < Exception, поэтому возникает эта проблема.]

Код, который я хотел бы написать, был бы таким:

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

РЕДАКТИРОВАТЬ: этот код работает до тех пор, пока вы получаете класс исключения, которое хотите правильно перехватить. Это либо SystemExit, либо Interrupt, либо IRB :: Abort, как показано ниже.

Тим Сноухайт
источник

Ответы:

132

Проблема в том, что когда программа Ruby завершается, она вызывает SystemExit . Когда входит control-C, возникает прерывание . Поскольку и SystemExit, и Interrupt являются производными от Exception , ваша обработка исключений останавливает выход или прерывание на своем пути. Вот исправление:

Где можешь, меняй

rescue Exception => e
  # ...
end

к

rescue StandardError => e
  # ...
end

для тех, кто не может перейти на StandardError, повторно вызовите исключение:

rescue Exception => e
  # ...
  raise
end

или, по крайней мере, повторно вызвать SystemExit и Interrupt

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

Любые созданные вами пользовательские исключения должны быть производными от StandardError , а не от Exception .

Уэйн Конрад
источник
1
Уэйн, не могли бы вы добавить в свой список пример IRB :: Abort?
Тим Сноухайт
1
@Tim, найдите irb.rb (в моей системе он находится в /usr/lib/ruby/1.8/irb.rb) и найдите основной цикл (найдите @ context.evaluate). Посмотрите на спасательные оговорки, и я думаю, вы поймете, почему IRB ведет себя именно так.
Уэйн Конрад
Спасибо. Просмотр определения #signal_handle в irb.rb тоже помог мне в понимании. У них также есть изящный трюк в привязке переменных исключения в основном цикле. (Использование условий спасения как способ выбрать конкретное исключение, а затем использование этого исключения за пределами спасательных органов.)
Тим Сноухайт,
эти работы идеально подходят:rescue SystemExit, Interrupt raise rescue Exception => e
Джеймс Тан
73

Если вы можете обернуть всю свою программу, вы можете сделать что-то вроде следующего:

 trap("SIGINT") { throw :ctrl_c }

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

Это в основном CtrlCиспользует catch / throw вместо обработки исключений, поэтому, если в существующем коде уже есть catch: ctrl_c, все должно быть в порядке.

В качестве альтернативы вы можете сделать файл trap("SIGINT") { exit! }. exit!немедленно завершается, исключение не возникает, поэтому код не может случайно его поймать.

Логан Капальдо
источник
2
Обратите внимание, что Ctrl-C в IRB отправляет IRB :: Abort, а не SIGINT. В противном случае ответ @ Logan - это решение.
Тим Сноухайт,
1
@TimSnowhite для рубинового интерпретатора SIGINTу меня отлично работает.
defhlt
1
throw и catch должны быть в одном потоке, поэтому это не сработает, если вы хотите перехватить исключение прерывания в другом потоке.
Мэтт Коннолли,
39

Если вы не можете заключить все свое приложение в begin ... rescueблок (например, Тор), вы можете просто перехватить SIGINT:

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

130 - стандартный код выхода.

Эрик Номич
источник
1
К вашему сведению, 130 - правильный код выхода для сценариев, прерванных 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))
Дориан
Отлично! У меня шаткий сервер Sinatra с постоянно работающим фоновым потоком, и это похоже на то, что мне нужно убить поток и на cntrl-c, без изменения поведения.
Нарфанатор
4

Пользуюсь ensureотлично! Это для вещей, которые вы хотите, чтобы произошло, когда ваши дела заканчиваются, независимо от того, почему они заканчиваются.

ворчливый
источник
0

Чистая обработка Ctrl-C в Ruby the 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

Источник

Норадж
источник
Хороший пример, но я думаю, что он добавляет больше сложности, чем действительно необходимо в контексте OP.
Рон Кляйн