enable TCP_NOPUSH/TCP_CORK by default
[unicorn.git] / lib / unicorn / socket_helper.rb
blob9600b34c6d11473f2ef63cd223612a1bebe161ae
1 # -*- encoding: binary -*-
2 # :enddoc:
3 require 'socket'
5 module Unicorn
6   module SocketHelper
7     include Socket::Constants
9     # :stopdoc:
10     # internal interface, only used by Rainbows!/Zbatery
11     DEFAULTS = {
12       # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
13       # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
14       # This change shouldn't affect Unicorn users behind nginx (a
15       # value of 1 remains an optimization), but Rainbows! users may
16       # want to use a higher value on Linux 2.6.32+ to protect against
17       # denial-of-service attacks
18       :tcp_defer_accept => 1,
20       # FreeBSD, we need to override this to 'dataready' when we
21       # eventually get HTTPS support
22       :accept_filter => 'httpready',
24       # same default value as Mongrel
25       :backlog => 1024,
27       # since we don't do keepalive, we'll always flush-on-close and
28       # this saves packets for everyone.
29       :tcp_nopush => true,
30     }
31     #:startdoc:
33     # configure platform-specific options (only tested on Linux 2.6 so far)
34     case RUBY_PLATFORM
35     when /linux/
36       # from /usr/include/linux/tcp.h
37       TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
39       # do not send out partial frames (Linux)
40       TCP_CORK = 3 unless defined?(TCP_CORK)
41     when /freebsd/
42       # do not send out partial frames (FreeBSD)
43       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
45       def accf_arg(af_name)
46         [ af_name, nil ].pack('a16a240')
47       end if defined?(SO_ACCEPTFILTER)
48     end
50     def set_tcp_sockopt(sock, opt)
51       # highly portable, but off by default because we don't do keepalive
52       if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
53         sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
54       end
56       unless (val = opt[:tcp_nopush]).nil?
57         val = val ? 1 : 0
58         if defined?(TCP_CORK) # Linux
59           sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
60         elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
61           sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
62         end
63       end
65       # No good reason to ever have deferred accepts off
66       # (except maybe benchmarking)
67       if defined?(TCP_DEFER_ACCEPT)
68         # this differs from nginx, since nginx doesn't allow us to
69         # configure the the timeout...
70         seconds = opt[:tcp_defer_accept]
71         seconds = DEFAULTS[:tcp_defer_accept] if seconds == true
72         seconds = 0 unless seconds # nil/false means disable this
73         sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
74       elsif respond_to?(:accf_arg)
75         if name = opt[:accept_filter]
76           begin
77             sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
78           rescue => e
79             logger.error("#{sock_name(sock)} " \
80                          "failed to set accept_filter=#{name} (#{e.inspect})")
81           end
82         end
83       end
84     end
86     def set_server_sockopt(sock, opt)
87       opt = DEFAULTS.merge(opt || {})
89       TCPSocket === sock and set_tcp_sockopt(sock, opt)
91       if opt[:rcvbuf] || opt[:sndbuf]
92         log_buffer_sizes(sock, "before: ")
93         sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
94         sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
95         log_buffer_sizes(sock, " after: ")
96       end
97       sock.listen(opt[:backlog])
98       rescue => e
99         logger.error "error setting socket options: #{e.inspect}"
100         logger.error e.backtrace.join("\n")
101     end
103     def log_buffer_sizes(sock, pfx = '')
104       rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
105       sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
106       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
107     end
109     # creates a new server, socket. address may be a HOST:PORT or
110     # an absolute path to a UNIX socket.  address can even be a Socket
111     # object in which case it is immediately returned
112     def bind_listen(address = '0.0.0.0:8080', opt = {})
113       return address unless String === address
115       sock = if address[0] == ?/
116         if File.exist?(address)
117           if File.socket?(address)
118             begin
119               UNIXSocket.new(address).close
120               # fall through, try to bind(2) and fail with EADDRINUSE
121               # (or succeed from a small race condition we can't sanely avoid).
122             rescue Errno::ECONNREFUSED
123               logger.info "unlinking existing socket=#{address}"
124               File.unlink(address)
125             end
126           else
127             raise ArgumentError,
128                   "socket=#{address} specified but it is not a socket!"
129           end
130         end
131         old_umask = File.umask(opt[:umask] || 0)
132         begin
133           Kgio::UNIXServer.new(address)
134         ensure
135           File.umask(old_umask)
136         end
137       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
138         Kgio::TCPServer.new($1, $2.to_i)
139       else
140         raise ArgumentError, "Don't know how to bind: #{address}"
141       end
142       set_server_sockopt(sock, opt)
143       sock
144     end
146     # Returns the configuration name of a socket as a string.  sock may
147     # be a string value, in which case it is returned as-is
148     # Warning: TCP sockets may not always return the name given to it.
149     def sock_name(sock)
150       case sock
151       when String then sock
152       when UNIXServer
153         Socket.unpack_sockaddr_un(sock.getsockname)
154       when TCPServer
155         Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
156       when Socket
157         begin
158           Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
159         rescue ArgumentError
160           Socket.unpack_sockaddr_un(sock.getsockname)
161         end
162       else
163         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
164       end
165     end
167     module_function :sock_name
169     # casts a given Socket to be a TCPServer or UNIXServer
170     def server_cast(sock)
171       begin
172         Socket.unpack_sockaddr_in(sock.getsockname)
173         Kgio::TCPServer.for_fd(sock.fileno)
174       rescue ArgumentError
175         Kgio::UNIXServer.for_fd(sock.fileno)
176       end
177     end
179   end # module SocketHelper
180 end # module Unicorn