about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--GNUmakefile2
-rwxr-xr-xbin/unicorn22
-rwxr-xr-xbin/unicorn_rails26
-rw-r--r--lib/unicorn.rb200
-rw-r--r--lib/unicorn/configurator.rb27
-rw-r--r--lib/unicorn/const.rb5
-rw-r--r--lib/unicorn/launcher.rb7
-rwxr-xr-xt/t0003-working_directory.sh5
-rwxr-xr-xt/t0010-reap-logging.sh55
-rwxr-xr-xt/t0303-rails3-alt-working_directory_config.ru.sh5
-rw-r--r--test/exec/test_exec.rb2
12 files changed, 188 insertions, 170 deletions
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 755e132..88b943a 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.1.4.GIT
+DEF_VER=v1.0.0.GIT
 
 LF='
 '
diff --git a/GNUmakefile b/GNUmakefile
index 3354ff1..b5fe9fd 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -169,7 +169,7 @@ NEWS: GIT-VERSION-FILE .manifest
         $(RAKE) -s news_rdoc > $@+
         mv $@+ $@
 
-SINCE = 1.0.0
+SINCE = 0.991.0
 ChangeLog: LOG_VERSION = \
   $(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
           echo $(GIT_VERSION) || git describe)
diff --git a/bin/unicorn b/bin/unicorn
index 8d984bd..73bff3a 100755
--- a/bin/unicorn
+++ b/bin/unicorn
@@ -4,16 +4,13 @@ require 'unicorn/launcher'
 require 'optparse'
 
 ENV["RACK_ENV"] ||= "development"
-daemonize = false
-options = { :listeners => [] }
-host, port = Unicorn::Const::DEFAULT_HOST, Unicorn::Const::DEFAULT_PORT
-set_listener = false
+rackup_opts = Unicorn::Configurator::RACKUP
+options = rackup_opts[:options]
 
 opts = OptionParser.new("", 24, '  ') do |opts|
   cmd = File.basename($0)
   opts.banner = "Usage: #{cmd} " \
                 "[ruby options] [#{cmd} options] [rackup config file]"
-
   opts.separator "Ruby options:"
 
   lineno = 1
@@ -46,14 +43,14 @@ opts = OptionParser.new("", 24, '  ') do |opts|
 
   opts.on("-o", "--host HOST",
           "listen on HOST (default: #{Unicorn::Const::DEFAULT_HOST})") do |h|
-    host = h
-    set_listener = true
+    rackup_opts[:host] = h
+    rackup_opts[:set_listener] = true
   end
 
   opts.on("-p", "--port PORT",
           "use PORT (default: #{Unicorn::Const::DEFAULT_PORT})") do |p|
