diff options
Diffstat (limited to 'lib/unicorn.rb')
-rw-r--r-- | lib/unicorn.rb | 106 |
1 files changed, 45 insertions, 61 deletions
diff --git a/lib/unicorn.rb b/lib/unicorn.rb index f5c1c8c..4a4e2e1 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -377,7 +377,9 @@ module Unicorn # worker. def murder_lazy_workers WORKERS.each_pair do |pid, worker| - Time.now - worker.tempfile.ctime <= @timeout and next + stat = worker.tempfile.stat + stat.mode == 0100000 and next + Time.now - stat.ctime <= @timeout and next logger.error "worker=#{worker.nr} PID:#{pid} is too old, killing" kill_worker(:KILL, pid) # take no prisoners for @timeout violations worker.tempfile.close rescue nil @@ -414,8 +416,6 @@ module Unicorn # 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) - # one syscall less than "client.nonblock = false": - client.fcntl(Fcntl::F_SETFL, File::RDWR) HttpResponse.write(client, @app.call(@request.read(client))) # if we get any error, try to write something back to the client # assuming we haven't closed the socket, but don't get hung up @@ -423,20 +423,15 @@ module Unicorn # the socket is closed at the end of this function rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF client.write_nonblock(Const::ERROR_500_RESPONSE) rescue nil + client.close rescue nil rescue HttpParserError # try to tell the client they're bad client.write_nonblock(Const::ERROR_400_RESPONSE) rescue nil + client.close rescue nil rescue Object => e client.write_nonblock(Const::ERROR_500_RESPONSE) rescue nil + client.close rescue nil logger.error "Read error: #{e.inspect}" logger.error e.backtrace.join("\n") - ensure - begin - client.closed? or client.close - rescue Object => e - logger.error "Client error: #{e.inspect}" - logger.error e.backtrace.join("\n") - end - @request.reset end # gets rid of stuff the worker has no business keeping track of @@ -475,16 +470,18 @@ 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 = 0 # closing anything we IO.select on will raise EBADF trap(:USR1) { nr = -65536; SELF_PIPE.first.close rescue nil } trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil } } - [:TERM, :INT].each { |sig| trap(sig) { exit(0) } } # instant shutdown + [: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 @@ -493,55 +490,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 + alive.chmod(t = 0) begin - ready.each do |sock| - begin - client = begin - sock.accept_nonblock - rescue Errno::EAGAIN - next - end - process_client(client) - rescue Errno::ECONNABORTED - # client closed the socket even before accept - client.close rescue nil - ensure - alive.chmod(nr += 1) if client - break if nr < 0 - end - 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 - ready = LISTENERS - else - master_pid == Process.ppid or exit(0) - 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 SignalException, SystemExit => e - raise e - 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 @@ -585,6 +568,7 @@ module Unicorn @config.reload @config.commit!(self) kill_each_worker(:QUIT) + Unicorn::Util.reopen_logs logger.info "done reloading config_file=#{@config.config_file}" rescue Object => e logger.error "error reloading config_file=#{@config.config_file}: " \ |