FIX: ensure child demon is correctly terminated from parent on stop

This commit is contained in:
Sam 2015-06-15 12:36:47 +10:00
parent 69ad0358c2
commit 861cd5d9b0
2 changed files with 90 additions and 9 deletions

View File

@ -3,6 +3,10 @@ module Demon; end
# intelligent fork based demonizer # intelligent fork based demonizer
class Demon::Base class Demon::Base
def self.demons
@demons
end
def self.start(count=1) def self.start(count=1)
@demons ||= {} @demons ||= {}
count.times do |i| count.times do |i|
@ -31,21 +35,57 @@ class Demon::Base
end end
end end
attr_reader :pid, :parent_pid, :started, :index
attr_accessor :stop_timeout
def initialize(index) def initialize(index)
@index = index @index = index
@pid = nil @pid = nil
@parent_pid = Process.pid @parent_pid = Process.pid
@started = false @started = false
@stop_timeout = 10
end end
def pid_file def pid_file
"#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid" "#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid"
end end
def alive?
if @pid
Demon::Base.alive?(@pid)
else
false
end
end
def stop def stop
@started = false @started = false
if @pid if @pid
# TODO configurable stop signal
Process.kill("HUP",@pid) Process.kill("HUP",@pid)
wait_for_stop = lambda {
timeout = @stop_timeout
while alive? && timeout > 0
timeout -= (@stop_timeout/10.0)
sleep(@stop_timeout/10.0)
Process.waitpid(@pid, Process::WNOHANG) rescue -1
end
Process.waitpid(@pid, Process::WNOHANG) rescue -1
}
wait_for_stop.call
if alive?
STDERR.puts "Process would not terminate cleanly, force quitting. pid: #{@pid}"
Process.kill("KILL", @pid)
end
wait_for_stop.call
@pid = nil @pid = nil
@started = false @started = false
end end
@ -96,7 +136,7 @@ class Demon::Base
def already_running? def already_running?
if File.exists? pid_file if File.exists? pid_file
pid = File.read(pid_file).to_i pid = File.read(pid_file).to_i
if alive?(pid) if Demon::Base.alive?(pid)
return pid return pid
end end
end end
@ -104,6 +144,15 @@ class Demon::Base
nil nil
end end
def self.alive?(pid)
begin
Process.kill(0, pid)
true
rescue
false
end
end
private private
def write_pid_file def write_pid_file
@ -130,14 +179,6 @@ class Demon::Base
end end
end end
def alive?(pid)
begin
Process.kill(0, pid)
true
rescue
false
end
end
def suppress_stdout def suppress_stdout
true true

View File

@ -0,0 +1,40 @@
require 'spec_helper'
require 'demon/base'
describe Demon do
class RudeDemon < Demon::Base
def self.prefix
"rude"
end
def after_fork
Signal.trap("HUP"){}
Signal.trap("TERM"){}
sleep 999999
end
end
it "can terminate rude demons" do
skip("forking rspec has side effects")
# Forking rspec has all sorts of weird side effects
# this spec works but we must skip it to keep rspec
# state happy
RudeDemon.start
_,demon = RudeDemon.demons.first
pid = demon.pid
wait_for {
demon.alive?
}
demon.stop_timeout = 0.05
demon.stop
demon.start
running = !!(Process.kill(0, pid)) rescue false
expect(running).to eq(false)
end
end