Object
This is the process manager of Unicorn. This manages worker processes which in turn handle the I/O and application process. Listener sockets are started in the master process and shared with forked worker children.
We populate this at startup so we can figure out how to reexecute and upgrade the currently running instance of Unicorn This Hash is considered a stable interface and changing its contents will allow you to switch between different installations of Unicorn or even different installations of the same applications without downtime. Keys of this constant Hash are described as follows:
0 - the path to the unicorn/unicorn_rails executable
:argv - a deep copy of the ARGV array the executable originally saw
:cwd - the working directory of the application, this is where
you originally started Unicorn.
To change your unicorn executable to a different path without downtime, you can set the following in your Unicorn config file, HUP and then continue with the traditional USR2 + QUIT upgrade steps:
Unicorn::HttpServer::START_CTX[0] = "/home/bofh/1.9.2/bin/unicorn"
Creates a working server on host:port (strange things happen if port isn’t a Number). Use HttpServer::run to start the server and HttpServer.run.join to join the thread that’s processing incoming requests on the socket.
# File lib/unicorn/http_server.rb, line 91 def initialize(app, options = {}) @app = app @request = Unicorn::HttpRequest.new self.reexec_pid = 0 options = options.dup self.ready_pipe = options.delete(:ready_pipe) self.init_listeners = options[:listeners] ? options[:listeners].dup : [] options[:use_defaults] = true self.config = Unicorn::Configurator.new(options) self.listener_opts = {} # we try inheriting listeners first, so we bind them later. # we don't write the pid file until we've bound listeners in case # unicorn was started twice by mistake. Even though our #pid= method # checks for stale/existing pid files, race conditions are still # possible (and difficult/non-portable to avoid) and can be likely # to clobber the pid if the second start was in quick succession # after the first, so we rely on the listener binding to fail in # that case. Some tests (in and outside of this source tree) and # monitoring tools may also rely on pid files existing before we # attempt to connect to the listener(s) config.commit!(self, :skip => [:listeners, :pid]) self.orig_app = app end
# File lib/unicorn/http_server.rb, line 364 def client_body_buffer_size Unicorn::TeeInput.client_body_buffer_size end
# File lib/unicorn/http_server.rb, line 368 def client_body_buffer_size=(bytes) Unicorn::TeeInput.client_body_buffer_size = bytes end
monitors children and receives signals forever (or until a termination signal is sent). This handles signals one-at-a-time time and we’ll happily drop signals in case somebody is signalling us too often.
# File lib/unicorn/http_server.rb, line 273 def join respawn = true last_check = Time.now proc_name 'master' logger.info "master process ready" # test_exec.rb relies on this message if ready_pipe ready_pipe.syswrite($$.to_s) ready_pipe.close rescue nil self.ready_pipe = nil end begin reap_all_workers case SIG_QUEUE.shift when nil # avoid murdering workers after our master process (or the # machine) comes out of suspend/hibernation if (last_check + @timeout) >= (last_check = Time.now) sleep_time = murder_lazy_workers else # wait for workers to wakeup on suspend sleep_time = @timeout/2.0 + 1 end maintain_worker_count if respawn master_sleep(sleep_time) when :QUIT # graceful shutdown break when :TERM, :INT # immediate shutdown stop(false) break when :USR1 # rotate logs logger.info "master reopening logs..." Unicorn::Util.reopen_logs logger.info "master done reopening logs" kill_each_worker(:USR1) when :USR2 # exec binary, stay alive in case something went wrong reexec when :WINCH if Process.ppid == 1 || Process.getpgrp != $$ respawn = false logger.info "gracefully stopping all workers" kill_each_worker(:QUIT) self.worker_processes = 0 else logger.info "SIGWINCH ignored because we're not daemonized" end when :TTIN respawn = true self.worker_processes += 1 when :TTOU self.worker_processes -= 1 if self.worker_processes > 0 when :HUP respawn = true if config.config_file load_config! else # exec binary and exit if there's no config file logger.info "config_file not present, reexecuting binary" reexec end end rescue Errno::EINTR rescue => e logger.error "Unhandled master loop exception #{e.inspect}." logger.error e.backtrace.join("\n") end while true stop # gracefully shutdown all workers on our way out logger.info "master complete" unlink_pid_safe(pid) if pid end
add a given address to the listeners set, idempotently Allows workers to add a private, per-process listener via the after_fork hook. Very useful for debugging and testing. :tries may be specified as an option for the number of times to retry, and :delay may be specified as the time in seconds to delay between retries. A negative value for :tries indicates the listen will be retried indefinitely, this is useful when workers belonging to different masters are spawned during a transparent upgrade.
# File lib/unicorn/http_server.rb, line 240 def listen(address, opt = {}.merge(listener_opts[address] || {})) address = config.expand_addr(address) return if String === address && listener_names.include?(address) delay = opt[:delay] || 0.5 tries = opt[:tries] || 5 begin io = bind_listen(address, opt) unless Kgio::TCPServer === io || Kgio::UNIXServer === io IO_PURGATORY << io io = server_cast(io) end logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}" LISTENERS << io io rescue Errno::EADDRINUSE => err logger.error "adding listener failed addr=#{address} (in use)" raise err if tries == 0 tries -= 1 logger.error "retrying in #{delay} seconds " "(#{tries < 0 ? 'infinite' : tries} tries left)" sleep(delay) retry rescue => err logger.fatal "error adding listener addr=#{address}" raise err end end
replaces current listener set with listeners. This will close the socket if it will not exist in the new listener set
# File lib/unicorn/http_server.rb, line 166 def listeners=(listeners) cur_names, dead_names = [], [] listener_names.each do |name| if // == name[0] # mark unlinked sockets as dead so we can rebind them (File.socket?(name) ? cur_names : dead_names) << name else cur_names << name end end set_names = listener_names(listeners) dead_names.concat(cur_names - set_names).uniq! LISTENERS.delete_if do |io| if dead_names.include?(sock_name(io)) IO_PURGATORY.delete_if do |pio| pio.fileno == io.fileno && (pio.close rescue nil).nil? # true end (io.close rescue nil).nil? # true else set_server_sockopt(io, listener_opts[sock_name(io)]) false end end (set_names - cur_names).each { |addr| listen(addr) } end
# File lib/unicorn/http_server.rb, line 197 def logger=(obj) Unicorn::HttpRequest::DEFAULTS["rack.logger"] = @logger = obj end
sets the path for the PID file of the master process
# File lib/unicorn/http_server.rb, line 202 def pid=(path) if path if x = valid_pid?(path) return path if pid && path == pid && x == $$ if x == reexec_pid && pid =~ /\.oldbin\z/ logger.warn("will not set pid=#{path} while reexec-ed " "child is running PID:#{x}") return end raise ArgumentError, "Already running on PID:#{x} " "(or pid=#{path} is stale)" end end unlink_pid_safe(pid) if pid if path fp = begin tmp = "#{File.dirname(path)}/#{rand}.#$$" File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644) rescue Errno::EEXIST retry end fp.syswrite("#$$\n") File.rename(fp.path, path) fp.close end @pid = path end
# File lib/unicorn/http_server.rb, line 355 def rewindable_input Unicorn::HttpRequest.input_class.method_defined?(:rewind) end
# File lib/unicorn/http_server.rb, line 359 def rewindable_input=(bool) Unicorn::HttpRequest.input_class = bool ? Unicorn::TeeInput : Unicorn::StreamInput end
Runs the thing. Returns self so you can run join on it
# File lib/unicorn/http_server.rb, line 117 def start BasicSocket.do_not_reverse_lookup = true # inherit sockets from parents, they need to be plain Socket objects # before they become Kgio::UNIXServer or Kgio::TCPServer inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd| io = Socket.for_fd(fd.to_i) set_server_sockopt(io, listener_opts[sock_name(io)]) IO_PURGATORY << io logger.info "inherited addr=#{sock_name(io)} fd=#{fd}" server_cast(io) end config_listeners = config[:listeners].dup LISTENERS.replace(inherited) # we start out with generic Socket objects that get cast to either # Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket # objects share the same OS-level file descriptor as the higher-level # *Server objects; we need to prevent Socket objects from being # garbage-collected config_listeners -= listener_names if config_listeners.empty? && LISTENERS.empty? config_listeners << Unicorn::Const::DEFAULT_LISTEN init_listeners << Unicorn::Const::DEFAULT_LISTEN START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}" end config_listeners.each { |addr| listen(addr) } raise ArgumentError, "no listeners" if LISTENERS.empty? # this pipe is used to wake us up from select(2) in #join when signals # are trapped. See trap_deferred. init_self_pipe! # setup signal handlers before writing pid file in case people get # trigger happy and send signals as soon as the pid file exists. # Note that signals don't actually get handled until the #join method QUEUE_SIGS.each { |sig| trap(sig) { SIG_QUEUE << sig; awaken_master } } trap(:CHLD) { awaken_master } self.pid = config[:pid] self.master_pid = $$ build_app! if preload_app maintain_worker_count self end
# File lib/unicorn/http_server.rb, line 195 def stderr_path=(path); redirect_io($stderr, path); end
# File lib/unicorn/http_server.rb, line 194 def stdout_path=(path); redirect_io($stdout, path); end
Terminates all workers, but does not exit master process
# File lib/unicorn/http_server.rb, line 344 def stop(graceful = true) self.listeners = [] limit = Time.now + timeout until WORKERS.empty? || Time.now > limit kill_each_worker(graceful ? :QUIT : :TERM) sleep(0.1) reap_all_workers end kill_each_worker(:KILL) end
Generated with the Darkfish Rdoc Generator 2.