about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--lib/unicorn.rb62
-rw-r--r--test/unit/test_server.rb2
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