about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-05-03 22:11:11 -0700
committerEric Wong <normalperson@yhbt.net>2009-05-03 22:11:11 -0700
commitd613f0aaa2758e294e440766c02243a2023b599c (patch)
tree919d855a14747471a292f27508a2d430ae61e54a
parentea1ef5d2c6d8fe612d620ee18250017da4ae5229 (diff)
downloadunicorn-d613f0aaa2758e294e440766c02243a2023b599c.tar.gz
First, reduce no-op fchmod syscalls under heavy traffic.
gettimeofday(2) is a cheaper syscall than fchmod(2).  Since
ctime resolution is only in seconds on most filesystems (and
Ruby can only get to seconds AFAIK), we can avoid fchmod(2)
happening within the same second.  This allows us to cheat on
synthetic benchmarks where performance is measured in
requests-per-second and not seconds-per-request :)

Secondly, cleanup the acceptor loop and avoid nested
begins/loops as much as possible.  If we got ECONNABORTED, then
there's no way the client variable would've been set correctly,
either.  If there was something there, then it is at the mercy
of the garbage collector because a method can't both return a
value and raise an exception.
-rw-r--r--lib/unicorn.rb81
1 files changed, 37 insertions, 44 deletions
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index a539960..aadd7af 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -469,7 +469,7 @@ module Unicorn
       nr = 0 # this becomes negative if we need to reopen logs
       alive = worker.tempfile # tempfile is our lifeline to the master process
       ready = LISTENERS
-      client = nil
+      t = ti = Time.now.to_i
 
       # closing anything we IO.select on will raise EBADF
       trap(:USR1) { nr = -65536; SELF_PIPE.first.close rescue nil }
@@ -477,8 +477,10 @@ module Unicorn
       [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
       @logger.info "worker=#{worker.nr} ready"
 
-      while alive
-        reopen_worker_logs(worker.nr) if nr < 0
+      begin
+        nr < 0 and reopen_worker_logs(worker.nr)
+        nr = 0
+
         # we're a goner in @timeout seconds anyways if alive.chmod
         # breaks, so don't trap the exception.  Using fchmod() since
         # futimes() is not available in base Ruby and I very strongly
@@ -487,50 +489,41 @@ module Unicorn
         # changes with chmod doesn't update ctime on all filesystems; so
         # we change our counter each and every time (after process_client
         # and before IO.select).
-        alive.chmod(nr = 0)
+        t == (ti = Time.now.to_i) or alive.chmod(t = ti)
+
+        ready.each do |sock|
+          begin
+            process_client(sock.accept_nonblock)
+            nr += 1
+            t == (ti = Time.now.to_i) or alive.chmod(t = ti)
+          rescue Errno::EAGAIN, Errno::ECONNABORTED
+          end
+          break if nr < 0
+        end
 
+        # make the following bet: if we accepted clients this round,
+        # we're probably reasonably busy, so avoid calling select()
+        # and do a speculative accept_nonblock on every listener
+        # before we sleep again in select().
+        redo unless nr == 0 # (nr < 0) => reopen logs
+
+        master_pid == Process.ppid or return
+        t == (ti = Time.now.to_i) or alive.chmod(t = ti)
         begin
-          ready.each do |sock|
-            begin
-              client = begin
-                sock.accept_nonblock
-              rescue Errno::EAGAIN
-                next
-              end
-              process_client(client)
-              alive.chmod(nr += 1)
-            rescue Errno::ECONNABORTED
-              # client closed the socket even before accept
-              client.close rescue nil
-            end
-            break if nr < 0
-          end
-          client = nil
-
-          # make the following bet: if we accepted clients this round,
-          # we're probably reasonably busy, so avoid calling select()
-          # and do a speculative accept_nonblock on every listener
-          # before we sleep again in select().
-          if nr == 0 # (nr < 0) => reopen logs
-            master_pid == Process.ppid or return
-            alive.chmod(nr += 1)
-            begin
-              # timeout used so we can detect parent death:
-              ret = IO.select(LISTENERS, nil, SELF_PIPE, @timeout) or next
-              ready = ret.first
-            rescue Errno::EINTR
-              ready = LISTENERS
-            rescue Errno::EBADF => e
-              nr < 0 or exit(alive ? 1 : 0)
-            end
-          end
-        rescue Object => e
-          if alive
-            logger.error "Unhandled listen loop exception #{e.inspect}."
-            logger.error e.backtrace.join("\n")
-          end
+          # timeout used so we can detect parent death:
+          ret = IO.select(LISTENERS, nil, SELF_PIPE, @timeout) or redo
+          ready = ret.first
+        rescue Errno::EINTR
+          ready = LISTENERS
+        rescue Errno::EBADF
+          nr < 0 or return
         end
-      end
+      rescue Object => e
+        if alive
+          logger.error "Unhandled listen loop exception #{e.inspect}."
+          logger.error e.backtrace.join("\n")
+        end
+      end while alive
     end
 
     # delivers a signal to a worker and fails gracefully if the worker