diff options
Diffstat (limited to 'lib/unicorn/configurator.rb')
-rw-r--r-- | lib/unicorn/configurator.rb | 237 |
1 files changed, 138 insertions, 99 deletions
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index 860962a..f6d13ab 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -1,38 +1,24 @@ +# -*- encoding: binary -*- + require 'socket' require 'logger' module Unicorn - # Implements a simple DSL for configuring a unicorn server. + # Implements a simple DSL for configuring a Unicorn server. # - # Example (when used with the unicorn config file): - # worker_processes 4 - # 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| - # server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil - # end - class Configurator - # The default logger writes its output to $stderr - DEFAULT_LOGGER = Logger.new($stderr) unless defined?(DEFAULT_LOGGER) + # See http://unicorn.bogomips.org/examples/unicorn.conf.rb for an + # example config file. An example config file for use with nginx is + # also available at http://unicorn.bogomips.org/examples/nginx.conf + class Configurator < Struct.new(:set, :config_file) # Default settings for Unicorn DEFAULTS = { :timeout => 60, - :listeners => [], - :logger => DEFAULT_LOGGER, + :logger => Logger.new($stderr), :worker_processes => 1, :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 }, :before_fork => lambda { |server, worker| server.logger.info("worker=#{worker.nr} spawning...") @@ -42,42 +28,44 @@ module Unicorn }, :pid => nil, :preload_app => false, - :stderr_path => nil, - :stdout_path => nil, } - attr_reader :config_file #:nodoc: - def initialize(defaults = {}) #:nodoc: - @set = Hash.new(:unset) + self.set = Hash.new(:unset) use_defaults = defaults.delete(:use_defaults) - @config_file = defaults.delete(:config_file) - @config_file.freeze - @set.merge!(DEFAULTS) if use_defaults + self.config_file = defaults.delete(:config_file) + set.merge!(DEFAULTS) if use_defaults defaults.each { |key, value| self.send(key, value) } + Hash === set[:listener_opts] or + set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} } + Array === set[:listeners] or set[:listeners] = [] reload end def reload #:nodoc: - instance_eval(File.read(@config_file)) if @config_file + instance_eval(File.read(config_file), config_file) if config_file + + # working_directory binds immediately (easier error checking that way), + # now ensure any paths we changed are correctly set. + [ :pid, :stderr_path, :stdout_path ].each do |var| + String === (path = set[var]) or next + path = File.expand_path(path) + test(?w, path) || test(?w, File.dirname(path)) or \ + raise ArgumentError, "directory for #{var}=#{path} not writable" + end end def commit!(server, options = {}) #:nodoc: skip = options[:skip] || [] - @set.each do |key, value| - (Symbol === value && value == :unset) and next + set.each do |key, value| + value == :unset and next skip.include?(key) and next - setter = "#{key}=" - if server.respond_to?(setter) - server.send(setter, value) - else - server.instance_variable_set("@#{key}", value) - end + server.__send__("#{key}=", value) end end def [](key) # :nodoc: - @set[key] + set[key] end # sets object to the +new+ Logger-like object. The new logger-like @@ -89,7 +77,7 @@ module Unicorn raise ArgumentError, "logger=#{new} does not respond to method=#{m}" end - @set[:logger] = new + set[:logger] = new end # sets after_fork hook to a given block. This block will be called by @@ -98,25 +86,18 @@ module Unicorn # # 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 + # addr = "127.0.0.1:#{9293 + worker.nr}" + # + # # the negative :tries parameter indicates we will retry forever + # # waiting on the existing process to exit with a 5 second :delay + # # Existing options for Unicorn::Configurator#listen such as + # # :backlog, :rcvbuf, :sndbuf are available here as well. + # server.listen(addr, :tries => -1, :delay => 5, :backlog => 128) # # # 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 + # worker.user('www-data', 'www-data') if Process.euid == 0 # end def after_fork(*args, &block) set_hook(:after_fork, block_given? ? block : args[0]) @@ -146,22 +127,43 @@ module Unicorn # 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. + # + # For running Unicorn behind nginx, it is recommended to set + # "fail_timeout=0" for in your nginx configuration like this + # to have nginx always retry backends that may have had workers + # SIGKILL-ed due to timeouts. + # + # # See http://wiki.nginx.org/NginxHttpUpstreamModule for more details + # # on nginx upstream configuration: + # upstream unicorn_backend { + # # for UNIX domain socket setups: + # server unix:/path/to/unicorn.sock fail_timeout=0; + # + # # for TCP setups + # server 192.168.0.7:8080 fail_timeout=0; + # server 192.168.0.8:8080 fail_timeout=0; + # server 192.168.0.9:8080 fail_timeout=0; + # } def timeout(seconds) Numeric === seconds or raise ArgumentError, "not numeric: timeout=#{seconds.inspect}" seconds >= 3 or raise ArgumentError, "too low: timeout=#{seconds.inspect}" - @set[:timeout] = seconds + set[:timeout] = seconds end # sets the current number of worker_processes to +nr+. Each worker - # process will serve exactly one client at a time. + # process will serve exactly one client at a time. You can + # increment or decrement this value at runtime by sending SIGTTIN + # or SIGTTOU respectively to the master process without reloading + # the rest of your Unicorn configuration. See the SIGNALS document + # for more information. def worker_processes(nr) Integer === nr or raise ArgumentError, "not an integer: worker_processes=#{nr.inspect}" nr >= 0 or raise ArgumentError, "not non-negative: worker_processes=#{nr.inspect}" - @set[:worker_processes] = nr + set[:worker_processes] = nr end # sets listeners to the given +addresses+, replacing or augmenting the @@ -172,14 +174,14 @@ module Unicorn def listeners(addresses) # :nodoc: Array === addresses or addresses = Array(addresses) addresses.map! { |addr| expand_addr(addr) } - @set[:listeners] = addresses + set[:listeners] = addresses end # 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. + # +: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 @@ -194,7 +196,7 @@ module Unicorn # # Default: 1024 # - # +rcvbuf+, +sndbuf+: maximum send and receive buffer sizes of sockets + # +:rcvbuf+, +:sndbuf+: maximum receive and send 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 @@ -208,13 +210,13 @@ module Unicorn # # Defaults: operating system defaults # - # +tcp_nodelay+: disables Nagle's algorithm on TCP sockets + # +:tcp_nodelay+: disables Nagle's algorithm on TCP sockets # # This has no effect on UNIX sockets. # # Default: operating system defaults (usually Nagle's algorithm enabled) # - # +tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD + # +:tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD # # This will prevent partial TCP frames from being sent out. # Enabling +tcp_nopush+ is generally not needed or recommended as @@ -224,12 +226,33 @@ module Unicorn # # This has no effect on UNIX sockets. # + # +:tries+: times to retry binding a socket if it is already in use + # + # A negative number indicates we will retry indefinitely, this is + # useful for migrations and upgrades when individual workers + # are binding to different ports. + # + # Default: 5 + # + # +:delay+: seconds to wait between successive +tries+ + # + # Default: 0.5 seconds + # + # +:umask+: sets the file mode creation mask for UNIX sockets + # + # Typically UNIX domain sockets are created with more liberal + # file permissions than the rest of the application. By default, + # we create UNIX domain sockets to be readable and writable by + # all local users to give them the same accessibility as + # locally-bound TCP listeners. + # + # This has no effect on TCP listeners. + # + # Default: 0 (world read/writable) def listen(address, opt = {}) 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| + [ :umask, :backlog, :sndbuf, :rcvbuf, :tries ].each do |key| value = opt[key] or next Integer === value or raise ArgumentError, "not an integer: #{key}=#{value.inspect}" @@ -239,11 +262,14 @@ module Unicorn TrueClass === value || FalseClass === value or raise ArgumentError, "not boolean: #{key}=#{value.inspect}" end - @set[:listener_opts][address].merge!(opt) + unless (value = opt[:delay]).nil? + Numeric === value or + raise ArgumentError, "not numeric: delay=#{value.inspect}" + end + set[:listener_opts][address].merge!(opt) end - @set[:listeners] = [] unless Array === @set[:listeners] - @set[:listeners] << address + set[:listeners] << address end # sets the +path+ for the PID file of the unicorn master process @@ -265,7 +291,7 @@ module Unicorn def preload_app(bool) case bool when TrueClass, FalseClass - @set[:preload_app] = bool + set[:preload_app] = bool else raise ArgumentError, "preload_app=#{bool.inspect} not a boolean" end @@ -286,35 +312,21 @@ module Unicorn set_path(:stdout_path, path) end - private - - def set_path(var, path) #:nodoc: - case path - when NilClass - when String - path = File.expand_path(path) - File.writable?(File.dirname(path)) or \ - raise ArgumentError, "directory for #{var}=#{path} not writable" - else - raise ArgumentError + # sets the working directory for Unicorn. This ensures USR2 will + # start a new instance of Unicorn in this directory. This may be + # a symlink. + def working_directory(path) + # just let chdir raise errors + path = File.expand_path(path) + if config_file && + config_file[0] != ?/ && + ! test(?r, "#{path}/#{config_file}") + raise ArgumentError, + "config_file=#{config_file} would not be accessible in" \ + " working_directory=#{path}" end - @set[var] = path - end - - def set_hook(var, my_proc, req_arity = 2) #:nodoc: - case my_proc - when Proc - arity = my_proc.arity - (arity == req_arity) or \ - raise ArgumentError, - "#{var}=#{my_proc.inspect} has invalid arity: " \ - "#{arity} (need #{req_arity})" - when NilClass - my_proc = DEFAULTS[var] - else - raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}" - end - @set[var] = my_proc + Dir.chdir(path) + HttpServer::START_CTX[:cwd] = ENV["PWD"] = path end # expands "unix:path/to/foo" to a socket relative to the current path @@ -340,5 +352,32 @@ module Unicorn end end + private + + def set_path(var, path) #:nodoc: + case path + when NilClass, String + set[var] = path + else + raise ArgumentError + end + end + + def set_hook(var, my_proc, req_arity = 2) #:nodoc: + case my_proc + when Proc + arity = my_proc.arity + (arity == req_arity) or \ + raise ArgumentError, + "#{var}=#{my_proc.inspect} has invalid arity: " \ + "#{arity} (need #{req_arity})" + when NilClass + my_proc = DEFAULTS[var] + else + raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}" + end + set[var] = my_proc + end + end end |