configurator: logger: drop "close" requirement
[unicorn.git] / lib / unicorn / configurator.rb
blob86e58a0bcd6e903f6f8e47be72790636e4d472db
1 # -*- encoding: binary -*-
3 require 'socket'
4 require 'logger'
6 module Unicorn
8   # Implements a simple DSL for configuring a Unicorn server.
9   #
10   # See http://unicorn.bogomips.org/examples/unicorn.conf.rb and
11   # http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
12   # example configuration files.  An example config file for use with
13   # nginx is also available at
14   # http://unicorn.bogomips.org/examples/nginx.conf
15   class Configurator < Struct.new(:set, :config_file, :after_reload)
17     # Default settings for Unicorn
18     DEFAULTS = {
19       :timeout => 60,
20       :logger => Logger.new($stderr),
21       :worker_processes => 1,
22       :after_fork => lambda { |server, worker|
23           server.logger.info("worker=#{worker.nr} spawned pid=#{$$}")
24         },
25       :before_fork => lambda { |server, worker|
26           server.logger.info("worker=#{worker.nr} spawning...")
27         },
28       :before_exec => lambda { |server|
29           server.logger.info("forked child re-executing...")
30         },
31       :pid => nil,
32       :preload_app => false,
33     }
35     def initialize(defaults = {}) #:nodoc:
36       self.set = Hash.new(:unset)
37       use_defaults = defaults.delete(:use_defaults)
38       self.config_file = defaults.delete(:config_file)
40       # after_reload is only used by unicorn_rails, unsupported otherwise
41       self.after_reload = defaults.delete(:after_reload)
43       set.merge!(DEFAULTS) if use_defaults
44       defaults.each { |key, value| self.send(key, value) }
45       Hash === set[:listener_opts] or
46           set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
47       Array === set[:listeners] or set[:listeners] = []
48       reload
49     end
51     def reload #:nodoc:
52       instance_eval(File.read(config_file), config_file) if config_file
54       # working_directory binds immediately (easier error checking that way),
55       # now ensure any paths we changed are correctly set.
56       [ :pid, :stderr_path, :stdout_path ].each do |var|
57         String === (path = set[var]) or next
58         path = File.expand_path(path)
59         test(?w, path) || test(?w, File.dirname(path)) or \
60               raise ArgumentError, "directory for #{var}=#{path} not writable"
61       end
63       # unicorn_rails creates dirs here after working_directory is bound
64       after_reload.call if after_reload
65     end
67     def commit!(server, options = {}) #:nodoc:
68       skip = options[:skip] || []
69       set.each do |key, value|
70         value == :unset and next
71         skip.include?(key) and next
72         server.__send__("#{key}=", value)
73       end
74     end
76     def [](key) # :nodoc:
77       set[key]
78     end
80     # sets object to the +new+ Logger-like object.  The new logger-like
81     # object must respond to the following methods:
82     #  +debug+, +info+, +warn+, +error+, +fatal+
83     # The default Logger will log its output to the path specified
84     # by +stderr_path+.  If you're running Unicorn daemonized, then
85     # you must specify a path to prevent error messages from going
86     # to /dev/null.
87     def logger(new)
88       %w(debug info warn error fatal).each do |m|
89         new.respond_to?(m) and next
90         raise ArgumentError, "logger=#{new} does not respond to method=#{m}"
91       end
93       set[:logger] = new
94     end
96     # sets after_fork hook to a given block.  This block will be called by
97     # the worker after forking.  The following is an example hook which adds
98     # a per-process listener to every worker:
99     #
100     #  after_fork do |server,worker|
101     #    # per-process listener ports for debugging/admin:
102     #    addr = "127.0.0.1:#{9293 + worker.nr}"
103     #
104     #    # the negative :tries parameter indicates we will retry forever
105     #    # waiting on the existing process to exit with a 5 second :delay
106     #    # Existing options for Unicorn::Configurator#listen such as
107     #    # :backlog, :rcvbuf, :sndbuf are available here as well.
108     #    server.listen(addr, :tries => -1, :delay => 5, :backlog => 128)
109     #
110     #    # drop permissions to "www-data" in the worker
111     #    # generally there's no reason to start Unicorn as a priviledged user
112     #    # as it is not recommended to expose Unicorn to public clients.
113     #    worker.user('www-data', 'www-data') if Process.euid == 0
114     #  end
115     def after_fork(*args, &block)
116       set_hook(:after_fork, block_given? ? block : args[0])
117     end
119     # sets before_fork got be a given Proc object.  This Proc
120     # object will be called by the master process before forking
121     # each worker.
122     def before_fork(*args, &block)
123       set_hook(:before_fork, block_given? ? block : args[0])
124     end
126     # sets the before_exec hook to a given Proc object.  This
127     # Proc object will be called by the master process right
128     # before exec()-ing the new unicorn binary.  This is useful
129     # for freeing certain OS resources that you do NOT wish to
130     # share with the reexeced child process.
131     # There is no corresponding after_exec hook (for obvious reasons).
132     def before_exec(*args, &block)
133       set_hook(:before_exec, block_given? ? block : args[0], 1)
134     end
136     # sets the timeout of worker processes to +seconds+.  Workers
137     # handling the request/app.call/response cycle taking longer than
138     # this time period will be forcibly killed (via SIGKILL).  This
139     # timeout is enforced by the master process itself and not subject
140     # to the scheduling limitations by the worker process.  Due the
141     # low-complexity, low-overhead implementation, timeouts of less
142     # than 3.0 seconds can be considered inaccurate and unsafe.
143     #
144     # For running Unicorn behind nginx, it is recommended to set
145     # "fail_timeout=0" for in your nginx configuration like this
146     # to have nginx always retry backends that may have had workers
147     # SIGKILL-ed due to timeouts.
148     #
149     #    # See http://wiki.nginx.org/NginxHttpUpstreamModule for more details
150     #    # on nginx upstream configuration:
151     #    upstream unicorn_backend {
152     #      # for UNIX domain socket setups:
153     #      server unix:/path/to/unicorn.sock fail_timeout=0;
154     #
155     #      # for TCP setups
156     #      server 192.168.0.7:8080 fail_timeout=0;
157     #      server 192.168.0.8:8080 fail_timeout=0;
158     #      server 192.168.0.9:8080 fail_timeout=0;
159     #    }
160     def timeout(seconds)
161       Numeric === seconds or raise ArgumentError,
162                                   "not numeric: timeout=#{seconds.inspect}"
163       seconds >= 3 or raise ArgumentError,
164                                   "too low: timeout=#{seconds.inspect}"
165       set[:timeout] = seconds
166     end
168     # sets the current number of worker_processes to +nr+.  Each worker
169     # process will serve exactly one client at a time.  You can
170     # increment or decrement this value at runtime by sending SIGTTIN
171     # or SIGTTOU respectively to the master process without reloading
172     # the rest of your Unicorn configuration.  See the SIGNALS document
173     # for more information.
174     def worker_processes(nr)
175       Integer === nr or raise ArgumentError,
176                              "not an integer: worker_processes=#{nr.inspect}"
177       nr >= 0 or raise ArgumentError,
178                              "not non-negative: worker_processes=#{nr.inspect}"
179       set[:worker_processes] = nr
180     end
182     # sets listeners to the given +addresses+, replacing or augmenting the
183     # current set.  This is for the global listener pool shared by all
184     # worker processes.  For per-worker listeners, see the after_fork example
185     # This is for internal API use only, do not use it in your Unicorn
186     # config file.  Use listen instead.
187     def listeners(addresses) # :nodoc:
188       Array === addresses or addresses = Array(addresses)
189       addresses.map! { |addr| expand_addr(addr) }
190       set[:listeners] = addresses
191     end
193     # adds an +address+ to the existing listener set.
194     #
195     # The following options may be specified (but are generally not needed):
196     #
197     # +:backlog+: this is the backlog of the listen() syscall.
198     #
199     # Some operating systems allow negative values here to specify the
200     # maximum allowable value.  In most cases, this number is only
201     # recommendation and there are other OS-specific tunables and
202     # variables that can affect this number.  See the listen(2)
203     # syscall documentation of your OS for the exact semantics of
204     # this.
205     #
206     # If you are running unicorn on multiple machines, lowering this number
207     # can help your load balancer detect when a machine is overloaded
208     # and give requests to a different machine.
209     #
210     # Default: 1024
211     #
212     # +:rcvbuf+, +:sndbuf+: maximum receive and send buffer sizes of sockets
213     #
214     # These correspond to the SO_RCVBUF and SO_SNDBUF settings which
215     # can be set via the setsockopt(2) syscall.  Some kernels
216     # (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and
217     # there is no need (and it is sometimes detrimental) to specify them.
218     #
219     # See the socket API documentation of your operating system
220     # to determine the exact semantics of these settings and
221     # other operating system-specific knobs where they can be
222     # specified.
223     #
224     # Defaults: operating system defaults
225     #
226     # +:tcp_nodelay+: disables Nagle's algorithm on TCP sockets
227     #
228     # This has no effect on UNIX sockets.
229     #
230     # Default: operating system defaults (usually Nagle's algorithm enabled)
231     #
232     # +:tcp_nopush+: enables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD
233     #
234     # This will prevent partial TCP frames from being sent out.
235     # Enabling +tcp_nopush+ is generally not needed or recommended as
236     # controlling +tcp_nodelay+ already provides sufficient latency
237     # reduction whereas Unicorn does not know when the best times are
238     # for flushing corked sockets.
239     #
240     # This has no effect on UNIX sockets.
241     #
242     # +:tries+: times to retry binding a socket if it is already in use
243     #
244     # A negative number indicates we will retry indefinitely, this is
245     # useful for migrations and upgrades when individual workers
246     # are binding to different ports.
247     #
248     # Default: 5
249     #
250     # +:delay+: seconds to wait between successive +tries+
251     #
252     # Default: 0.5 seconds
253     #
254     # +:umask+: sets the file mode creation mask for UNIX sockets
255     #
256     # Typically UNIX domain sockets are created with more liberal
257     # file permissions than the rest of the application.  By default,
258     # we create UNIX domain sockets to be readable and writable by
259     # all local users to give them the same accessibility as
260     # locally-bound TCP listeners.
261     #
262     # This has no effect on TCP listeners.
263     #
264     # Default: 0 (world read/writable)
265     def listen(address, opt = {})
266       address = expand_addr(address)
267       if String === address
268         [ :umask, :backlog, :sndbuf, :rcvbuf, :tries ].each do |key|
269           value = opt[key] or next
270           Integer === value or
271             raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
272         end
273         [ :tcp_nodelay, :tcp_nopush ].each do |key|
274           (value = opt[key]).nil? and next
275           TrueClass === value || FalseClass === value or
276             raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
277         end
278         unless (value = opt[:delay]).nil?
279           Numeric === value or
280             raise ArgumentError, "not numeric: delay=#{value.inspect}"
281         end
282         set[:listener_opts][address].merge!(opt)
283       end
285       set[:listeners] << address
286     end
288     # sets the +path+ for the PID file of the unicorn master process
289     def pid(path); set_path(:pid, path); end
291     # Enabling this preloads an application before forking worker
292     # processes.  This allows memory savings when using a
293     # copy-on-write-friendly GC but can cause bad things to happen when
294     # resources like sockets are opened at load time by the master
295     # process and shared by multiple children.  People enabling this are
296     # highly encouraged to look at the before_fork/after_fork hooks to
297     # properly close/reopen sockets.  Files opened for logging do not
298     # have to be reopened as (unbuffered-in-userspace) files opened with
299     # the File::APPEND flag are written to atomically on UNIX.
300     #
301     # In addition to reloading the unicorn-specific config settings,
302     # SIGHUP will reload application code in the working
303     # directory/symlink when workers are gracefully restarted.
304     def preload_app(bool)
305       case bool
306       when TrueClass, FalseClass
307         set[:preload_app] = bool
308       else
309         raise ArgumentError, "preload_app=#{bool.inspect} not a boolean"
310       end
311     end
313     # Allow redirecting $stderr to a given path.  Unlike doing this from
314     # the shell, this allows the unicorn process to know the path its
315     # writing to and rotate the file if it is used for logging.  The
316     # file will be opened with the File::APPEND flag and writes
317     # synchronized to the kernel (but not necessarily to _disk_) so
318     # multiple processes can safely append to it.
319     #
320     # If you are daemonizing and using the default +logger+, it is important
321     # to specify this as errors will otherwise be lost to /dev/null.
322     # Some applications/libraries may also triggering warnings that go to
323     # stderr, and they will end up here.
324     def stderr_path(path)
325       set_path(:stderr_path, path)
326     end
328     # Same as stderr_path, except for $stdout.  Not many Rack applications
329     # write to $stdout, but any that do will have their output written here.
330     # It is safe to point this to the same location a stderr_path.
331     # Like stderr_path, this defaults to /dev/null when daemonized.
332     def stdout_path(path)
333       set_path(:stdout_path, path)
334     end
336     # sets the working directory for Unicorn.  This ensures USR2 will
337     # start a new instance of Unicorn in this directory.  This may be
338     # a symlink.
339     def working_directory(path)
340       # just let chdir raise errors
341       path = File.expand_path(path)
342       if config_file &&
343          config_file[0] != ?/ &&
344          ! test(?r, "#{path}/#{config_file}")
345         raise ArgumentError,
346               "config_file=#{config_file} would not be accessible in" \
347               " working_directory=#{path}"
348       end
349       Dir.chdir(path)
350       HttpServer::START_CTX[:cwd] = ENV["PWD"] = path
351     end
353     # Runs worker processes as the specified +user+ and +group+.
354     # The master process always stays running as the user who started it.
355     # This switch will occur after calling the after_fork hook, and only
356     # if the Worker#user method is not called in the after_fork hook
357     def user(user, group = nil)
358       # raises ArgumentError on invalid user/group
359       Etc.getpwnam(user)
360       Etc.getgrnam(group) if group
361       set[:user] = [ user, group ]
362     end
364     # expands "unix:path/to/foo" to a socket relative to the current path
365     # expands pathnames of sockets if relative to "~" or "~username"
366     # expands "*:port and ":port" to "0.0.0.0:port"
367     def expand_addr(address) #:nodoc
368       return "0.0.0.0:#{address}" if Integer === address
369       return address unless String === address
371       case address
372       when %r{\Aunix:(.*)\z}
373         File.expand_path($1)
374       when %r{\A~}
375         File.expand_path(address)
376       when %r{\A(?:\*:)?(\d+)\z}
377         "0.0.0.0:#$1"
378       when %r{\A(.*):(\d+)\z}
379         # canonicalize the name
380         packed = Socket.pack_sockaddr_in($2.to_i, $1)
381         Socket.unpack_sockaddr_in(packed).reverse!.join(':')
382       else
383         address
384       end
385     end
387   private
389     def set_path(var, path) #:nodoc:
390       case path
391       when NilClass, String
392         set[var] = path
393       else
394         raise ArgumentError
395       end
396     end
398     def set_hook(var, my_proc, req_arity = 2) #:nodoc:
399       case my_proc
400       when Proc
401         arity = my_proc.arity
402         (arity == req_arity) or \
403           raise ArgumentError,
404                 "#{var}=#{my_proc.inspect} has invalid arity: " \
405                 "#{arity} (need #{req_arity})"
406       when NilClass
407         my_proc = DEFAULTS[var]
408       else
409         raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}"
410       end
411       set[var] = my_proc
412     end
414   end