From 2fbbd59ea9b47e7bbf44ba848ac017905720c2fd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jun 2009 17:33:15 -0700 Subject: Unbind listeners as before stopping workers This allows another process to take our listeners sooner rather than later. (cherry picked from commit 8c2040127770e40e344a927ddc187bf801073e33) --- lib/unicorn.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index f43bb0f..7173a29 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -253,6 +253,7 @@ module Unicorn # Terminates all workers, but does not exit master process def stop(graceful = true) + self.listeners = [] kill_each_worker(graceful ? :QUIT : :TERM) timeleft = @timeout step = 0.2 @@ -263,8 +264,6 @@ module Unicorn (timeleft -= step) > 0 and next kill_each_worker(:KILL) end - ensure - self.listeners = [] end private -- cgit v1.2.3-24-ge0c7 From 98e77adbdcdd96d24c98fd098d9a046f152d6535 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jun 2009 17:47:55 -0700 Subject: Retry listen() on EADDRINUSE 5 times ever 500ms This number of retries and delay taken directly from nginx (cherry picked from commit d247b5d95a3ad2de65cc909db21fdfbc6194b4c9) --- lib/unicorn.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index 7173a29..7acc00b 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -168,16 +168,23 @@ module Unicorn def listen(address, opt = {}.merge(@listener_opts[address] || {})) return if String === address && listener_names.include?(address) - if io = bind_listen(address, opt) + delay, tries = 0.5, 5 + begin + io = bind_listen(address, opt) unless TCPServer === io || UNIXServer === io IO_PURGATORY << io io = server_cast(io) end logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}" LISTENERS << io - else + return io + rescue Errno::EADDRINUSE => err logger.error "adding listener failed addr=#{address} (in use)" - raise Errno::EADDRINUSE, address + raise err if tries == 0 + tries -= 1 + logger.error "retrying in #{delay} seconds (#{tries} tries left)" + sleep(delay) + retry end end -- cgit v1.2.3-24-ge0c7 From f63f79f24ea3c6419b2664cbda5f2cbb41225bbe Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jun 2009 19:16:43 -0700 Subject: Re-add support for non-portable socket options Now that we support tunnelling arbitrary protocols over HTTP as well as "100 Continue" responses, TCP_NODELAY actually becomes useful to us. TCP_NODELAY is actually reasonably portable nowadays; even. While we're adding non-portable options, TCP_CORK/TCP_NOPUSH can be enabled, too. Unlike some other servers, these can't be disabled explicitly/intelligently to force a flush, however. However, these may still improve performance with "normal" HTTP applications (Mongrel has always had TCP_CORK enabled in Linux). While we're adding OS-specific features, we might as well support TCP_DEFER_ACCEPT in Linux and FreeBSD the "httpready" accept filter to prevent abuse. These options can all be enabled on a per-listener basis. (cherry picked from commit 563d03f649ef31d2aec3505cbbed1e015493b8fc) --- lib/unicorn/configurator.rb | 24 ++++++++++++++++++++- lib/unicorn/socket_helper.rb | 50 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index a432f64..860962a 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -207,7 +207,24 @@ module Unicorn # specified. # # Defaults: operating system defaults - def listen(address, opt = { :backlog => 1024 }) + # + # +tcp_nodelay+: disables Nagle's algorithm on TCP sockets + # + # This has no effect on UNIX sockets. + # + # Default: operating system defaults (usually Nagle's algorithm enabled) + # + # +tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD + # + # This will prevent partial TCP frames from being sent out. + # Enabling +tcp_nopush+ is generally not needed or recommended as + # controlling +tcp_nodelay+ already provides sufficient latency + # reduction whereas Unicorn does not know when the best times are + # for flushing corked sockets. + # + # This has no effect on UNIX sockets. + # + def listen(address, opt = {}) address = expand_addr(address) if String === address Hash === @set[:listener_opts] or @@ -217,6 +234,11 @@ module Unicorn Integer === value or raise ArgumentError, "not an integer: #{key}=#{value.inspect}" end + [ :tcp_nodelay, :tcp_nopush ].each do |key| + (value = opt[key]).nil? and next + TrueClass === value || FalseClass === value or + raise ArgumentError, "not boolean: #{key}=#{value.inspect}" + end @set[:listener_opts][address].merge!(opt) end diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb index 850ad85..f8e3725 100644 --- a/lib/unicorn/socket_helper.rb +++ b/lib/unicorn/socket_helper.rb @@ -4,8 +4,56 @@ module Unicorn module SocketHelper include Socket::Constants + # configure platform-specific options (only tested on Linux 2.6 so far) + case RUBY_PLATFORM + when /linux/ + # from /usr/include/linux/tcp.h + TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT) + TCP_CORK = 3 unless defined?(TCP_CORK) + when /freebsd(([1-4]\..{1,2})|5\.[0-4])/ + # Do nothing for httpready, just closing a bug when freebsd <= 5.4 + TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH) + when /freebsd/ + TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH) + # Use the HTTP accept filter if available. + # The struct made by pack() is defined in /usr/include/sys/socket.h + # as accept_filter_arg + # We won't be seupportin the "dataready" filter unlike nginx + # since we only support HTTP and no other protocols + unless `/sbin/sysctl -nq net.inet.accf.http`.empty? + HTTPREADY = ['httpready', nil].pack('a16a240').freeze + end + end + + def set_tcp_sockopt(sock, opt) + + # highly portable, but off by default because we don't do keepalive + if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil? + sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0) rescue nil + end + + unless (val = opt[:tcp_nopush]).nil? + val = val ? 1 : 0 + if defined?(TCP_CORK) # Linux + sock.setsockopt(IPPROTO_TCP, TCP_CORK, val) rescue nil + elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD) + sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val) rescue nil + end + end + + # No good reason to ever have deferred accepts off + if defined?(TCP_DEFER_ACCEPT) + sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil + elsif defined?(SO_ACCEPTFILTER) && defined?(HTTPREADY) + sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, HTTPREADY) rescue nil + end + end + def set_server_sockopt(sock, opt) opt ||= {} + + TCPSocket === sock and set_tcp_sockopt(sock, opt) + if opt[:rcvbuf] || opt[:sndbuf] log_buffer_sizes(sock, "before: ") sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf] @@ -25,7 +73,7 @@ module Unicorn # creates a new server, socket. address may be a HOST:PORT or # an absolute path to a UNIX socket. address can even be a Socket # object in which case it is immediately returned - def bind_listen(address = '0.0.0.0:8080', opt = { :backlog => 1024 }) + def bind_listen(address = '0.0.0.0:8080', opt = {}) return address unless String === address sock = if address[0..0] == "/" -- cgit v1.2.3-24-ge0c7 From c8baa439a99d6a898ee3d5b980390a36d23eea2e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 4 Jul 2009 16:30:59 -0700 Subject: Minor cleanups to core (cherry picked from commit ec70433f84664af0dff1336845ddd51f50a714a3) --- lib/unicorn.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index 7acc00b..e0dd4ef 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -51,7 +51,7 @@ module Unicorn # don't rely on Dir.pwd here since it's not symlink-aware, and # symlink dirs are the default with Capistrano... :cwd => `/bin/sh -c pwd`.chomp("\n"), - :zero => $0.dup, + 0 => $0.dup, } Worker = Struct.new(:nr, :tempfile) unless defined?(Worker) @@ -363,7 +363,7 @@ module Unicorn listener_fds = LISTENERS.map { |sock| sock.fileno } ENV['UNICORN_FD'] = listener_fds.join(',') Dir.chdir(START_CTX[:cwd]) - cmd = [ START_CTX[:zero] ] + START_CTX[:argv] + cmd = [ START_CTX[0] ].concat(START_CTX[:argv]) # avoid leaking FDs we don't know about, but let before_exec # unset FD_CLOEXEC, if anything else in the app eventually @@ -459,7 +459,7 @@ module Unicorn # traps for USR1, USR2, and HUP may be set in the @after_fork Proc # by the user. def init_worker_process(worker) - QUEUE_SIGS.each { |sig| trap(sig, 'IGNORE') } + QUEUE_SIGS.each { |sig| trap(sig, nil) } trap(:CHLD, 'DEFAULT') SIG_QUEUE.clear proc_name "worker[#{worker.nr}]" @@ -615,8 +615,8 @@ module Unicorn end def proc_name(tag) - $0 = ([ File.basename(START_CTX[:zero]), tag ] + - START_CTX[:argv]).join(' ') + $0 = ([ File.basename(START_CTX[0]), tag + ]).concat(START_CTX[:argv]).join(' ') end def redirect_io(io, path) -- cgit v1.2.3-24-ge0c7 From 74bbd50328663257c8ee135be7fd2532ef7d1933 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 9 Jul 2009 01:19:24 -0700 Subject: always set FD_CLOEXEC on sockets post-accept() FD_CLOEXEC is not guaranteed to be inherited by the accept()-ed descriptors even if the listener socket has this set. This can be a problem with applications that fork+exec long running background processes. Thanks to Paul Sponagl for helping me find this. --- lib/unicorn.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index e0dd4ef..aac530b 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -436,6 +436,7 @@ 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(app, client) + client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) 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 -- cgit v1.2.3-24-ge0c7 From f3d7ea324c893ba9d8787afbaa9d2e55fcae0133 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 8 Jul 2009 16:04:14 -0700 Subject: unicorn 0.8.2 --- CHANGELOG | 1 + lib/unicorn/const.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 64d03ab..cc7d5fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +v0.8.2 - socket handling bugfixes and usability tweaks v0.8.1 - safer timeout handling, more consistent reload behavior v0.8.0 - enforce Rack dependency, minor performance improvements and fixes v0.7.1 - minor fixes, cleanups and documentation improvements diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb index 250868d..72a4d61 100644 --- a/lib/unicorn/const.rb +++ b/lib/unicorn/const.rb @@ -5,7 +5,7 @@ module Unicorn # gave about a 3% to 10% performance improvement over using the strings directly. # Symbols did not really improve things much compared to constants. module Const - UNICORN_VERSION="0.8.1".freeze + UNICORN_VERSION="0.8.2".freeze DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address DEFAULT_PORT = "8080".freeze # default TCP listen port -- cgit v1.2.3-24-ge0c7