drop redundant IO#close_on_exec=false calls
[unicorn.git] / lib / unicorn / socket_helper.rb
blobc2ba75e368ed75404ba70dab76c039158def5ea5
1 # -*- encoding: binary -*-
2 # :enddoc:
3 require 'socket'
5 module Unicorn
7   # Instead of using a generic Kgio::Socket for everything,
8   # tag TCP sockets so we can use TCP_INFO under Linux without
9   # incurring extra syscalls for Unix domain sockets.
10   # TODO: remove these when we remove kgio
11   TCPClient = Class.new(Kgio::Socket) # :nodoc:
12   class TCPSrv < Kgio::TCPServer # :nodoc:
13     def kgio_tryaccept # :nodoc:
14       super(TCPClient)
15     end
16   end
18   if IO.instance_method(:write).arity == 1 # Ruby <= 2.4
19     require 'unicorn/write_splat'
20     UNIXClient = Class.new(Kgio::Socket) # :nodoc:
21     class UNIXSrv < Kgio::UNIXServer # :nodoc:
22       include Unicorn::WriteSplat
23       def kgio_tryaccept # :nodoc:
24         super(UNIXClient)
25       end
26     end
27     TCPClient.__send__(:include, Unicorn::WriteSplat)
28   else # Ruby 2.5+
29     UNIXSrv = Kgio::UNIXServer
30   end
32   module SocketHelper
34     # internal interface
35     DEFAULTS = {
36       # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
37       # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
38       # This change shouldn't affect unicorn users behind nginx (a
39       # value of 1 remains an optimization).
40       :tcp_defer_accept => 1,
42       # FreeBSD, we need to override this to 'dataready' if we
43       # eventually support non-HTTP/1.x
44       :accept_filter => 'httpready',
46       # same default value as Mongrel
47       :backlog => 1024,
49       # favor latency over bandwidth savings
50       :tcp_nopush => nil,
51       :tcp_nodelay => true,
52     }
54     # configure platform-specific options (only tested on Linux 2.6 so far)
55     def accf_arg(af_name)
56       [ af_name, nil ].pack('a16a240')
57     end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
59     def set_tcp_sockopt(sock, opt)
60       # just in case, even LANs can break sometimes.  Linux sysadmins
61       # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
62       Socket.const_defined?(:SO_KEEPALIVE) and
63         sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
65       if Socket.const_defined?(:TCP_NODELAY)
66         val = opt[:tcp_nodelay]
67         val = DEFAULTS[:tcp_nodelay] if val.nil?
68         sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
69       end
71       val = opt[:tcp_nopush]
72       unless val.nil?
73         if Socket.const_defined?(:TCP_CORK) # Linux
74           sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
75         elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
76           sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
77         end
78       end
80       # No good reason to ever have deferred accepts off in single-threaded
81       # servers (except maybe benchmarking)
82       if Socket.const_defined?(:TCP_DEFER_ACCEPT)
83         # this differs from nginx, since nginx doesn't allow us to
84         # configure the the timeout...
85         seconds = opt[:tcp_defer_accept]
86         seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
87         seconds = 0 unless seconds # nil/false means disable this
88         sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
89       elsif respond_to?(:accf_arg)
90         name = opt[:accept_filter]
91         name = DEFAULTS[:accept_filter] if name.nil?
92         sock.listen(opt[:backlog])
93         got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
94         arg = accf_arg(name)
95         begin
96           sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
97         rescue => e
98           logger.error("#{sock_name(sock)} " \
99                        "failed to set accept_filter=#{name} (#{e.inspect})")
100           logger.error("perhaps accf_http(9) needs to be loaded".freeze)
101         end if arg != got
102       end
103     end
105     def set_server_sockopt(sock, opt)
106       opt = DEFAULTS.merge(opt || {})
108       TCPSocket === sock and set_tcp_sockopt(sock, opt)
110       rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
111       if rcvbuf || sndbuf
112         log_buffer_sizes(sock, "before: ")
113         sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
114         sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
115         log_buffer_sizes(sock, " after: ")
116       end
117       sock.listen(opt[:backlog])
118     rescue => e
119       Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
120     end
122     def log_buffer_sizes(sock, pfx = '')
123       rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
124       sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
125       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
126     end
128     # creates a new server, socket. address may be a HOST:PORT or
129     # an absolute path to a UNIX socket.  address can even be a Socket
130     # object in which case it is immediately returned
131     def bind_listen(address = '0.0.0.0:8080', opt = {})
132       return address unless String === address
134       sock = if address.start_with?('/')
135         if File.exist?(address)
136           if File.socket?(address)
137             begin
138               UNIXSocket.new(address).close
139               # fall through, try to bind(2) and fail with EADDRINUSE
140               # (or succeed from a small race condition we can't sanely avoid).
141             rescue Errno::ECONNREFUSED
142               logger.info "unlinking existing socket=#{address}"
143               File.unlink(address)
144             end
145           else
146             raise ArgumentError,
147                   "socket=#{address} specified but it is not a socket!"
148           end
149         end
150         old_umask = File.umask(opt[:umask] || 0)
151         begin
152           UNIXSrv.new(address)
153         ensure
154           File.umask(old_umask)
155         end
156       elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
157         new_tcp_server($1, $2.to_i, opt.merge(:ipv6=>true))
158       elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
159         new_tcp_server($1, $2.to_i, opt)
160       else
161         raise ArgumentError, "Don't know how to bind: #{address}"
162       end
163       set_server_sockopt(sock, opt)
164       sock
165     end
167     def new_tcp_server(addr, port, opt)
168       # n.b. we set FD_CLOEXEC in the workers
169       sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
170       if opt.key?(:ipv6only)
171         Socket.const_defined?(:IPV6_V6ONLY) or
172           abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
173         sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
174       end
175       sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
176       if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
177         sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
178       end
179       sock.bind(Socket.pack_sockaddr_in(port, addr))
180       sock.autoclose = false
181       TCPSrv.for_fd(sock.fileno)
182     end
184     # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
185     def tcp_name(sock)
186       port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
187       addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
188     end
189     module_function :tcp_name
191     # Returns the configuration name of a socket as a string.  sock may
192     # be a string value, in which case it is returned as-is
193     # Warning: TCP sockets may not always return the name given to it.
194     def sock_name(sock)
195       case sock
196       when String then sock
197       when UNIXServer
198         Socket.unpack_sockaddr_un(sock.getsockname)
199       when TCPServer
200         tcp_name(sock)
201       when Socket
202         begin
203           tcp_name(sock)
204         rescue ArgumentError
205           Socket.unpack_sockaddr_un(sock.getsockname)
206         end
207       else
208         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
209       end
210     end
212     module_function :sock_name
214     # casts a given Socket to be a TCPServer or UNIXServer
215     def server_cast(sock)
216       begin
217         Socket.unpack_sockaddr_in(sock.getsockname)
218         TCPSrv.for_fd(sock.fileno)
219       rescue ArgumentError
220         UNIXSrv.for_fd(sock.fileno)
221       end
222     end
224   end # module SocketHelper
225 end # module Unicorn