class Snipr::ProcessSignaller
Class that can send signals to targetted processes or their parent processes and invoke callbacks around the actions. Delegates process location to a ProcessLocator
. Is configured using a block on initialization as follows:
signaller = ProcessSignaller.new
do |signaller|
signaller.include /resque/ signaller.exclude /scheduler/ signaller.signal "USR1" signaller.target_parent false singaller.dry_run signaller.on_no_processes do puts "No processes" end signaller.before_signal do |signal, process| puts "Sending #{signal} to #{process.pid}" end signaller.after_signal do |signal, process| puts "Sent #{signal} to #{process.pid}" end signaller.on_error do |e, signal, process| puts "Ooops, got #{e} sending #{signal} to #{process.pid}" end
end
signaller.send_signals
Attributes
Public Class Methods
# File lib/snipr/process_signaller.rb, line 44 def initialize(&block) @locator = ProcessLocator.new on_no_processes {} before_signal {} after_signal {} on_error {|e, process, signal| raise e} block.call(self) end
Public Instance Methods
Callback invoked immediately after sending a signal to a process. Will send both the signal and the KernelProcess object as returned by the locator.
# File lib/snipr/process_signaller.rb, line 108 def after_signal(&callback) @after_signal = callback end
Callback invoked immediately before sending a signal to a process. Will send both the signal and the KernelProcess object as returned by the locator.
# File lib/snipr/process_signaller.rb, line 100 def before_signal(&callback) @before_signal = callback end
Ensure that the PID we are attempting to kill still matches what we expect This is not used when pkill option is set
# File lib/snipr/process_signaller.rb, line 156 def cmd_matches?(process) process.command == Snipr.exec_cmd("ps -p #{process.pid} -o 'command='").first.strip end
Invoke if you want to have callbacks invoked, but not actually send signals to located processes.
# File lib/snipr/process_signaller.rb, line 131 def dry_run @dry_run = true end
Specify or access the locator collaborator that is responsible for collecting the processes to operate on
# File lib/snipr/process_signaller.rb, line 86 def locator(locator=nil) @locator ||= (locator || ProcessLocator.new) end
Callback invoked if an error is encountered. If this is within the context of attempting to send a signal to a process, then the exception, signal and KernelProcess object are sent. Otherwise, only the exception is sent.
# File lib/snipr/process_signaller.rb, line 117 def on_error(&callback) @on_error = callback end
Callback invoked when no processes are found
# File lib/snipr/process_signaller.rb, line 92 def on_no_processes(&callback) @on_no_processes = callback end
Use pkill to ensure that the process we are attempting to signal matches what we know about it. This only works for the targetted process, not the parent.
# File lib/snipr/process_signaller.rb, line 139 def pkill @pkill = which("pkill") raise "pkill not found in path or is not executable!" unless @pkill end
# File lib/snipr/process_signaller.rb, line 160 def ppid_matches?(process) Integer(process.ppid) == Integer(Snipr.exec_cmd("ps -p #{process.pid} -o 'ppid='").first.strip) end
# File lib/snipr/process_signaller.rb, line 164 def process_matches?(process) cmd_matches?(process) && ppid_matches?(process) end
Send the specified signal to all located processes, invoking callbacks as appropriate.
# File lib/snipr/process_signaller.rb, line 56 def send_signals processes = @locator.locate if processes.empty? @on_no_processes.call else processes.each do |process| signal_process(process) end end rescue StandardError => e @on_error.call(e) end
Set to true if the signal should be sent to the parent of any located processes. Defaults to false.
# File lib/snipr/process_signaller.rb, line 124 def target_parent(flag=false) @target_parent = flag end
Determine if a command exists in our PATH
# File lib/snipr/process_signaller.rb, line 145 def which(cmd) ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| location = File.join(path, cmd) return location if File.executable?(location) end return nil end
Private Instance Methods
# File lib/snipr/process_signaller.rb, line 169 def signal_process(process) @before_signal.call(@signal, process) unless @dry_run if @target_parent Process.kill(@signal, process.ppid) if process_matches?(process) else if @pkill system("#{@pkill} --signal #{@signal} -P #{process.ppid} -f \"^#{Regexp.escape(process.command)}\"") else Process.kill(@signal, process.pid) if process_matches?(process) end end end @after_signal.call(@signal, process) rescue StandardError => e @on_error.call(e, @signal, process) end