From 33cf16e950d32db97ef03fa304eb2b73e9b3cc87 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 6 Feb 2009 03:09:48 -0800 Subject: Support multiple listeners per-process Use select(2) to multiplex non-blocking accept(2) calls between them. Additionally, aggressively make a bet after accepting clients where we'll try to do a non-blocking accept(2) against the full set of descriptors. This is based on the assumption that if we just accepted connections, we're probably reasonably busy. This should lead to lower latency under high load; but some wasted cycles when requests come in intermitently. By this same logic, we don't really care for the thundering herd problem, either; since it is only noticeable with many (hundreds) of processes when most of them are idle. --- lib/unicorn.rb | 62 +++++++++++++++++++++++++++++++++++------------- test/unit/test_server.rb | 2 +- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index a17ab2d..a301df6 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -52,12 +52,11 @@ module Unicorn # socket and a simple HttpServer.process_client function to # do the heavy lifting with the IO and Ruby. class HttpServer - attr_reader :workers, :logger, :host, :port, :timeout, :nr_workers + attr_reader :workers, :logger, :listeners, :timeout, :nr_workers DEFAULTS = { :timeout => 60, - :host => '0.0.0.0', - :port => 8080, + :listeners => %w(0.0.0.0:8080), :logger => Logger.new(STDERR), :nr_workers => 1 } @@ -74,7 +73,7 @@ module Unicorn instance_variable_set("@#{key.to_s.downcase}", value) end - @socket = Socket.unicorn_server_new("#{@host}:#{@port}", 1024) + @listeners.map! { |address| Socket.unicorn_server_new(address, 1024) } end # Does the majority of the IO processing. It has been written in Ruby using @@ -162,24 +161,53 @@ module Unicorn # to get this hash later. def start BasicSocket.do_not_reverse_lookup = true - @socket.unicorn_server_init if @socket.respond_to?(:unicorn_server_init) + @listeners.each do |sock| + sock.unicorn_server_init if sock.respond_to?(:unicorn_server_init) + end (1..@nr_workers).each do |worker_nr| pid = fork do - alive = true + nr, alive, listeners = 0, true, @listeners trap('TERM') { exit 0 } - trap('QUIT') { alive = false; @socket.close rescue nil } + trap('QUIT') do + alive = false + @listeners.each { |sock| sock.close rescue nil } + end + while alive begin - client, addr = @socket.accept - client.unicorn_client_init - process_client(client) - rescue Errno::EMFILE - logger.error "too many open files" - sleep 0.5 - rescue Errno::ECONNABORTED - # client closed the socket even before accept - client.close rescue nil + nr_before = nr + listeners.each do |sock| + begin + client, addr = begin + sock.accept_nonblock + rescue Errno::EAGAIN + next + end + nr += 1 + client.unicorn_client_init + process_client(client) + rescue Errno::ECONNABORTED + # client closed the socket even before accept + client.close rescue nil + end + alive or exit(0) + end + + # make the following bet: if we accepted clients this round, + # we're probably reasonably busy, so avoid calling select(2) + # and try to do a blind non-blocking accept(2) on everything + # before we sleep again in select + if nr > nr_before + listeners = @listeners + else + begin + ret = IO.select(@listeners, nil, nil, nil) or next + listeners = ret[0] + rescue Errno::EBADF + exit(alive ? 1 : 0) + end + end rescue Object => e if alive logger.error "Unhandled listen loop exception #{e.inspect}." @@ -224,7 +252,7 @@ module Unicorn ensure trap('CHLD', old_chld_handler) - @socket.close rescue nil + @listeners.each { |sock| sock.close rescue nil } end end diff --git a/test/unit/test_server.rb b/test/unit/test_server.rb index df57989..ae8f5b8 100644 --- a/test/unit/test_server.rb +++ b/test/unit/test_server.rb @@ -25,7 +25,7 @@ class WebServerTest < Test::Unit::TestCase @tester = TestHandler.new @app = Rack::URLMap.new('/test' => @tester) redirect_test_io do - @server = HttpServer.new(@app, :Host => "127.0.0.1", :Port => @port) + @server = HttpServer.new(@app, :listeners => [ "127.0.0.1:#{@port}" ] ) end @server.start end -- cgit v1.2.3-24-ge0c7