about summary refs log tree commit homepage
path: root/lib/unicorn/configurator.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/unicorn/configurator.rb')
-rw-r--r--lib/unicorn/configurator.rb159
1 files changed, 114 insertions, 45 deletions
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb
index dd9ae3b..a432f64 100644
--- a/lib/unicorn/configurator.rb
+++ b/lib/unicorn/configurator.rb
@@ -1,5 +1,4 @@
-require 'unicorn/socket'
-require 'unicorn/const'
+require 'socket'
 require 'logger'
 
 module Unicorn
@@ -8,42 +7,40 @@ module Unicorn
   #
   # Example (when used with the unicorn config file):
   #   worker_processes 4
-  #   listeners %w(0.0.0.0:9292 /tmp/my_app.sock)
+  #   listen '/tmp/my_app.sock', :backlog => 1
+  #   listen '0.0.0.0:9292'
   #   timeout 10
   #   pid "/tmp/my_app.pid"
-  #   after_fork do |server,worker_nr|
-  #     server.listen("127.0.0.1:#{9293 + worker_nr}") rescue nil
+  #   after_fork do |server,worker|
+  #     server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
   #   end
   class Configurator
-    include ::Unicorn::SocketHelper
-
     # The default logger writes its output to $stderr
     DEFAULT_LOGGER = Logger.new($stderr) unless defined?(DEFAULT_LOGGER)
 
     # Default settings for Unicorn
     DEFAULTS = {
       :timeout => 60,
-      :listeners => [ Const::DEFAULT_LISTEN ],
+      :listeners => [],
       :logger => DEFAULT_LOGGER,
       :worker_processes => 1,
-      :after_fork => lambda { |server, worker_nr|
-          server.logger.info("worker=#{worker_nr} spawned pid=#{$$}")
+      :after_fork => lambda { |server, worker|
+          server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
 
           # per-process listener ports for debugging/admin:
           # "rescue nil" statement is needed because USR2 will
           # cause the master process to reexecute itself and the
           # per-worker ports can be taken, necessitating another
           # HUP after QUIT-ing the original master:
-          # server.listen("127.0.0.1:#{8081 + worker_nr}") rescue nil
+          # server.listen("127.0.0.1:#{8081 + worker.nr}") rescue nil
         },
-      :before_fork => lambda { |server, worker_nr|
-          server.logger.info("worker=#{worker_nr} spawning...")
+      :before_fork => lambda { |server, worker|
+          server.logger.info("worker=#{worker.nr} spawning...")
         },
       :before_exec => lambda { |server|
           server.logger.info("forked child re-executing...")
         },
       :pid => nil,
-      :backlog => 1024,
       :preload_app => false,
       :stderr_path => nil,
       :stdout_path => nil,
@@ -83,23 +80,6 @@ module Unicorn
       @set[key]
     end
 
-    # Changes the listen() syscall backlog to +nr+ for yet-to-be-created
-    # sockets.  Due to limitations of the OS, this cannot affect
-    # existing listener sockets in any way, sockets must be completely
-    # closed and rebound (inherited sockets preserve their existing
-    # backlog setting).  Some operating systems allow negative values
-    # here to specify the maximum allowable value.  See the listen(2)
-    # syscall documentation of your OS for the exact semantics of this.
-    #
-    # If you are running unicorn on multiple machines, lowering this number
-    # can help your load balancer detect when a machine is overloaded
-    # and give requests to a different machine.
-    def backlog(nr)
-      Integer === nr or raise ArgumentError,
-         "not an integer: backlog=#{nr.inspect}"
-      @set[:backlog] = nr
-    end
-
     # sets object to the +new+ Logger-like object.  The new logger-like
     # object must respond to the following methods:
     #  +debug+, +info+, +warn+, +error+, +fatal+, +close+
@@ -116,23 +96,37 @@ module Unicorn
     # the worker after forking.  The following is an example hook which adds
     # a per-process listener to every worker:
     #
-    #  after_fork do |server,worker_nr|
+    #  after_fork do |server,worker|
     #    # per-process listener ports for debugging/admin:
     #    # "rescue nil" statement is needed because USR2 will
     #    # cause the master process to reexecute itself and the
     #    # per-worker ports can be taken, necessitating another
     #    # HUP after QUIT-ing the original master:
-    #    server.listen("127.0.0.1:#{9293 + worker_nr}") rescue nil
+    #    server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
+    #
+    #    # drop permissions to "www-data" in the worker
+    #    # generally there's no reason to start Unicorn as a priviledged user
+    #    # as it is not recommended to expose Unicorn to public clients.
+    #    uid, gid = Process.euid, Process.egid
+    #    user, group = 'www-data', 'www-data'
+    #    target_uid = Etc.getpwnam(user).uid
+    #    target_gid = Etc.getgrnam(group).gid
+    #    worker.tempfile.chown(target_uid, target_gid)
+    #    if uid != target_uid || gid != target_gid
+    #      Process.initgroups(user, target_gid)
+    #      Process::GID.change_privilege(target_gid)
+    #      Process::UID.change_privilege(target_uid)
+    #    end
     #  end
-    def after_fork(&block)
-      set_hook(:after_fork, block)
+    def after_fork(*args, &block)
+      set_hook(:after_fork, block_given? ? block : args[0])
     end
 
     # sets before_fork got be a given Proc object.  This Proc
     # object will be called by the master process before forking
     # each worker.
-    def before_fork(&block)
-      set_hook(:before_fork, block)
+    def before_fork(*args, &block)
+      set_hook(:before_fork, block_given? ? block : args[0])
     end
 
     # sets the before_exec hook to a given Proc object.  This
@@ -141,20 +135,22 @@ module Unicorn
     # for freeing certain OS resources that you do NOT wish to
     # share with the reexeced child process.
     # There is no corresponding after_exec hook (for obvious reasons).
-    def before_exec(&block)
-      set_hook(:before_exec, block, 1)
+    def before_exec(*args, &block)
+      set_hook(:before_exec, block_given? ? block : args[0], 1)
     end
 
     # sets the timeout of worker processes to +seconds+.  Workers
     # handling the request/app.call/response cycle taking longer than
     # this time period will be forcibly killed (via SIGKILL).  This
     # timeout is enforced by the master process itself and not subject
-    # to the scheduling limitations by the worker process.
+    # to the scheduling limitations by the worker process.  Due the
+    # low-complexity, low-overhead implementation, timeouts of less
+    # than 3.0 seconds can be considered inaccurate and unsafe.
     def timeout(seconds)
       Numeric === seconds or raise ArgumentError,
                                   "not numeric: timeout=#{seconds.inspect}"
-      seconds > 0 or raise ArgumentError,
-                                  "not positive: timeout=#{seconds.inspect}"
+      seconds >= 3 or raise ArgumentError,
+                                  "too low: timeout=#{seconds.inspect}"
       @set[:timeout] = seconds
     end
 
@@ -171,13 +167,59 @@ module Unicorn
     # sets listeners to the given +addresses+, replacing or augmenting the
     # current set.  This is for the global listener pool shared by all
     # worker processes.  For per-worker listeners, see the after_fork example
-    def listeners(addresses)
+    # This is for internal API use only, do not use it in your Unicorn
+    # config file.  Use listen instead.
+    def listeners(addresses) # :nodoc:
       Array === addresses or addresses = Array(addresses)
+      addresses.map! { |addr| expand_addr(addr) }
       @set[:listeners] = addresses
     end
 
-    # adds an +address+ to the existing listener set
-    def listen(address)
+    # adds an +address+ to the existing listener set.
+    #
+    # The following options may be specified (but are generally not needed):
+    #
+    # +backlog+: this is the backlog of the listen() syscall.
+    #
+    # Some operating systems allow negative values here to specify the
+    # maximum allowable value.  In most cases, this number is only
+    # recommendation and there are other OS-specific tunables and
+    # variables that can affect this number.  See the listen(2)
+    # syscall documentation of your OS for the exact semantics of
+    # this.
+    #
+    # If you are running unicorn on multiple machines, lowering this number
+    # can help your load balancer detect when a machine is overloaded
+    # and give requests to a different machine.
+    #
+    # Default: 1024
+    #
+    # +rcvbuf+, +sndbuf+: maximum send and receive buffer sizes of sockets
+    #
+    # These correspond to the SO_RCVBUF and SO_SNDBUF settings which
+    # can be set via the setsockopt(2) syscall.  Some kernels
+    # (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and
+    # there is no need (and it is sometimes detrimental) to specify them.
+    #
+    # See the socket API documentation of your operating system
+    # to determine the exact semantics of these settings and
+    # other operating system-specific knobs where they can be
+    # specified.
+    #
+    # Defaults: operating system defaults
+    def listen(address, opt = { :backlog => 1024 })
+      address = expand_addr(address)
+      if String === address
+        Hash === @set[:listener_opts] or
+          @set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
+        [ :backlog, :sndbuf, :rcvbuf ].each do |key|
+          value = opt[key] or next
+          Integer === value or
+            raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
+        end
+        @set[:listener_opts][address].merge!(opt)
+      end
+
       @set[:listeners] = [] unless Array === @set[:listeners]
       @set[:listeners] << address
     end
@@ -194,6 +236,10 @@ module Unicorn
     # properly close/reopen sockets.  Files opened for logging do not
     # have to be reopened as (unbuffered-in-userspace) files opened with
     # the File::APPEND flag are written to atomically on UNIX.
+    #
+    # In addition to reloading the unicorn-specific config settings,
+    # SIGHUP will reload application code in the working
+    # directory/symlink when workers are gracefully restarted.
     def preload_app(bool)
       case bool
       when TrueClass, FalseClass
@@ -249,5 +295,28 @@ module Unicorn
       @set[var] = my_proc
     end
 
+    # expands "unix:path/to/foo" to a socket relative to the current path
+    # expands pathnames of sockets if relative to "~" or "~username"
+    # expands "*:port and ":port" to "0.0.0.0:port"
+    def expand_addr(address) #:nodoc
+      return "0.0.0.0:#{address}" if Integer === address
+      return address unless String === address
+
+      case address
+      when %r{\Aunix:(.*)\z}
+        File.expand_path($1)
+      when %r{\A~}
+        File.expand_path(address)
+      when %r{\A(?:\*:)?(\d+)\z}
+        "0.0.0.0:#$1"
+      when %r{\A(.*):(\d+)\z}
+        # canonicalize the name
+        packed = Socket.pack_sockaddr_in($2.to_i, $1)
+        Socket.unpack_sockaddr_in(packed).reverse!.join(':')
+      else
+        address
+      end
+    end
+
   end
 end