-    port = p.to_i
-    set_listener = true
+    rackup_opts[:port] = p.to_i
+    rackup_opts[:set_listener] = true
   end
 
   opts.on("-E", "--env ENVIRONMENT",
@@ -62,7 +59,7 @@ opts = OptionParser.new("", 24, '  ') do |opts|
   end
 
   opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
-    daemonize = d ? true : false
+    rackup_opts[:daemonize] = !!d
   end
 
   opts.on("-P", "--pid FILE", "DEPRECATED") do |f|
@@ -109,16 +106,15 @@ opts = OptionParser.new("", 24, '  ') do |opts|
 end
 
 app = Unicorn.builder(ARGV[0] || 'config.ru', opts)
-options[:listeners] << "#{host}:#{port}" if set_listener
 
 if $DEBUG
   require 'pp'
   pp({
     :unicorn_options => options,
     :app => app,
-    :daemonize => daemonize,
+    :daemonize => rackup_opts[:daemonize],
   })
 end
 
-Unicorn::Launcher.daemonize!(options) if daemonize
+Unicorn::Launcher.daemonize!(options) if rackup_opts[:daemonize]
 Unicorn.run(app, options)
diff --git a/bin/unicorn_rails b/bin/unicorn_rails
index 0b2d92f..0294b59 100755
--- a/bin/unicorn_rails
+++ b/bin/unicorn_rails
@@ -4,11 +4,9 @@ require 'unicorn/launcher'
 require 'optparse'
 require 'fileutils'
 
-daemonize = false
-options = { :listeners => [] }
-host, port = Unicorn::Const::DEFAULT_HOST, Unicorn::Const::DEFAULT_PORT
-set_listener = false
 ENV['RAILS_ENV'] ||= "development"
+rackup_opts = Unicorn::Configurator::RACKUP
+options = rackup_opts[:options]
 
 opts = OptionParser.new("", 24, '  ') do |opts|
   cmd = File.basename($0)
@@ -46,13 +44,14 @@ opts = OptionParser.new("", 24, '  ') do |opts|
 
   opts.on("-o", "--host HOST",
           "listen on HOST (default: #{Unicorn::Const::DEFAULT_HOST})") do |h|
-    host = h
-    set_listener = true
+    rackup_opts[:host] = h
+    rackup_opts[:set_listener] = true
   end
 
-  opts.on("-p", "--port PORT", "use PORT (default: #{port})") do |p|
-    port = p.to_i
-    set_listener = true
+  opts.on("-p", "--port PORT",
+          "use PORT (default: #{Unicorn::Const::DEFAULT_PORT})") do |p|
+    rackup_opts[:port] = p.to_i
+    rackup_opts[:set_listener] = true
   end
 
   opts.on("-E", "--env RAILS_ENV",
@@ -61,7 +60,7 @@ opts = OptionParser.new("", 24, '  ') do |opts|
   end
 
   opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
-    daemonize = d ? true : false
+    rackup_opts[:daemonize] = !!d
   end
 
   # Unicorn-specific stuff
@@ -186,15 +185,14 @@ def rails_builder(ru, opts, daemonize)
   end
 end
 
-app = rails_builder(ARGV[0], opts, daemonize)
-options[:listeners] << "#{host}:#{port}" if set_listener
+app = rails_builder(ARGV[0], opts, rackup_opts[:daemonize])
 
 if $DEBUG
   require 'pp'
   pp({
     :unicorn_options => options,
     :app => app,
-    :daemonize => daemonize,
+    :daemonize => rackup_opts[:daemonize],
   })
 end
 
@@ -203,7 +201,7 @@ options[:after_reload] = lambda do
   FileUtils.mkdir_p(%w(cache pids sessions sockets).map! { |d| "tmp/#{d}" })
 end
 
-if daemonize
+if rackup_opts[:daemonize]
   options[:pid] = "tmp/pids/unicorn.pid"
   Unicorn::Launcher.daemonize!(options)
 end
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index 8f490bb..7f91352 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -392,65 +392,59 @@ module Unicorn
         self.ready_pipe = nil
       end
       begin
-        loop do
-          reap_all_workers
-          case SIG_QUEUE.shift
-          when nil
-            # avoid murdering workers after our master process (or the
-            # machine) comes out of suspend/hibernation
-            if (last_check + timeout) >= (last_check = Time.now)
-              murder_lazy_workers
-            else
-              # wait for workers to wakeup on suspend
-              master_sleep(timeout/2.0 + 1)
-            end
-            maintain_worker_count if respawn
-            master_sleep(1)
-          when :QUIT # graceful shutdown
-            break
-          when :TERM, :INT # immediate shutdown
-            stop(false)
-            break
-          when :USR1 # rotate logs
-            logger.info "master reopening logs..."
-            Unicorn::Util.reopen_logs
-            logger.info "master done reopening logs"
-            kill_each_worker(:USR1)
-          when :USR2 # exec binary, stay alive in case something went wrong
+        reap_all_workers
+        case SIG_QUEUE.shift
+        when nil
+          # avoid murdering workers after our master process (or the
+          # machine) comes out of suspend/hibernation
+          if (last_check + timeout) >= (last_check = Time.now)
+            murder_lazy_workers
+          else
+            # wait for workers to wakeup on suspend
+            master_sleep(timeout/2.0 + 1)
+          end
+          maintain_worker_count if respawn
+          master_sleep(1)
+        when :QUIT # graceful shutdown
+          break
+        when :TERM, :INT # immediate shutdown
+          stop(false)
+          break
+        when :USR1 # rotate logs
+          logger.info "master reopening logs..."
+          Unicorn::Util.reopen_logs
+          logger.info "master done reopening logs"
+          kill_each_worker(:USR1)
+        when :USR2 # exec binary, stay alive in case something went wrong
+          reexec
+        when :WINCH
+          if Process.ppid == 1 || Process.getpgrp != $$
+            respawn = false
+            logger.info "gracefully stopping all workers"
+            kill_each_worker(:QUIT)
+            self.worker_processes = 0
+          else
+            logger.info "SIGWINCH ignored because we're not daemonized"
+          end
+        when :TTIN
+          respawn = true
+          self.worker_processes += 1
+        when :TTOU
+          self.worker_processes -= 1 if self.worker_processes > 0
+        when :HUP
+          respawn = true
+          if config.config_file
+            load_config!
+          else # exec binary and exit if there's no config file
+            logger.info "config_file not present, reexecuting binary"
             reexec
-          when :WINCH
-            if Process.ppid == 1 || Process.getpgrp != $$
-              respawn = false
-              logger.info "gracefully stopping all workers"
-              kill_each_worker(:QUIT)
-              self.worker_processes = 0
-            else
-              logger.info "SIGWINCH ignored because we're not daemonized"
-            end
-          when :TTIN
-            respawn = true
-            self.worker_processes += 1
-          when :TTOU
-            self.worker_processes -= 1 if self.worker_processes > 0
-          when :HUP
-            respawn = true
-            if config.config_file
-              load_config!
-              redo # immediate reaping since we may have QUIT workers
-            else # exec binary and exit if there's no config file
-              logger.info "config_file not present, reexecuting binary"
-              reexec
-              break
-            end
           end
         end
       rescue Errno::EINTR
-        retry
       rescue => e
         logger.error "Unhandled master loop exception #{e.inspect}."
         logger.error e.backtrace.join("\n")
-        retry
-      end
+      end while true
       stop # gracefully shutdown all workers on our way out
       logger.info "master complete"
       unlink_pid_safe(pid) if pid
@@ -489,42 +483,34 @@ module Unicorn
     # wait for a signal hander to wake us up and then consume the pipe
     # Wake up every second anyways to run murder_lazy_workers
     def master_sleep(sec)
-      begin
-        IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
-        SELF_PIPE[0].read_nonblock(Const::CHUNK_SIZE, HttpRequest::BUF)
+      IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
+      SELF_PIPE[0].read_nonblock(Const::CHUNK_SIZE, HttpRequest::BUF)
       rescue Errno::EAGAIN, Errno::EINTR
-        break
-      end while true
     end
 
     def awaken_master
-      begin
-        SELF_PIPE[1].write_nonblock('.') # wakeup master process from select
+      SELF_PIPE[1].write_nonblock('.') # wakeup master process from select
       rescue Errno::EAGAIN, Errno::EINTR
-        # pipe is full, master should wake up anyways
-        retry
-      end
     end
 
     # reaps all unreaped workers
     def reap_all_workers
       begin
-        loop do
-          wpid, status = Process.waitpid2(-1, Process::WNOHANG)
-          wpid or break
-          if reexec_pid == wpid
-            logger.error "reaped #{status.inspect} exec()-ed"
-            self.reexec_pid = 0
-            self.pid = pid.chomp('.oldbin') if pid
-            proc_name 'master'
-          else
-            worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
-            logger.info "reaped #{status.inspect} " \
-                        "worker=#{worker.nr rescue 'unknown'}"
-          end
+        wpid, status = Process.waitpid2(-1, Process::WNOHANG)
+        wpid or return
+        if reexec_pid == wpid
+          logger.error "reaped #{status.inspect} exec()-ed"
+          self.reexec_pid = 0
+          self.pid = pid.chomp('.oldbin') if pid
+          proc_name 'master'
+        else
+          worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
+          m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
+          status.success? ? logger.info(m) : logger.error(m)
         end
       rescue Errno::ECHILD
-      end
+        break
+      end while true
     end
 
     # reexecutes the START_CTX with a new binary
@@ -565,8 +551,7 @@ module Unicorn
         # relies on FD inheritence.
         (3..1024).each do |io|
           next if listener_fds.include?(io)
-          io = IO.for_fd(io) rescue nil
-          io or next
+          io = IO.for_fd(io) rescue next
           IO_PURGATORY << io
           io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
         end
@@ -730,15 +715,13 @@ module Unicorn
 
         ppid == Process.ppid or return
         alive.chmod(m = 0 == m ? 1 : 0)
-        begin
-          # timeout used so we can detect parent death:
-          ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) or redo
-          ready = ret[0]
-        rescue Errno::EINTR
-          ready = LISTENERS
-        rescue Errno::EBADF
-          nr < 0 or return
-        end
+
+        # timeout used so we can detect parent death:
+        ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) and ready = ret[0]
+      rescue Errno::EINTR
+        ready = LISTENERS
+      rescue Errno::EBADF
+        nr < 0 or return
       rescue => e
         if alive
           logger.error "Unhandled listen loop exception #{e.inspect}."
