1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
|
module Stud
# Bind a block to be called when a certain signal is received.
#
# Same arguments to Signal::trap.
#
# The behavior of this method is different than Signal::trap because
# multiple handlers can request notification for the same signal.
#
# For example, this is valid:
#
# Stud.trap("INT") { puts "Hello" }
# Stud.trap("INT") { puts "World" }
#
# When SIGINT is received, both callbacks will be invoked, in order.
#
# This helps avoid the situation where a library traps a signal outside of
# your control.
#
# If something has already used Signal::trap, that callback will be saved
# and scheduled the same way as any other Stud::trap.
def self.trap(signal, &block)
@traps ||= Hash.new { |h,k| h[k] = [] }
if !@traps.include?(signal)
# First trap call for this signal, tell ruby to invoke us.
previous_trap = Signal::trap(signal) { simulate_signal(signal) }
# If there was a previous trap (via Kernel#trap) set, make sure we remember it.
if previous_trap.is_a?(Proc)
# MRI's default traps are "DEFAULT" string
# JRuby's default traps are Procs with a source_location of "(internal")
if RUBY_ENGINE != "jruby" || previous_trap.source_location.first != "(internal)"
@traps[signal] << previous_trap
end
end
end
@traps[signal] << block
return block.object_id
end # def self.trap
# Simulate a signal. This lets you force an interrupt without
# sending a signal to yourself.
def self.simulate_signal(signal)
#puts "Simulate: #{signal} w/ #{@traps[signal].count} callbacks"
@traps[signal].each(&:call)
end # def self.simulate_signal
# Remove a previously set signal trap.
#
# 'signal' is the name of the signal ("INT", etc)
# 'id' is the value returned by a previous Stud.trap() call
def self.untrap(signal, id)
@traps[signal].delete_if { |block| block.object_id == id }
# Restore the default handler if there are no custom traps anymore.
if @traps[signal].empty?
@traps.delete(signal)
Signal::trap(signal, "DEFAULT")
end
end # def self.untrap
end # module Stud
# Monkey-patch the main 'trap' stuff? This could be useful.
#module Signal
#def trap(signal, value=nil, &block)
#if value.nil?
#Stud.trap(signal, &block)
#else
## do nothing?
#end
#end # def trap
#end
#
#module Kernel
#def trap(signal, value=nil, &block)
#Signal.trap(signal, value, &block)
#end
#end
|