summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-01-05 16:10:17 -0800
committerEric Wong <normalperson@yhbt.net>2010-01-05 16:10:17 -0800
commit841f967666292f634c6af485f7ac452f36cc2028 (patch)
tree4dba86c2fb545a57c7728d1275d276e12d3b26d5
parent40924ca5f42a5708159ac27b992805f24ecbae9b (diff)
parentfe005f50efc8db5b9f4b2387b3b2c42f12d7c2c0 (diff)
* ready_pipe:
  launcher: no point in sync-ing $stdin
  launcher: fix compatibility with other servers
  clarify errors when listeners fail to bind
  launcher: descriptive error message on startup failure
  Avoid leaking ready pipe file descriptor to workers
  exit with failure if master dies when daemonized
-rwxr-xr-xbin/unicorn2
-rwxr-xr-xbin/unicorn_rails2
-rw-r--r--lib/unicorn.rb14
-rw-r--r--lib/unicorn/launcher.rb38
-rw-r--r--test/exec/test_exec.rb2
5 files changed, 47 insertions, 11 deletions
diff --git a/bin/unicorn b/bin/unicorn
index 651c2ff..5af021d 100755
--- a/bin/unicorn
+++ b/bin/unicorn
@@ -161,5 +161,5 @@ if $DEBUG
   })
 end
 
-Unicorn::Launcher.daemonize! if daemonize
+Unicorn::Launcher.daemonize!(options) if daemonize
 Unicorn.run(app, options)
diff --git a/bin/unicorn_rails b/bin/unicorn_rails
index 4a22a8c..b1458fc 100755
--- a/bin/unicorn_rails
+++ b/bin/unicorn_rails
@@ -202,6 +202,6 @@ end
 
 if daemonize
   options[:pid] = rails_pid
-  Unicorn::Launcher.daemonize!
+  Unicorn::Launcher.daemonize!(options)
 end
 Unicorn.run(app, options)
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index 225e00a..69ecf33 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -38,7 +38,7 @@ module Unicorn
                                 :before_fork, :after_fork, :before_exec,
                                 :logger, :pid, :app, :preload_app,
                                 :reexec_pid, :orig_app, :init_listeners,
-                                :master_pid, :config)
+                                :master_pid, :config, :ready_pipe)
     include ::Unicorn::SocketHelper
 
     # prevents IO objects in here from being GC-ed
@@ -162,6 +162,7 @@ module Unicorn
     def initialize(app, options = {})
       self.app = app
       self.reexec_pid = 0
+      self.ready_pipe = options.delete(:ready_pipe)
       self.init_listeners = options[:listeners] ? options[:listeners].dup : []
       self.config = Configurator.new(options.merge(:use_defaults => true))
       self.listener_opts = {}
@@ -327,6 +328,11 @@ module Unicorn
       trap(:CHLD) { |sig_nr| awaken_master }
       proc_name 'master'
       logger.info "master process ready" # test_exec.rb relies on this message
+      if ready_pipe
+        ready_pipe.syswrite($$.to_s)
+        ready_pipe.close rescue nil
+        self.ready_pipe = nil
+      end
       begin
         loop do
           reap_all_workers
@@ -531,7 +537,11 @@ module Unicorn
         WORKERS.values.include?(worker_nr) and next
         worker = Worker.new(worker_nr, Unicorn::Util.tmpio)
         before_fork.call(self, worker)
-        WORKERS[fork { worker_loop(worker) }] = worker
+        WORKERS[fork {
+          ready_pipe.close if ready_pipe
+          self.ready_pipe = nil
+          worker_loop(worker)
+        }] = worker
       end
     end
 
diff --git a/lib/unicorn/launcher.rb b/lib/unicorn/launcher.rb
index 1229b84..e71f93b 100644
--- a/lib/unicorn/launcher.rb
+++ b/lib/unicorn/launcher.rb
@@ -1,6 +1,6 @@
 # -*- encoding: binary -*-
 
-$stdin.sync = $stdout.sync = $stderr.sync = true
+$stdout.sync = $stderr.sync = true
 $stdin.binmode
 $stdout.binmode
 $stderr.binmode
@@ -19,21 +19,47 @@ class Unicorn::Launcher
   #     the directory it was started in when being re-executed
   #     to pickup code changes if the original deployment directory
   #     is a symlink or otherwise got replaced.
-  def self.daemonize!
+  def self.daemonize!(options = nil)
     $stdin.reopen("/dev/null")
 
     # We only start a new process group if we're not being reexecuted
     # and inheriting file descriptors from our parent
     unless ENV['UNICORN_FD']
-      exit if fork
-      Process.setsid
-      exit if fork
+      if options
+        # grandparent - reads pipe, exits when master is ready
+        #  \_ parent  - exits immediately ASAP
+        #      \_ unicorn master - writes to pipe when ready
 
+        rd, wr = IO.pipe
+        grandparent = $$
+        if fork
+          wr.close # grandparent does not write
+        else
+          rd.close # unicorn master does not read
+          Process.setsid
+          exit if fork # parent dies now
+        end
+
+        if grandparent == $$
+          # this will block until HttpServer#join runs (or it dies)
+          master_pid = (rd.readpartial(16) rescue nil).to_i
+          unless master_pid > 1
+            warn "master failed to start, check stderr log for details"
+            exit!(1)
+          end
+          exit 0
+        else # unicorn master process
+          options[:ready_pipe] = wr
+        end
+      else # backwards compat
+        exit if fork
+        Process.setsid
+        exit if fork
+      end
       # $stderr/$stderr can/will be redirected separately in the Unicorn config
       Unicorn::Configurator::DEFAULTS[:stderr_path] = "/dev/null"
       Unicorn::Configurator::DEFAULTS[:stdout_path] = "/dev/null"
     end
-    $stdin.sync = $stdout.sync = $stderr.sync = true
   end
 
 end
diff --git a/test/exec/test_exec.rb b/test/exec/test_exec.rb
index fc0719b..24ba856 100644
--- a/test/exec/test_exec.rb
+++ b/test/exec/test_exec.rb
@@ -805,7 +805,7 @@ EOF
       exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
     end
     pid, status = Process.waitpid2(pid)
-    assert status.success?, "original process exited successfully"
+    assert ! status.success?, "original process exited successfully"
     sleep 1 # can't waitpid on a daemonized process :<
     assert err.stat.size > 0
   end