@@ -750,11 +733,9 @@ module Unicorn
     # delivers a signal to a worker and fails gracefully if the worker
     # is no longer running.
     def kill_worker(signal, wpid)
-      begin
-        Process.kill(signal, wpid)
+      Process.kill(signal, wpid)
       rescue Errno::ESRCH
         worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
-      end
     end
 
     # delivers a signal to each worker
@@ -774,33 +755,28 @@ module Unicorn
     # nil otherwise.
     def valid_pid?(path)
       wpid = File.read(path).to_i
-      wpid <= 0 and return nil
-      begin
-        Process.kill(0, wpid)
-        wpid
-      rescue Errno::ESRCH
+      wpid <= 0 and return
+      Process.kill(0, wpid)
+      wpid
+      rescue Errno::ESRCH, Errno::ENOENT
         # don't unlink stale pid files, racy without non-portable locking...
-      end
-      rescue Errno::ENOENT
     end
 
     def load_config!
       loaded_app = app
-      begin
-        logger.info "reloading config_file=#{config.config_file}"
-        config[:listeners].replace(init_listeners)
-        config.reload
-        config.commit!(self)
-        kill_each_worker(:QUIT)
-        Unicorn::Util.reopen_logs
-        self.app = orig_app
-        build_app! if preload_app
-        logger.info "done reloading config_file=#{config.config_file}"
-      rescue StandardError, LoadError, SyntaxError => e
-        logger.error "error reloading config_file=#{config.config_file}: " \
-                     "#{e.class} #{e.message} #{e.backtrace}"
-        self.app = loaded_app
-      end
+      logger.info "reloading config_file=#{config.config_file}"
+      config[:listeners].replace(init_listeners)
+      config.reload
+      config.commit!(self)
+      kill_each_worker(:QUIT)
+      Unicorn::Util.reopen_logs
+      self.app = orig_app
+      build_app! if preload_app
+      logger.info "done reloading config_file=#{config.config_file}"
+    rescue StandardError, LoadError, SyntaxError => e
+      logger.error "error reloading config_file=#{config.config_file}: " \
+                   "#{e.class} #{e.message} #{e.backtrace}"
+      self.app = loaded_app
     end
 
     # returns an array of string names for the given listener array
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb
index 6be6fbd..ce886cf 100644
--- a/lib/unicorn/configurator.rb
+++ b/lib/unicorn/configurator.rb
@@ -9,13 +9,19 @@ require 'logger'
 # nginx is also available at
 # http://unicorn.bogomips.org/examples/nginx.conf
 class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload)
