summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-04-24 13:36:49 -0700
committerEric Wong <normalperson@yhbt.net>2009-04-24 13:36:49 -0700
commite74dac7aebd453449d632c697cb05a277c998690 (patch)
tree303bfd4efa97caa9fe4248b1110dcd1dcf758101
parent3baf3e68c1a7f4d1187e292cc5001fb1dc379194 (diff)
This allows dynamic tuning of the worker_processes count without
having to restart existing ones.  This also allows
worker_processes to be set to a low initial amount in the config
file for low-traffic deployments/upgrades and then scaled up as
the old processes are killed off.

Remove the proposed reexec_worker_processes from TODO since this
is far more flexible and powerful.

This will allow not-yet-existent third-party monitoring tools to
dynamically change and scale worker processes according to site
load without increasing the complexity of Unicorn itself.
-rw-r--r--SIGNALS4
-rw-r--r--TODO6
-rw-r--r--lib/unicorn.rb20
-rw-r--r--test/exec/test_exec.rb22
4 files changed, 42 insertions, 10 deletions
diff --git a/SIGNALS b/SIGNALS
index fc0fa31..7cd087f 100644
--- a/SIGNALS
+++ b/SIGNALS
@@ -25,6 +25,10 @@ processes are documented here as well.
  * WINCH - gracefully stops workers but keep the master running.
    This will only work for daemonized processes.
 
+ * TTIN - increment the number of worker processes by one
+
+ * TTOU - decrement the number of worker processes by one
+
 === Worker Processes
 
 Sending signals directly to the worker processes should not normally be
diff --git a/TODO b/TODO
index e572d0a..204bb7d 100644
--- a/TODO
+++ b/TODO
@@ -1,11 +1,5 @@
 == 1.0.0
 
-  * reexec_worker_processes config option:
-    This is the number of worker processes to startup initially
-    when being reexecuted.
-
-    Default: worker_processes/2 + 1
-
   * integration tests with nginx including bad client handling
 
   * tests for timeout
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index 9510e4b..33be99a 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -101,7 +101,7 @@ module Unicorn
       raise ArgumentError, "no listeners" if LISTENERS.empty?
       self.pid = @config[:pid]
       build_app! if @preload_app
-      spawn_missing_workers
+      maintain_worker_count
       self
     end
 
@@ -192,7 +192,7 @@ module Unicorn
           case SIG_QUEUE.shift
           when nil
             murder_lazy_workers
-            spawn_missing_workers if respawn
+            maintain_worker_count if respawn
             master_sleep
           when :QUIT # graceful shutdown
             break
@@ -214,6 +214,10 @@ module Unicorn
             else
               logger.info "SIGWINCH ignored because we're not daemonized"
             end
+          when :TTIN
+            @worker_processes += 1
+          when :TTOU
+            @worker_processes -= 1 if @worker_processes > 0
           when :HUP
             respawn = true
             if @config.config_file
@@ -257,7 +261,8 @@ module Unicorn
     private
 
     # list of signals we care about and trap in master.
-    QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP ].freeze
+    QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP,
+                   :TTIN, :TTOU ].freeze
 
     # defer a signal for later processing in #join (master process)
     def trap_deferred(signal)
@@ -380,7 +385,6 @@ module Unicorn
     end
 
     def spawn_missing_workers
-      return if WORKERS.size == @worker_processes
       (0...@worker_processes).each do |worker_nr|
         WORKERS.values.include?(worker_nr) and next
         begin
@@ -399,6 +403,14 @@ module Unicorn
       end
     end
 
+    def maintain_worker_count
+      (off = WORKERS.size - @worker_processes) == 0 and return
+      off < 0 and return spawn_missing_workers
+      WORKERS.each_pair { |pid,w|
+        w.nr >= @worker_processes and kill_worker(:QUIT, pid) rescue nil
+      }
+    end
+
     # once a client is accepted, it is processed in its entirety here
     # in 3 easy steps: read request, call app, write app response
     def process_client(client)
diff --git a/test/exec/test_exec.rb b/test/exec/test_exec.rb
index b0462dc..014b270 100644
--- a/test/exec/test_exec.rb
+++ b/test/exec/test_exec.rb
@@ -98,6 +98,28 @@ end
     assert_shutdown(pid)
   end
 
+  def test_ttin_ttou
+    File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
+    pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
+    log = "test_stderr.#{pid}.log"
+    wait_master_ready(log)
+    [ 2, 3].each { |i|
+      assert_nothing_raised { Process.kill(:TTIN, pid) }
+      wait_workers_ready(log, i)
+    }
+    File.truncate(log, 0)
+    reaped = nil
+    [ 2, 1, 0].each { |i|
+      assert_nothing_raised { Process.kill(:TTOU, pid) }
+      DEFAULT_TRIES.times {
+        sleep DEFAULT_RES
+        reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
+        break if reaped.size == 1
+      }
+      assert_equal 1, reaped.size
+    }
+  end
+
   def test_help
     redirect_test_io do
       assert(system($unicorn_bin, "-h"), "help text returns true")