socket_helper: do not hide errors when setting socket options
[unicorn.git] / lib / unicorn / socket_helper.rb
blob886d0f31a51b15893eebd98e6ffb09537037b325
1 # -*- encoding: binary -*-
3 require 'socket'
5 module Unicorn
6   module SocketHelper
7     include Socket::Constants
9     # configure platform-specific options (only tested on Linux 2.6 so far)
10     case RUBY_PLATFORM
11     when /linux/
12       # from /usr/include/linux/tcp.h
13       TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
14       TCP_CORK = 3 unless defined?(TCP_CORK)
15     when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
16       # Do nothing for httpready, just closing a bug when freebsd <= 5.4
17       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
18     when /freebsd/
19       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
20       # Use the HTTP accept filter if available.
21       # The struct made by pack() is defined in /usr/include/sys/socket.h
22       # as accept_filter_arg
23       unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
24         FILTER_ARG = ['httpready', nil].pack('a16a240')
25       end
26     end
28     def set_tcp_sockopt(sock, opt)
30       # highly portable, but off by default because we don't do keepalive
31       if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
32         sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
33       end
35       unless (val = opt[:tcp_nopush]).nil?
36         val = val ? 1 : 0
37         if defined?(TCP_CORK) # Linux
38           sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
39         elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
40           sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
41         end
42       end
44       # No good reason to ever have deferred accepts off
45       if defined?(TCP_DEFER_ACCEPT)
46         sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1)
47       elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG)
48         sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG)
49       end
50     end
52     def set_server_sockopt(sock, opt)
53       opt ||= {}
55       TCPSocket === sock and set_tcp_sockopt(sock, opt)
57       if opt[:rcvbuf] || opt[:sndbuf]
58         log_buffer_sizes(sock, "before: ")
59         sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
60         sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
61         log_buffer_sizes(sock, " after: ")
62       end
63       sock.listen(opt[:backlog] || 1024)
64       rescue => e
65         if respond_to?(:logger)
66           logger.error "error setting socket options: #{e.inspect}"
67           logger.error e.backtrace.join("\n")
68         end
69     end
71     def log_buffer_sizes(sock, pfx = '')
72       respond_to?(:logger) or return
73       rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
74       sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
75       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
76     end
78     # creates a new server, socket. address may be a HOST:PORT or
79     # an absolute path to a UNIX socket.  address can even be a Socket
80     # object in which case it is immediately returned
81     def bind_listen(address = '0.0.0.0:8080', opt = {})
82       return address unless String === address
84       sock = if address[0] == ?/
85         if File.exist?(address)
86           if File.socket?(address)
87             if self.respond_to?(:logger)
88               logger.info "unlinking existing socket=#{address}"
89             end
90             File.unlink(address)
91           else
92             raise ArgumentError,
93                   "socket=#{address} specified but it is not a socket!"
94           end
95         end
96         old_umask = File.umask(opt[:umask] || 0)
97         begin
98           UNIXServer.new(address)
99         ensure
100           File.umask(old_umask)
101         end
102       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
103         TCPServer.new($1, $2.to_i)
104       else
105         raise ArgumentError, "Don't know how to bind: #{address}"
106       end
107       set_server_sockopt(sock, opt)
108       sock
109     end
111     # Returns the configuration name of a socket as a string.  sock may
112     # be a string value, in which case it is returned as-is
113     # Warning: TCP sockets may not always return the name given to it.
114     def sock_name(sock)
115       case sock
116       when String then sock
117       when UNIXServer
118         Socket.unpack_sockaddr_un(sock.getsockname)
119       when TCPServer
120         Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
121       when Socket
122         begin
123           Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
124         rescue ArgumentError
125           Socket.unpack_sockaddr_un(sock.getsockname)
126         end
127       else
128         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
129       end
130     end
132     # casts a given Socket to be a TCPServer or UNIXServer
133     def server_cast(sock)
134       begin
135         Socket.unpack_sockaddr_in(sock.getsockname)
136         TCPServer.for_fd(sock.fileno)
137       rescue ArgumentError
138         UNIXServer.for_fd(sock.fileno)
139       end
140     end
142   end # module SocketHelper
143 end # module Unicorn