การจับ Ctrl-c ในทับทิม


107

ฉันผ่านโปรแกรมทับทิมที่ใช้งานมายาวนานซึ่งมีเหตุการณ์ต่างๆเกิดขึ้นมากมาย

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

ปัญหาคือว่าเมื่อโปรแกรมทับทิมปลายก็ไม่ได้โดยการระดมSystemExit เมื่อมีการควบคุม-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ไม่ยกเว้น


1
เวย์นคุณจะใจดีเพิ่ม IRB :: Abort ตัวอย่างในรายการของคุณด้วยหรือไม่?
Tim Snowhite

1
@ ทิมไปหา irb.rb (ในระบบของฉันมันอยู่ใน /usr/lib/ruby/1.8/irb.rb) และค้นหาลูปหลัก (ค้นหา @ context.evaluate) ดูคำสั่งการช่วยเหลือแล้วฉันคิดว่าคุณจะเข้าใจว่าทำไม IRB ถึงมีพฤติกรรมในแบบที่เป็นอยู่
Wayne Conrad

ขอบคุณ. การดูคำจำกัดความของ #signal_handle ใน irb.rb ช่วยให้ฉันเข้าใจเช่นกัน พวกเขามีเคล็ดลับที่เรียบร้อยภายในลูปหลักของการรวมตัวแปรข้อยกเว้นเช่นกัน (ใช้คำสั่งช่วยเหลือเป็นวิธีในการเลือกข้อยกเว้นที่เฉพาะเจาะจงจากนั้นใช้ข้อยกเว้นนั้นนอกหน่วยกู้ภัย)
Tim Snowhite

งานเหล่านี้สมบูรณ์แบบ:rescue SystemExit, Interrupt raise rescue Exception => e
James Tan

74

หากคุณสามารถรวมโปรแกรมทั้งหมดของคุณคุณสามารถทำสิ่งต่อไปนี้:

 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 คือทางออก
Tim Snowhite

1
@TimSnowhite สำหรับล่ามทับทิมSIGINTทำงานได้ดีสำหรับฉัน
defhlt

1
การโยนและการจับต้องอยู่บนเธรดเดียวกันดังนั้นจะใช้ไม่ได้หากคุณต้องการจับข้อยกเว้นการขัดจังหวะในเธรดอื่น
Matt Connolly

39

หากคุณไม่สามารถรวมแอปพลิเคชันทั้งหมดของคุณไว้ในbegin ... rescueบล็อก (เช่น Thor) คุณสามารถดักจับSIGINT:

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

130 เป็นรหัสทางออกมาตรฐาน


1
FYI, 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))
Dorian

สมบูรณ์แบบ! ฉันมีเซิร์ฟเวอร์ Sinatra ที่ว่องไวพร้อมเธรดพื้นหลังที่ทำงานอยู่ตลอดเวลาและดูเหมือนว่าสิ่งที่ฉันต้องการเพื่อฆ่าเธรดเช่นกันบน cntrl-c โดยไม่ต้องเปลี่ยนพฤติกรรม
Narfanator

4

ฉันใช้ensureเพื่อเอฟเฟกต์ที่ยอดเยี่ยม! นี่คือสิ่งที่คุณต้องการให้เกิดขึ้นเมื่อสิ่งต่างๆของคุณสิ้นสุดลงไม่ว่าจะจบลงด้วยเหตุใด


0

การจัดการ Ctrl-C อย่างหมดจดใน Rubyด้วยวิธี 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
Ron Klein
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.