Class: Chore::Signal
- Inherits:
-
Object
- Object
- Chore::Signal
- Defined in:
- lib/chore/signal.rb
Overview
Provides smarter signal handling capabilities than Ruby's built-in Signal class. Specifically it runs callbacks in a separate thread since:
(1) Ruby 2.0 cannot obtain locks in the main Signal thread
(2) Doing so can result in deadlocks in Ruby 1.9.x.
Ruby's core implementation can be found at: ruby-doc.org/core-1.9.3/Signal.html
Differences
There are a few important differences with the way signals trapped through this class behave than through Ruby's Signal class.
Sequential processing
In Ruby, signals are interrupt-driven – the thread is executing at the time will be interrupted at that point in the call stack and start executing the signal handler. This increases the potential for deadlocks if mutexes are in use by both the thread and the signal handler.
In Chore, signal handlers are executed sequentially. When a handler is started, it must complete before the next signal is processed. These handlers are also executed in their own thread and, therefore, will compete for resources with the rest of the application.
Forking
In Ruby, forking does not disrupt the ability to process signals. Signals trapped in the master process will continue to be trapped in forked child processes.
In Chore, this is not the case. When a process is forked, any trapped signals will no longer get processed. This is because the thread that processes those incoming signals gets killed.
In order to process these signals, `Chore::Signal.reset` must be called, followed by additional calls to re-register those signal handlers.
Signal ordering
It is important to note that in Ruby, signals are essentially processed as LIFO (Last-In, First-Out) since they are interrupt driven. Similar behaviors is present in Chore's implementation.
Having LIFO behavior is the reason why this class uses a queue for tracking the list of incoming signals, instead of writing them out to a pipe.
Constant Summary collapse
- PRIORITIES =
The priorities of signals to handle. If not defined, the signal is considered high-priority.
{ 'CHLD' => :secondary }
Class Method Summary collapse
-
.reset ⇒ Object
Resets signals and handlers back to their defaults.
-
.trap(signal, command = nil, &block) ⇒ Object
Traps the given signal and runs the block when the signal is sent to this process.
Class Method Details
.reset ⇒ Object
Resets signals and handlers back to their defaults. Any unprocessed signals will be discarded.
This should be called after forking a processing in order to ensure that signals continue to get processed. Note, however, that new handlers must get registered after forking.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/chore/signal.rb', line 106 def reset # Reset traps back to their default behavior. Note that this *must* # be done first in order to prevent trap handlers from being called # while the wake pipe / listener are being reset. If this is run # out of order, then it's possible for those callbacks to hit errors. @handlers.keys.each {|signal| trap(signal, 'DEFAULT')} # Reset signals back to their empty state @listener = nil @primary_signals.clear @secondary_signals.clear @wake_out.close @wake_in.close @wake_in, @wake_out = IO.pipe end |
.trap(signal, command = nil, &block) ⇒ Object
Traps the given signal and runs the block when the signal is sent to this process. This will run the block outside of the trap thread.
Only a single handler can be registered for a signal at any point. If a signal has already been trapped, a warning will be generated and the previous handler for the signal will be returned.
See ::Signal#trap @ ruby-doc.org/core-1.9.3/Signal.html#method-c-trap for more information.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/chore/signal.rb', line 77 def trap(signal, command = nil, &block) if command # Command given for Ruby to interpret -- pass it directly onto Signal @handlers.delete(signal) ::Signal.trap(signal, command) else # Ensure we're listening for signals listen if @handlers[signal] Chore.logger.debug "#{signal} signal has been overwritten:\n#{caller * "\n"}" end # Wrap handlers so they run in the listener thread signals = PRIORITIES[signal] == :secondary ? @secondary_signals : @primary_signals @handlers[signal] = block ::Signal.trap(signal) do signals << signal wakeup end end end |