about summary refs log tree commit homepage
path: root/lib/unicorn/socket.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/unicorn/socket.rb')
-rw-r--r--lib/unicorn/socket.rb158
1 files changed, 97 insertions, 61 deletions
diff --git a/lib/unicorn/socket.rb b/lib/unicorn/socket.rb
index 3f567c6..bc09688 100644
--- a/lib/unicorn/socket.rb
+++ b/lib/unicorn/socket.rb
@@ -1,84 +1,120 @@
+require 'fcntl'
+require 'socket'
+require 'io/nonblock'
+
 # non-portable Socket code goes here:
 class Socket
+  module Constants
+    # configure platform-specific options (only tested on Linux 2.6 so far)
+    case RUBY_PLATFORM
+    when /linux/
+      # from /usr/include/linux/tcp.h
+      TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
+      TCP_CORK = 3 unless defined?(TCP_CORK)
+    when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
+    when /freebsd/
+      # Use the HTTP accept filter if available.
+      # The struct made by pack() is defined in /usr/include/sys/socket.h
+      # as accept_filter_arg
+      unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
+        unless defined?(SO_ACCEPTFILTER_HTTPREADY)
+          SO_ACCEPTFILTER_HTTPREADY = ['httpready',nil].pack('a16a240').freeze
+        end
 
-  # configure platform-specific options (only tested on Linux 2.6 so far)
-  case RUBY_PLATFORM
-  when /linux/
-    # from /usr/include/linux/tcp.h
-    TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
-    TCP_CORK = 3 unless defined?(TCP_CORK)
-
-    def unicorn_server_init
-      self.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1)
-    end
-  when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
-  when /freebsd/
-    # Use the HTTP accept filter if available.
-    # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg
-    unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
-      unless defined?(SO_ACCEPTFILTER_HTTPREADY)
-        SO_ACCEPTFILTER_HTTPREADY = ['httpready',nil].pack('a16a240').freeze
-      end
-
-      def unicorn_server_init
-        self.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, SO_ACCEPTFILTER_HTTPREADY)
       end
     end
   end
+end
 
-  def unicorn_client_init
-    self.sync = true
-    self.nonblock = false
-    self.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
-    self.setsockopt(SOL_TCP, TCP_CORK, 1) if defined?(TCP_CORK)
+class UNIXSocket
+  UNICORN_PEERADDR = '127.0.0.1'.freeze
+  def unicorn_peeraddr
+    UNICORN_PEERADDR
   end
+end
 
+class TCPSocket
   def unicorn_peeraddr
-    Socket.unpack_sockaddr_in(getpeername)
+    peeraddr.last
   end
+end
 
-  # returns the config-friendly name of the current listener socket, this is
-  # useful for config reloads and even works across execs where the Unicorn
-  # binary is replaced
-  def unicorn_addr
-    @unicorn_addr ||= if respond_to?(:getsockname)
-      port, host = Socket.unpack_sockaddr_in(getsockname)
-      "#{host}:#{port}"
-    elsif respond_to?(:getsockname)
-      addr = Socket.unpack_sockaddr_un(getsockname)
-      # strip the pid from the temp socket path
-      addr.gsub!(/\.\d+\.tmp$/, '') or
-        raise ArgumentError, "PID not found in path: #{addr}"
-    else
-      raise ArgumentError, "could not determine unicorn_addr for #{self}"
+module Unicorn
+  module SocketHelper
+    include Socket::Constants
+
+    def set_client_sockopt(sock)
+      sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
+      sock.setsockopt(SOL_TCP, TCP_CORK, 1) if defined?(TCP_CORK)
     end
-  end
 
-  class << self
+    def set_server_sockopt(sock)
+      if defined?(TCP_DEFER_ACCEPT)
+        sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
+      end
+      if defined?(SO_ACCEPTFILTER_HTTPREADY)
+        sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER,
+                        SO_ACCEPTFILTER_HTTPREADY) rescue nil
+      end
+    end
+
+    # 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
+    def bind_listen(address = '0.0.0.0:8080', backlog = 1024)
+      return address if address.kind_of?(Socket)
 
-    # creates a new server, address may be a HOST:PORT or
-    # an absolute path to a UNIX socket.  When creating a UNIX
-    # socket to listen on, we always add a PID suffix to it
-    # when binding and then rename it into its intended name to
-    # atomically replace and start listening for new connections.
-    def unicorn_server_new(address = '0.0.0.0:8080', backlog = 1024)
       domain, bind_addr = if address[0..0] == "/"
-        [ AF_UNIX, pack_sockaddr_un("#{address}.#{$$}.tmp") ]
+        [ AF_UNIX, Socket.pack_sockaddr_un(address) ]
       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
-        [ AF_INET, pack_sockaddr_in($2.to_i, $1) ]
+        [ AF_INET, Socket.pack_sockaddr_in($2.to_i, $1) ]
+      else
+        raise ArgumentError, "Don't know how to bind: #{address}"
       end
-      s = new(domain, SOCK_STREAM, 0)
-      s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) if defined?(SO_REUSEADDR)
-      s.bind(bind_addr)
-      s.listen(backlog)
-      s.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined?(Fcntl::FD_CLOEXEC)
 
-      # atomically replace existing domain socket
-      File.rename("#{address}.#{$$}.tmp", address) if domain == AF_UNIX
-      s
+      sock = Socket.new(domain, SOCK_STREAM, 0)
+      sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) if defined?(SO_REUSEADDR)
+      begin
+        sock.bind(bind_addr)
+      rescue Errno::EADDRINUSE
+        sock.close rescue nil
+        return nil
+      end
+      sock.listen(backlog)
+      set_server_sockopt(sock) if domain == AF_INET
+      sock
     end
 
-  end
+    # 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.
+    def sock_name(sock)
+      case sock
+      when String then sock
+      when UNIXServer
+        Socket.unpack_sockaddr_un(sock.getsockname)
+      when TCPServer
+        Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
+      when Socket
+        begin
+          Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
+        rescue ArgumentError
+          Socket.unpack_sockaddr_un(sock.getsockname)
+        end
+      else
+        raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
+      end
+    end
 
-end
+    # casts a given Socket to be a TCPServer or UNIXServer
+    def server_cast(sock)
+      begin
+        Socket.unpack_sockaddr_in(sock.getsockname)
+        TCPServer.for_fd(sock.fileno)
+      rescue ArgumentError
+        UNIXServer.for_fd(sock.fileno)
+      end
+    end
 
+  end # module SocketHelper
+end # module Unicorn