PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/unicorn/socket_helper.rb

https://github.com/plasticcube/unicorn
Ruby | 208 lines | 158 code | 19 blank | 31 comment | 20 complexity | 0a9dd79c76b896843fc0082f36dd2fa4 MD5 | raw file
Possible License(s): GPL-3.0
  1. # -*- encoding: binary -*-
  2. # :enddoc:
  3. require 'socket'
  4. module Unicorn
  5. module SocketHelper
  6. # :stopdoc:
  7. include Socket::Constants
  8. # prevents IO objects in here from being GC-ed
  9. IO_PURGATORY = []
  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,
  19. # FreeBSD, we need to override this to 'dataready' if we
  20. # eventually get HTTPS support
  21. :accept_filter => 'httpready',
  22. # same default value as Mongrel
  23. :backlog => 1024,
  24. # favor latency over bandwidth savings
  25. :tcp_nopush => nil,
  26. :tcp_nodelay => true,
  27. }
  28. #:startdoc:
  29. # configure platform-specific options (only tested on Linux 2.6 so far)
  30. case RUBY_PLATFORM
  31. when /linux/
  32. # from /usr/include/linux/tcp.h
  33. TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
  34. # do not send out partial frames (Linux)
  35. TCP_CORK = 3 unless defined?(TCP_CORK)
  36. when /freebsd/
  37. # do not send out partial frames (FreeBSD)
  38. TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
  39. def accf_arg(af_name)
  40. [ af_name, nil ].pack('a16a240')
  41. end if defined?(SO_ACCEPTFILTER)
  42. end
  43. def set_tcp_sockopt(sock, opt)
  44. # just in case, even LANs can break sometimes. Linux sysadmins
  45. # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
  46. sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if defined?(SO_KEEPALIVE)
  47. if defined?(TCP_NODELAY)
  48. val = opt[:tcp_nodelay]
  49. val = DEFAULTS[:tcp_nodelay] if nil == val
  50. sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
  51. end
  52. val = opt[:tcp_nopush]
  53. unless val.nil?
  54. if defined?(TCP_CORK) # Linux
  55. sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
  56. elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is lightly tested (FreeBSD)
  57. sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
  58. end
  59. end
  60. # No good reason to ever have deferred accepts off
  61. # (except maybe benchmarking)
  62. if defined?(TCP_DEFER_ACCEPT)
  63. # this differs from nginx, since nginx doesn't allow us to
  64. # configure the the timeout...
  65. seconds = opt[:tcp_defer_accept]
  66. seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
  67. seconds = 0 unless seconds # nil/false means disable this
  68. sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
  69. elsif respond_to?(:accf_arg)
  70. name = opt[:accept_filter]
  71. name = DEFAULTS[:accept_filter] if nil == name
  72. begin
  73. sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
  74. rescue => e
  75. logger.error("#{sock_name(sock)} " \
  76. "failed to set accept_filter=#{name} (#{e.inspect})")
  77. end
  78. end
  79. end
  80. def set_server_sockopt(sock, opt)
  81. opt = DEFAULTS.merge(opt || {})
  82. TCPSocket === sock and set_tcp_sockopt(sock, opt)
  83. if opt[:rcvbuf] || opt[:sndbuf]
  84. log_buffer_sizes(sock, "before: ")
  85. sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
  86. sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
  87. log_buffer_sizes(sock, " after: ")
  88. end
  89. sock.listen(opt[:backlog])
  90. rescue => e
  91. Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
  92. end
  93. def log_buffer_sizes(sock, pfx = '')
  94. rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
  95. sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
  96. logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
  97. end
  98. # creates a new server, socket. address may be a HOST:PORT or
  99. # an absolute path to a UNIX socket. address can even be a Socket
  100. # object in which case it is immediately returned
  101. def bind_listen(address = '0.0.0.0:8080', opt = {})
  102. return address unless String === address
  103. sock = if address[0] == ?/
  104. if File.exist?(address)
  105. if File.socket?(address)
  106. begin
  107. UNIXSocket.new(address).close
  108. # fall through, try to bind(2) and fail with EADDRINUSE
  109. # (or succeed from a small race condition we can't sanely avoid).
  110. rescue Errno::ECONNREFUSED
  111. logger.info "unlinking existing socket=#{address}"
  112. File.unlink(address)
  113. end
  114. else
  115. raise ArgumentError,
  116. "socket=#{address} specified but it is not a socket!"
  117. end
  118. end
  119. old_umask = File.umask(opt[:umask] || 0)
  120. begin
  121. Kgio::UNIXServer.new(address)
  122. ensure
  123. File.umask(old_umask)
  124. end
  125. elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
  126. new_ipv6_server($1, $2.to_i, opt)
  127. elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
  128. Kgio::TCPServer.new($1, $2.to_i)
  129. else
  130. raise ArgumentError, "Don't know how to bind: #{address}"
  131. end
  132. set_server_sockopt(sock, opt)
  133. sock
  134. end
  135. def new_ipv6_server(addr, port, opt)
  136. opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port)
  137. defined?(IPV6_V6ONLY) or
  138. abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
  139. sock = Socket.new(AF_INET6, SOCK_STREAM, 0)
  140. sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
  141. sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  142. sock.bind(Socket.pack_sockaddr_in(port, addr))
  143. IO_PURGATORY << sock
  144. Kgio::TCPServer.for_fd(sock.fileno)
  145. end
  146. # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
  147. def tcp_name(sock)
  148. port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
  149. /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
  150. end
  151. module_function :tcp_name
  152. # Returns the configuration name of a socket as a string. sock may
  153. # be a string value, in which case it is returned as-is
  154. # Warning: TCP sockets may not always return the name given to it.
  155. def sock_name(sock)
  156. case sock
  157. when String then sock
  158. when UNIXServer
  159. Socket.unpack_sockaddr_un(sock.getsockname)
  160. when TCPServer
  161. tcp_name(sock)
  162. when Socket
  163. begin
  164. tcp_name(sock)
  165. rescue ArgumentError
  166. Socket.unpack_sockaddr_un(sock.getsockname)
  167. end
  168. else
  169. raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
  170. end
  171. end
  172. module_function :sock_name
  173. # casts a given Socket to be a TCPServer or UNIXServer
  174. def server_cast(sock)
  175. begin
  176. Socket.unpack_sockaddr_in(sock.getsockname)
  177. Kgio::TCPServer.for_fd(sock.fileno)
  178. rescue ArgumentError
  179. Kgio::UNIXServer.for_fd(sock.fileno)
  180. end
  181. end
  182. end # module SocketHelper
  183. end # module Unicorn