Menangkap Ctrl-c dalam ruby

107

Saya melewati program ruby ​​lama yang berjalan lama, yang memiliki banyak kejadian

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

sepanjang itu.

Tanpa melacak setiap kemungkinan pengecualian yang masing-masing dapat tangani (setidaknya tidak segera), saya masih ingin menutupnya sesekali CtrlC.

Dan saya ingin melakukannya dengan cara yang hanya menambah kode (jadi saya tidak memengaruhi perilaku yang ada, atau melewatkan pengecualian yang tertangkap di tengah proses.)

[ CtrlCadalah SIGINT, atau SystemExit, yang tampaknya setara dengan SignalException.new("INT")sistem penanganan pengecualian Ruby. class SignalException < Exception, itulah mengapa masalah ini muncul.]

Kode yang ingin saya tulis adalah:

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

EDIT: Kode ini berfungsi, selama Anda mendapatkan kelas pengecualian yang ingin Anda perangkap dengan benar. Itu adalah SystemExit, Interrupt, atau IRB :: Abort seperti di bawah ini.

Tim Snowhite
sumber

Jawaban:

132

Masalahnya adalah ketika program Ruby berakhir, ia melakukannya dengan menaikkan SystemExit . Saat control-C masuk, Interrupt akan muncul . Karena SystemExit dan Interrupt berasal dari Exception , penanganan pengecualian Anda menghentikan pintu keluar atau interupsi di jalurnya. Berikut perbaikannya:

Dimanapun Anda bisa, ubahlah

rescue Exception => e
  # ...
end

untuk

rescue StandardError => e
  # ...
end

bagi mereka yang tidak dapat Anda ubah ke StandardError, ajukan kembali pengecualian:

rescue Exception => e
  # ...
  raise
end

atau, paling tidak, naikkan kembali SystemExit dan Interrupt

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

Setiap pengecualian khusus yang Anda buat harus berasal dari StandardError , bukan Exception .

Wayne Conrad
sumber
1
Wayne, maukah Anda menambahkan contoh IRB :: Abort ke daftar Anda juga?
Tim Snowhite
1
@Tim, cari irb.rb (di sistem saya, ada di /usr/lib/ruby/1.8/irb.rb) dan temukan loop utama (cari @ context.evaluate). Lihatlah klausul penyelamatan dan saya pikir Anda akan mengerti mengapa IRB berperilaku seperti itu.
Wayne Conrad
Terima kasih. Melihat definisi #signal_handle di irb.rb membantu pemahaman saya juga. Mereka memiliki trik rapi dalam pengikatan variabel pengecualian juga. (Menggunakan klausul penyelamatan sebagai cara untuk memilih pengecualian tertentu, kemudian menggunakan pengecualian itu di luar badan penyelamat.)
Tim Snowhite
ini bekerja dengan sempurna:rescue SystemExit, Interrupt raise rescue Exception => e
James Tan
73

Jika Anda dapat menyelesaikan seluruh program, Anda dapat melakukan sesuatu seperti berikut:

 trap("SIGINT") { throw :ctrl_c }

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

Ini pada dasarnya memiliki CtrlCpenggunaan catch / throw alih-alih penanganan pengecualian, jadi kecuali kode yang ada sudah memiliki catch: ctrl_c di dalamnya, itu akan baik-baik saja.

Sebagai alternatif, Anda dapat melakukan a trap("SIGINT") { exit! }. exit!segera keluar, itu tidak memunculkan pengecualian sehingga kode tidak dapat menangkapnya secara tidak sengaja.

Logan Capaldo
sumber
2
Perhatikan bahwa Ctrl-C di IRB mengirimkan IRB :: Abort, bukan SIGINT. Jika tidak, jawaban @ Logan adalah solusi.
Tim Snowhite
1
@TimSnowhite untuk penerjemah ruby SIGINTberfungsi dengan baik untuk saya.
defhlt
1
melempar dan menangkap harus berada di utas yang sama, jadi ini tidak akan berfungsi jika Anda ingin menangkap pengecualian Interupsi di utas lain.
Matt Connolly
39

Jika Anda tidak dapat membungkus seluruh aplikasi Anda dalam satu begin ... rescueblok (misalnya, Thor), Anda dapat menjebak SIGINT:

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

130 adalah kode keluar standar.

Erik Nomitch
sumber
1
FYI, 130 adalah kode keluar yang benar untuk skrip yang diinterupsi 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
Sempurna! Saya memiliki server Sinatra yang miring dengan utas latar belakang yang terus berjalan, dan ini terlihat seperti yang saya perlukan untuk mematikan utas juga di cntrl-c, tanpa mengubah perilaku.
Narfanator
4

Saya menggunakan ensureuntuk efek yang bagus! Ini untuk hal-hal yang Anda inginkan terjadi ketika barang Anda berakhir tidak peduli mengapa itu berakhir.

hidung belang
sumber
0

Menangani Ctrl-C dengan rapi di Ruby dengan cara 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

Sumber

noraj
sumber
Contoh yang bagus, tapi saya pikir itu menambah kompleksitas lebih dari yang sebenarnya dibutuhkan dalam konteks OP.
Ron Klein