From 861cd5d9b08aabb05164e42f464ea3fa34dfefcd Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 15 Jun 2015 12:36:47 +1000 Subject: [PATCH] FIX: ensure child demon is correctly terminated from parent on stop --- lib/demon/base.rb | 59 +++++++++++++++++++++++++----- spec/components/demon/base_spec.rb | 40 ++++++++++++++++++++ 2 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 spec/components/demon/base_spec.rb diff --git a/lib/demon/base.rb b/lib/demon/base.rb index e7cffd419b..0a7f201726 100644 --- a/lib/demon/base.rb +++ b/lib/demon/base.rb @@ -3,6 +3,10 @@ module Demon; end # intelligent fork based demonizer class Demon::Base + def self.demons + @demons + end + def self.start(count=1) @demons ||= {} count.times do |i| @@ -31,21 +35,57 @@ class Demon::Base end end + attr_reader :pid, :parent_pid, :started, :index + attr_accessor :stop_timeout + def initialize(index) @index = index @pid = nil @parent_pid = Process.pid @started = false + @stop_timeout = 10 end def pid_file "#{Rails.root}/tmp/pids/#{self.class.prefix}_#{@index}.pid" end + def alive? + if @pid + Demon::Base.alive?(@pid) + else + false + end + end + def stop @started = false if @pid + # TODO configurable stop signal 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 @started = false end @@ -96,7 +136,7 @@ class Demon::Base def already_running? if File.exists? pid_file pid = File.read(pid_file).to_i - if alive?(pid) + if Demon::Base.alive?(pid) return pid end end @@ -104,6 +144,15 @@ class Demon::Base nil end + def self.alive?(pid) + begin + Process.kill(0, pid) + true + rescue + false + end + end + private def write_pid_file @@ -130,14 +179,6 @@ class Demon::Base end end - def alive?(pid) - begin - Process.kill(0, pid) - true - rescue - false - end - end def suppress_stdout true diff --git a/spec/components/demon/base_spec.rb b/spec/components/demon/base_spec.rb new file mode 100644 index 0000000000..58a5811d8b --- /dev/null +++ b/spec/components/demon/base_spec.rb @@ -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