From e74dac7aebd453449d632c697cb05a277c998690 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Apr 2009 13:36:49 -0700 Subject: SIGTT{IN,OU} {in,de}crements worker_processes 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. --- SIGNALS | 4 ++++ TODO | 6 ------ lib/unicorn.rb | 20 ++++++++++++++++---- test/exec/test_exec.rb | 22 ++++++++++++++++++++++ 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") -- cgit v1.2.3-24-ge0c7