avoid IO_PURGATORY on Ruby 1.9+
[unicorn.git] / lib / unicorn / socket_helper.rb
blob820b778b2e1b9052152b5ad1a47bebaa1d35f470
1 # -*- encoding: binary -*-
2 # :enddoc:
3 require 'socket'
5 module Unicorn
6   module SocketHelper
7     # :stopdoc:
8     include Socket::Constants
10     # prevents IO objects in here from being GC-ed
11     # kill this when we drop 1.8 support
12     IO_PURGATORY = []
14     # internal interface, only used by Rainbows!/Zbatery
15     DEFAULTS = {
16       # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
17       # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
18       # This change shouldn't affect Unicorn users behind nginx (a
19       # value of 1 remains an optimization), but Rainbows! users may
20       # want to use a higher value on Linux 2.6.32+ to protect against
21       # denial-of-service attacks
22       :tcp_defer_accept => 1,
24       # FreeBSD, we need to override this to 'dataready' if we
25       # eventually get HTTPS support
26       :accept_filter => 'httpready',
28       # same default value as Mongrel
29       :backlog => 1024,
31       # favor latency over bandwidth savings
32       :tcp_nopush => nil,
33       :tcp_nodelay => true,
34     }
35     #:startdoc:
37     # configure platform-specific options (only tested on Linux 2.6 so far)
38     case RUBY_PLATFORM
39     when /linux/
40       # from /usr/include/linux/tcp.h
41       TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
43       # do not send out partial frames (Linux)
44       TCP_CORK = 3 unless defined?(TCP_CORK)
46       # Linux got SO_REUSEPORT in 3.9, BSDs have had it for ages
47       unless defined?(SO_REUSEPORT)
48         if RUBY_PLATFORM =~ /(?:alpha|mips|parisc|sparc)/
49           SO_REUSEPORT = 0x0200 # untested
50         else
51           SO_REUSEPORT = 15 # only tested on x86_64 and i686
52         end
53       end
54     when /freebsd/
55       # do not send out partial frames (FreeBSD)
56       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
58       def accf_arg(af_name)
59         [ af_name, nil ].pack('a16a240')
60       end if defined?(SO_ACCEPTFILTER)
61     end
63     def prevent_autoclose(io)
64       if io.respond_to?(:autoclose=)
65         io.autoclose = false
66       else
67         IO_PURGATORY << io
68       end
69     end
71     def set_tcp_sockopt(sock, opt)
72       # just in case, even LANs can break sometimes.  Linux sysadmins
73       # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
74       sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if defined?(SO_KEEPALIVE)
76       if defined?(TCP_NODELAY)
77         val = opt[:tcp_nodelay]
78         val = DEFAULTS[:tcp_nodelay] if nil == val
79         sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
80       end
82       val = opt[:tcp_nopush]
83       unless val.nil?
84         if defined?(TCP_CORK) # Linux
85           sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
86         elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is lightly tested (FreeBSD)
87           sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
88         end
89       end
91       # No good reason to ever have deferred accepts off
92       # (except maybe benchmarking)
93       if defined?(TCP_DEFER_ACCEPT)
94         # this differs from nginx, since nginx doesn't allow us to
95         # configure the the timeout...
96         seconds = opt[:tcp_defer_accept]
97         seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
98         seconds = 0 unless seconds # nil/false means disable this
99         sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
100       elsif respond_to?(:accf_arg)
101         name = opt[:accept_filter]
102         name = DEFAULTS[:accept_filter] if nil == name
103         begin
104           sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
105         rescue => e
106           logger.error("#{sock_name(sock)} " \
107                        "failed to set accept_filter=#{name} (#{e.inspect})")
108         end
109       end
110     end
112     def set_server_sockopt(sock, opt)
113       opt = DEFAULTS.merge(opt || {})
115       TCPSocket === sock and set_tcp_sockopt(sock, opt)
117       if opt[:rcvbuf] || opt[:sndbuf]
118         log_buffer_sizes(sock, "before: ")
119         sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
120         sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
121         log_buffer_sizes(sock, " after: ")
122       end
123       sock.listen(opt[:backlog])
124       rescue => e
125         Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
126     end
128     def log_buffer_sizes(sock, pfx = '')
129       rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
130       sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
131       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
132     end
134     # creates a new server, socket. address may be a HOST:PORT or
135     # an absolute path to a UNIX socket.  address can even be a Socket
136     # object in which case it is immediately returned
137     def bind_listen(address = '0.0.0.0:8080', opt = {})
138       return address unless String === address
140       sock = if address[0] == ?/
141         if File.exist?(address)
142           if File.socket?(address)
143             begin
144               UNIXSocket.new(address).close
145               # fall through, try to bind(2) and fail with EADDRINUSE
146               # (or succeed from a small race condition we can't sanely avoid).
147             rescue Errno::ECONNREFUSED
148               logger.info "unlinking existing socket=#{address}"
149               File.unlink(address)
150             end
151           else
152             raise ArgumentError,
153                   "socket=#{address} specified but it is not a socket!"
154           end
155         end
156         old_umask = File.umask(opt[:umask] || 0)
157         begin
158           Kgio::UNIXServer.new(address)
159         ensure
160           File.umask(old_umask)
161         end
162       elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
163         new_tcp_server($1, $2.to_i, opt.merge(:ipv6=>true))
164       elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
165         new_tcp_server($1, $2.to_i, opt)
166       else
167         raise ArgumentError, "Don't know how to bind: #{address}"
168       end
169       set_server_sockopt(sock, opt)
170       sock
171     end
173     def new_tcp_server(addr, port, opt)
174       # n.b. we set FD_CLOEXEC in the workers
175       sock = Socket.new(opt[:ipv6] ? AF_INET6 : AF_INET, SOCK_STREAM, 0)
176       if opt.key?(:ipv6only)
177         defined?(IPV6_V6ONLY) or
178           abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
179         sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
180       end
181       sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
182       if defined?(SO_REUSEPORT) && opt[:reuseport]
183         sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
184       end
185       sock.bind(Socket.pack_sockaddr_in(port, addr))
186       prevent_autoclose(sock)
187       Kgio::TCPServer.for_fd(sock.fileno)
188     end
190     # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
191     def tcp_name(sock)
192       port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
193       /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
194     end
195     module_function :tcp_name
197     # Returns the configuration name of a socket as a string.  sock may
198     # be a string value, in which case it is returned as-is
199     # Warning: TCP sockets may not always return the name given to it.
200     def sock_name(sock)
201       case sock
202       when String then sock
203       when UNIXServer
204         Socket.unpack_sockaddr_un(sock.getsockname)
205       when TCPServer
206         tcp_name(sock)
207       when Socket
208         begin
209           tcp_name(sock)
210         rescue ArgumentError
211           Socket.unpack_sockaddr_un(sock.getsockname)
212         end
213       else
214         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
215       end
216     end
218     module_function :sock_name
220     # casts a given Socket to be a TCPServer or UNIXServer
221     def server_cast(sock)
222       begin
223         Socket.unpack_sockaddr_in(sock.getsockname)
224         Kgio::TCPServer.for_fd(sock.fileno)
225       rescue ArgumentError
226         Kgio::UNIXServer.for_fd(sock.fileno)
227       end
228     end
230   end # module SocketHelper
231 end # module Unicorn