module Unicorn::SocketHelper

Constants

SO_REUSEPORT
TCP_CORK

do not send out partial frames (Linux)

TCP_DEFER_ACCEPT

from /usr/include/linux/tcp.h

TCP_NOPUSH

do not send out partial frames (FreeBSD)

Public Class Methods

sock_name(sock) click to toggle source

Returns the configuration name of a socket as a string. sock may be a string value, in which case it is returned as-is Warning: TCP sockets may not always return the name given to it.

# File lib/unicorn/socket_helper.rb, line 199
def sock_name(sock)
  case sock
  when String then sock
  when UNIXServer
    Socket.unpack_sockaddr_un(sock.getsockname)
  when TCPServer
    tcp_name(sock)
  when Socket
    begin
      tcp_name(sock)
    rescue ArgumentError
      Socket.unpack_sockaddr_un(sock.getsockname)
    end
  else
    raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
  end
end
tcp_name(sock) click to toggle source

returns rfc2732-style (e.g. “[::1]:666”) addresses for IPv6

# File lib/unicorn/socket_helper.rb, line 190
def tcp_name(sock)
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
  /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
end

Public Instance Methods

accf_arg(af_name) click to toggle source
# File lib/unicorn/socket_helper.rb, line 57
def accf_arg(af_name)
  [ af_name, nil ].pack('a16a240')
end
bind_listen(address = '0.0.0.0:8080', opt = {}) click to toggle source

creates a new server, socket. address may be a HOST:PORT or an absolute path to a UNIX socket. address can even be a Socket object in which case it is immediately returned

# File lib/unicorn/socket_helper.rb, line 136
def bind_listen(address = '0.0.0.0:8080', opt = {})
  return address unless String === address

  sock = if address[0] == /
    if File.exist?(address)
      if File.socket?(address)
        begin
          UNIXSocket.new(address).close
          # fall through, try to bind(2) and fail with EADDRINUSE
          # (or succeed from a small race condition we can't sanely avoid).
        rescue Errno::ECONNREFUSED
          logger.info "unlinking existing socket=#{address}"
          File.unlink(address)
        end
      else
        raise ArgumentError,
              "socket=#{address} specified but it is not a socket!"
      end
    end
    old_umask = File.umask(opt[:umask] || 0)
    begin
      Kgio::UNIXServer.new(address)
    ensure
      File.umask(old_umask)
    end
  elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
    new_tcp_server($1, $2.to_i, opt.merge(:ipv6=>true))
  elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
    new_tcp_server($1, $2.to_i, opt)
  else
    raise ArgumentError, "Don't know how to bind: #{address}"
  end
  set_server_sockopt(sock, opt)
  sock
end
log_buffer_sizes(sock, pfx = '') click to toggle source
# File lib/unicorn/socket_helper.rb, line 127
def log_buffer_sizes(sock, pfx = '')
  rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
  sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
  logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
end
new_tcp_server(addr, port, opt) click to toggle source
# File lib/unicorn/socket_helper.rb, line 172
def new_tcp_server(addr, port, opt)
  # n.b. we set FD_CLOEXEC in the workers
  sock = Socket.new(opt[:ipv6] ? AF_INET6 : AF_INET, SOCK_STREAM, 0)
  if opt.key?(:ipv6only)
    defined?(IPV6_V6ONLY) or
      abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
    sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
  end
  sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  if defined?(SO_REUSEPORT) && opt[:reuseport]
    sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
  end
  sock.bind(Socket.pack_sockaddr_in(port, addr))
  prevent_autoclose(sock)
  Kgio::TCPServer.for_fd(sock.fileno)
end
prevent_autoclose(io) click to toggle source
# File lib/unicorn/socket_helper.rb, line 62
def prevent_autoclose(io)
  if io.respond_to?(:autoclose=)
    io.autoclose = false
  else
    IO_PURGATORY << io
  end
end
server_cast(sock) click to toggle source

casts a given Socket to be a TCPServer or UNIXServer

# File lib/unicorn/socket_helper.rb, line 220
def server_cast(sock)
  begin
    Socket.unpack_sockaddr_in(sock.getsockname)
    Kgio::TCPServer.for_fd(sock.fileno)
  rescue ArgumentError
    Kgio::UNIXServer.for_fd(sock.fileno)
  end
end
set_server_sockopt(sock, opt) click to toggle source
# File lib/unicorn/socket_helper.rb, line 111
def set_server_sockopt(sock, opt)
  opt = DEFAULTS.merge(opt || {})

  TCPSocket === sock and set_tcp_sockopt(sock, opt)

  if opt[:rcvbuf] || opt[:sndbuf]
    log_buffer_sizes(sock, "before: ")
    sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
    sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
    log_buffer_sizes(sock, " after: ")
  end
  sock.listen(opt[:backlog])
  rescue => e
    Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
end
set_tcp_sockopt(sock, opt) click to toggle source
# File lib/unicorn/socket_helper.rb, line 70
def set_tcp_sockopt(sock, opt)
  # just in case, even LANs can break sometimes.  Linux sysadmins
  # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
  sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if defined?(SO_KEEPALIVE)

  if defined?(TCP_NODELAY)
    val = opt[:tcp_nodelay]
    val = DEFAULTS[:tcp_nodelay] if nil == val
    sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
  end

  val = opt[:tcp_nopush]
  unless val.nil?
    if defined?(TCP_CORK) # Linux
      sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
    elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is lightly tested (FreeBSD)
      sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
    end
  end

  # No good reason to ever have deferred accepts off
  # (except maybe benchmarking)
  if defined?(TCP_DEFER_ACCEPT)
    # this differs from nginx, since nginx doesn't allow us to
    # configure the the timeout...
    seconds = opt[:tcp_defer_accept]
    seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
    seconds = 0 unless seconds # nil/false means disable this
    sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
  elsif respond_to?(:accf_arg)
    name = opt[:accept_filter]
    name = DEFAULTS[:accept_filter] if nil == name
    begin
      sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
    rescue => e
      logger.error("#{sock_name(sock)} "                         "failed to set accept_filter=#{name} (#{e.inspect})")
    end
  end
end

Private Instance Methods

sock_name(sock) click to toggle source

Returns the configuration name of a socket as a string. sock may be a string value, in which case it is returned as-is Warning: TCP sockets may not always return the name given to it.

# File lib/unicorn/socket_helper.rb, line 199
def sock_name(sock)
  case sock
  when String then sock
  when UNIXServer
    Socket.unpack_sockaddr_un(sock.getsockname)
  when TCPServer
    tcp_name(sock)
  when Socket
    begin
      tcp_name(sock)
    rescue ArgumentError
      Socket.unpack_sockaddr_un(sock.getsockname)
    end
  else
    raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
  end
end
tcp_name(sock) click to toggle source

returns rfc2732-style (e.g. “[::1]:666”) addresses for IPv6

# File lib/unicorn/socket_helper.rb, line 190
def tcp_name(sock)
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
  /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
end