+  # :stopdoc:
   # used to stash stuff for deferred processing of cli options in
   # config.ru after "working_directory" is bound.  Do not rely on
   # this being around later on...
-  RACKUP = {} # :nodoc:
+  RACKUP = {
+    :daemonize => false,
+    :host => Unicorn::Const::DEFAULT_HOST,
+    :port => Unicorn::Const::DEFAULT_PORT,
+    :set_listener => false,
+    :options => { :listeners => [] }
+  }
 
   # Default settings for Unicorn
-  # :stopdoc:
   DEFAULTS = {
     :timeout => 60,
     :logger => Logger.new($stderr),
@@ -55,6 +61,9 @@ class Unicorn::Configurator < Struct.new(:set, :config_file, :after_reload)
 
     parse_rackup_file
 
+    RACKUP[:set_listener] and
+      set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
+
     # unicorn_rails creates dirs here after working_directory is bound
     after_reload.call if after_reload
 
@@ -489,23 +498,15 @@ private
     /^#\\(.*)/ =~ File.read(ru) or return
     RACKUP[:optparse].parse!($1.split(/\s+/))
 
-    # XXX ugly as hell, WILL FIX in 2.x (along with Rainbows!/Zbatery)
-    host, port, set_listener, options, daemonize =
-                    eval("[ host, port, set_listener, options, daemonize ]",
-                         TOPLEVEL_BINDING)
-
-    # XXX duplicate code from bin/unicorn{,_rails}
-    set[:listeners] << "#{host}:#{port}" if set_listener
-
-    if daemonize
+    if RACKUP[:daemonize]
       # unicorn_rails wants a default pid path, (not plain 'unicorn')
       if after_reload
         spid = set[:pid]
         pid('tmp/pids/unicorn.pid') if spid.nil? || spid == :unset
       end
       unless RACKUP[:daemonized]
-        Unicorn::Launcher.daemonize!(options)
-        RACKUP[:ready_pipe] = options.delete(:ready_pipe)
+        Unicorn::Launcher.daemonize!(RACKUP[:options])
+        RACKUP[:ready_pipe] = RACKUP[:options].delete(:ready_pipe)
       end
     end
   end
diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb
index a166780..6be5941 100644
--- a/lib/unicorn/const.rb
+++ b/lib/unicorn/const.rb
@@ -8,8 +8,9 @@ module Unicorn
   # Symbols did not really improve things much compared to constants.
   module Const
 
-    # The current version of Unicorn, currently 1.1.4
-    UNICORN_VERSION="1.1.4"
+    # The current version of Unicorn, currently 2.0.0pre
+    # this constant is deprecated and will soon move to Unicorn::VERSION
+    UNICORN_VERSION="2.0.0pre"
 
     DEFAULT_HOST = "0.0.0.0" # default TCP listen host address
     DEFAULT_PORT = 8080      # default TCP listen port
diff --git a/lib/unicorn/launcher.rb b/lib/unicorn/launcher.rb
index 0d415dd..662b603 100644
--- a/lib/unicorn/launcher.rb
+++ b/lib/unicorn/launcher.rb
@@ -20,6 +20,7 @@ module Unicorn::Launcher
   #     to pickup code changes if the original deployment directory
   #     is a symlink or otherwise got replaced.
   def self.daemonize!(options)
+    cfg = Unicorn::Configurator
     $stdin.reopen("/dev/null")
 
     # We only start a new process group if we're not being reexecuted
@@ -52,9 +53,9 @@ module Unicorn::Launcher
       end
     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"
-    Unicorn::Configurator::RACKUP[:daemonized] = true
+    cfg::DEFAULTS[:stderr_path] ||= "/dev/null"
+    cfg::DEFAULTS[:stdout_path] ||= "/dev/null"
+    cfg::RACKUP[:daemonized] = true
   end
 
 end
diff --git a/t/t0003-working_directory.sh b/t/t0003-working_directory.sh
index 53345ae..79988d8 100755
--- a/t/t0003-working_directory.sh
+++ b/t/t0003-working_directory.sh
@@ -1,9 +1,4 @@
 #!/bin/sh
-if test -n "$RBX_SKIP"
-then
-        echo "$0 is broken under Rubinius for now"
-        exit 0
-fi
 . ./test-lib.sh
 
 t_plan 4 "config.ru inside alt working_directory"
diff --git a/t/t0010-reap-logging.sh b/t/t0010-reap-logging.sh
new file mode 100755
index 0000000..93d8c60
--- /dev/null
+++ b/t/t0010-reap-logging.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+. ./test-lib.sh
+t_plan 9 "reap worker logging messages"
+
+t_begin "setup and start" && {
+        unicorn_setup
+        cat >> $unicorn_config <<EOF
+after_fork { |s,w| File.open('$fifo','w') { |f| f.write '.' } }
+EOF
+        unicorn -c $unicorn_config pid.ru &
+        test '.' = $(cat $fifo)
+        unicorn_wait_start
+}
+
+t_begin "kill 1st worker=0" && {
+        pid_1=$(curl http://$listen/)
+        kill -9 $pid_1
+}
+
+t_begin "wait for 2nd worker to start" && {
+        test '.' = $(cat $fifo)
+}
+
+t_begin "ensure log of 1st reap is an ERROR" && {
+        dbgcat r_err
+        grep 'ERROR.*reaped.*worker=0' $r_err | grep $pid_1
+        dbgcat r_err
+        > $r_err
+}
+
+t_begin "kill 2nd worker gracefully" && {
+        pid_2=$(curl http://$listen/)
+        kill -QUIT $pid_2
+}
+
+t_begin "wait for 3rd worker=0 to start " && {
+        test '.' = $(cat $fifo)
+}
+
+t_begin "ensure log of 2nd reap is a INFO" && {
+        grep 'INFO.*reaped.*worker=0' $r_err | grep $pid_2
+        > $r_err
+}
+
+t_begin "killing succeeds" && {
+        kill $unicorn_pid
+        wait
+        kill -0 $unicorn_pid && false
+}
+
+t_begin "check stderr" && {
+        check_stderr
+}
+
+t_done
diff --git a/t/t0303-rails3-alt-working_directory_config.ru.sh b/t/t0303-rails3-alt-working_directory_config.ru.sh
index 444f05a..1433f94 100755
--- a/t/t0303-rails3-alt-working_directory_config.ru.sh
+++ b/t/t0303-rails3-alt-working_directory_config.ru.sh
@@ -1,9 +1,4 @@
 #!/bin/sh
-if test -n "$RBX_SKIP"
-then
-        echo "$0 is broken under Rubinius for now"
-        exit 0
-fi
 . ./test-rails3.sh
 
 t_plan 5 "Rails 3 (beta) inside alt working_directory (w/ config.ru)"
diff --git a/test/exec/test_exec.rb b/test/exec/test_exec.rb
index 1d24ca3..581d5d5 100644
--- a/test/exec/test_exec.rb
+++ b/test/exec/test_exec.rb
@@ -614,7 +614,7 @@ EOF
     results = retry_hit(["http://#{@addr}:#{@port}/"])
     assert_equal String, results[0].class
     assert_shutdown(pid)
-  end unless ENV['RBX_SKIP']
+  end
 
   def test_config_ru_alt_path
     config_path = "#{@tmpdir}/foo.ru"