diff options
Diffstat (limited to 'lib/unicorn/configurator.rb')
-rw-r--r-- | lib/unicorn/configurator.rb | 159 |
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 |