From e194b075bbd00264c8ef1fcf42b905411f9627bd Mon Sep 17 00:00:00 2001 From: Tom Preston-Werner Date: Sat, 22 Sep 2007 13:43:18 -0700 Subject: [PATCH] make self-daemonizing command synchronous --- examples/events.god | 12 ++++- lib/god.rb | 2 +- lib/god/behaviors/clean_pid_file.rb | 6 ++- lib/god/conditions/http_response_code.rb | 24 +++++++--- lib/god/process.rb | 78 +++++++++++++++++--------------- lib/god/watch.rb | 31 +++++++------ test/test_process.rb | 11 ----- test/test_watch.rb | 8 ++-- 8 files changed, 97 insertions(+), 75 deletions(-) diff --git a/examples/events.god b/examples/events.god index 3627756..9406398 100644 --- a/examples/events.god +++ b/examples/events.god @@ -12,6 +12,7 @@ God.watch do |w| w.start = "mongrel_rails start -P ./log/mongrel.pid -c #{RAILS_ROOT} -d" w.stop = "mongrel_rails stop -P ./log/mongrel.pid -c #{RAILS_ROOT}" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") + w.log = File.join(RAILS_ROOT, "log/commands.log") # clean pid files before start if necessary w.behavior(:clean_pid_file) @@ -31,7 +32,7 @@ God.watch do |w| # failsafe on.condition(:tries) do |c| - c.times = 3 + c.times = 8 c.transition = :start end end @@ -52,6 +53,15 @@ God.watch do |w| on.condition(:cpu_usage) do |c| c.interval = 10 c.above = 10.percent + c.times = 5 + end + + on.condition(:http_response_code) do |c| + c.host = 'localhost' + c.port = '3000' + c.path = '/' + c.code_is_not = 201 + c.timeout = 10.seconds c.times = [3, 5] end end diff --git a/lib/god.rb b/lib/god.rb index 7a9cb71..771d231 100644 --- a/lib/god.rb +++ b/lib/god.rb @@ -276,7 +276,7 @@ module God def self.stop_all self.watches.sort.each do |name, w| Thread.new do - w.unmonitor if w.state + w.unmonitor if w.state != :unmonitored w.action(:stop) if w.alive? end end diff --git a/lib/god/behaviors/clean_pid_file.rb b/lib/god/behaviors/clean_pid_file.rb index 85ee2e1..df404fa 100644 --- a/lib/god/behaviors/clean_pid_file.rb +++ b/lib/god/behaviors/clean_pid_file.rb @@ -9,7 +9,11 @@ module God end def before_start - File.delete(self.watch.pid_file) rescue nil + File.delete(self.watch.pid_file) + + "deleted pid file" + rescue + "no pid file to delete" end end diff --git a/lib/god/conditions/http_response_code.rb b/lib/god/conditions/http_response_code.rb index ad9227e..dbee675 100644 --- a/lib/god/conditions/http_response_code.rb +++ b/lib/god/conditions/http_response_code.rb @@ -26,6 +26,7 @@ module God end @timeline = Timeline.new(self.times[1]) + @history = Timeline.new(self.times[1]) end def valid? @@ -49,33 +50,44 @@ module God actual_response_code = response.code.to_i if self.code_is && self.code_is.include?(actual_response_code) - pass + pass(actual_response_code) elsif self.code_is_not && !self.code_is_not.include?(actual_response_code) - pass + pass(actual_response_code) else - fail + fail(actual_response_code) end rescue Timeout::Error - self.code_is ? fail : pass + self.code_is ? fail('Timeout') : pass('Timeout') end private - def pass + def pass(code) @timeline << true if @timeline.select { |x| x }.size >= self.times.first + self.info = "http response abnormal #{history(code, true)}" @timeline.clear + @history.clear true else + self.info = "http response nominal #{history(code, true)}" false end end - def fail + def fail(code) @timeline << false + self.info = "http response nominal #{history(code, false)}" false end + def history(code, passed) + entry = code.to_s.dup + entry = '*' + entry if passed + @history << entry + '[' + @history.join(", ") + ']' + end + end end diff --git a/lib/god/process.rb b/lib/god/process.rb index 3dc08ef..a2de46a 100644 --- a/lib/god/process.rb +++ b/lib/god/process.rb @@ -7,6 +7,8 @@ module God attr_accessor :name, :uid, :gid, :log, :start, :stop, :restart def initialize + self.log = '/dev/null' + @pid_file = nil @tracking_pid = false end @@ -38,12 +40,6 @@ module God LOG.log(self, :error, "No stop command was specified") end - # self-daemonizing processes cannot specify log - if !@tracking_pid && self.log - valid = false - LOG.log(self, :error, "Self-daemonizing processes cannot specify a log file") - end - # uid must exist if specified if self.uid begin @@ -123,43 +119,51 @@ module God if command.kind_of?(String) # string command - # fork/exec to setuid/gid - r, w = IO.pipe - opid = fork do - STDOUT.reopen(w) - r.close - pid = fork do - ::Process.setsid - ::Process::Sys.setgid(Etc.getgrnam(self.gid).gid) if self.gid - ::Process::Sys.setuid(Etc.getpwnam(self.uid).uid) if self.uid - Dir.chdir "/" - $0 = command - STDIN.reopen "/dev/null" - if self.log + if @tracking_pid + # fork/exec to setuid/gid + r, w = IO.pipe + opid = fork do + STDOUT.reopen(w) + r.close + pid = fork do + ::Process.setsid + ::Process::Sys.setgid(Etc.getgrnam(self.gid).gid) if self.gid + ::Process::Sys.setuid(Etc.getpwnam(self.uid).uid) if self.uid + Dir.chdir "/" + $0 = command + STDIN.reopen "/dev/null" STDOUT.reopen self.log, "a" - else - STDOUT.reopen "/dev/null", "a" + STDERR.reopen STDOUT + + exec command unless command.empty? end - STDERR.reopen STDOUT - - exec command unless command.empty? + puts pid.to_s end - puts pid.to_s - end - - ::Process.waitpid(opid, 0) - w.close - pid = r.gets.chomp - - if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action)) - File.open(default_pid_file, 'w') do |f| - f.write pid + + ::Process.waitpid(opid, 0) + w.close + pid = r.gets.chomp + + if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action)) + File.open(default_pid_file, 'w') do |f| + f.write pid + end + + @tracking_pid = true + @pid_file = default_pid_file end + else + orig_stdout = STDOUT.dup + orig_stderr = STDERR.dup + + STDOUT.reopen self.log, "a" + STDERR.reopen STDOUT + + system(command) - @tracking_pid = true - @pid_file = default_pid_file + STDOUT.reopen orig_stdout + STDERR.reopen orig_stderr end - elsif command.kind_of?(Proc) # lambda command command.call diff --git a/lib/god/watch.rb b/lib/god/watch.rb index d4b2506..ab5b74c 100644 --- a/lib/god/watch.rb +++ b/lib/god/watch.rb @@ -99,16 +99,10 @@ module God def action(a, c = nil) case a when :start - msg = "#{self.name} start: #{self.start.to_s}" - Syslog.debug(msg) - LOG.log(self, :info, msg) call_action(c, :start) sleep(self.start_grace + self.grace) when :restart if self.restart - msg = "#{self.name} restart: #{self.restart.to_s}" - Syslog.debug(msg) - LOG.log(self, :info, msg) call_action(c, :restart) else action(:stop, c) @@ -116,24 +110,33 @@ module God end sleep(self.restart_grace + self.grace) when :stop - if self.stop - msg = "#{self.name} stop: #{self.stop.to_s}" - Syslog.debug(msg) - LOG.log(self, :info, msg) - end call_action(c, :stop) sleep(self.stop_grace + self.grace) - end + end end def call_action(condition, action) # before before_items = self.behaviors before_items += [condition] if condition - before_items.each { |b| b.send("before_#{action}") } + before_items.each do |b| + info = b.send("before_#{action}") + if info + msg = "#{self.name} before_#{action}: #{info} (#{b.base_name})" + Syslog.debug(msg) + LOG.log(self, :info, msg) + end + end + + # log + if self.send(action) + msg = "#{self.name} #{action}: #{self.send(action).to_s}" + Syslog.debug(msg) + LOG.log(self, :info, msg) + end @process.call_action(action) - + # after after_items = self.behaviors after_items += [condition] if condition diff --git a/test/test_process.rb b/test/test_process.rb index 1966a5b..f0baab7 100644 --- a/test/test_process.rb +++ b/test/test_process.rb @@ -116,17 +116,6 @@ class TestProcessDaemon < Test::Unit::TestCase end end - def test_valid_should_return_false_if_self_daemonized_and_log - @p.pid_file = 'foo' - @p.start = 'baz' - @p.stop = 'qux' - @p.log = 'bar' - - no_stdout do - assert !@p.valid? - end - end - # defaul_pid_file def test_default_pid_file diff --git a/test/test_watch.rb b/test/test_watch.rb index 1f56b74..99ffa73 100644 --- a/test/test_watch.rb +++ b/test/test_watch.rb @@ -207,7 +207,7 @@ class TestWatch < Test::Unit::TestCase c = Conditions::FakePollCondition.new [:start, :stop].each do |cmd| @watch.expects(:call_action).with(c, cmd) - no_stdout { @watch.action(cmd, c) } + @watch.action(cmd, c) end end @@ -215,14 +215,14 @@ class TestWatch < Test::Unit::TestCase c = Conditions::FakePollCondition.new @watch.expects(:call_action).with(c, :stop) @watch.expects(:call_action).with(c, :start) - no_stdout { @watch.action(:restart, c) } + @watch.action(:restart, c) end def test_action_should_restart_to_call_action_if_present @watch.restart = lambda { } c = Conditions::FakePollCondition.new @watch.expects(:call_action).with(c, :restart) - no_stdout { @watch.action(:restart, c) } + @watch.action(:restart, c) end # call_action @@ -230,7 +230,7 @@ class TestWatch < Test::Unit::TestCase def test_call_action c = Conditions::FakePollCondition.new God::Process.any_instance.expects(:call_action).with(:start) - @watch.call_action(c, :start) + no_stdout { @watch.call_action(c, :start) } end # canonical_hash_form -- 2.11.4.GIT