Class: Unicorn::HttpServer
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Instance Chain:
self,
HttpResponse ,
SocketHelper
|
|
Inherits: | Object |
Defined in: | lib/unicorn/http_server.rb |
Overview
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.
Users do not need to know the internals of this class, but reading the source is education for programmers wishing to learn how unicorn works. See Configurator
for information on how to configure unicorn.
Constant Summary
-
LISTENERS =
Internal use only
all bound listener sockets note: this is public used by raindrops, but not recommended for use in new projects
[]
-
NEW_LISTENERS =
Internal use only
listeners we have yet to bind
[]
-
START_CTX =
# File 'lib/unicorn/http_server.rb', line 52{ :argv => ARGV.map(&:dup), 0 => $0.dup, }
SocketHelper
- Included
HttpResponse
- Included
Class Method Summary
-
.new(app, options = {}) ⇒ HttpServer
constructor
Internal use only
Creates a working server on host:port (strange things happen if port isn’t a Number).
Instance Attribute Summary
- #after_fork rw Internal use only
- #after_worker_exit=(value) writeonly Internal use only
- #after_worker_ready=(value) writeonly Internal use only
- #app rw Internal use only
- #before_exec rw Internal use only
- #before_fork rw Internal use only
- #check_client_connection rw Internal use only
- #check_client_connection=(bool) rw Internal use only
- #client_body_buffer_size rw Internal use only
- #client_body_buffer_size=(bytes) rw Internal use only
- #config rw Internal use only
- #default_middleware rw Internal use only
- #early_hints rw Internal use only
- #listener_opts rw Internal use only
-
#listeners=(listeners)
writeonly
Internal use only
replaces current listener set with
listeners
. - #logger rw Internal use only
- #logger=(obj) rw Internal use only
- #orig_app rw Internal use only
- #pid rw Internal use only
-
#pid=(path)
rw
Internal use only
sets the path for the PID file of the master process.
- #preload_app rw Internal use only
- #ready_pipe rw Internal use only
- #rewindable_input rw Internal use only
- #rewindable_input=(bool) rw Internal use only
- #stderr_path=(path) writeonly Internal use only
- #stdout_path=(path) writeonly Internal use only
- #timeout rw Internal use only
- #user rw Internal use only
- #worker_exec=(value) writeonly Internal use only
- #worker_processes rw Internal use only
Instance Method Summary
- #clobber_pid(path) Internal use only
-
#join
Internal use only
monitors children and receives signals forever (or until a termination signal is sent).
-
#listen(address, opt = {}.merge(listener_opts[address] || {}))
Internal use only
add a given address to the
listeners
set, idempotently Allows workers to add a private, per-process listener via the after_fork hook. -
#start
Internal use only
Runs the thing.
-
#stop(graceful = true)
Internal use only
Terminates all workers, but does not exit master process.
- #after_fork_internal private Internal use only
- #awaken_master private Internal use only
-
#bind_new_listeners!
private
Internal use only
call only after calling inherit_listeners! This binds any listeners we did NOT inherit from the parent.
- #build_app! private Internal use only
- #e100_response_write(client, env) private Internal use only
- #e103_response_write(client, headers) private Internal use only
-
#handle_error(client, e)
private
Internal use only
if we get any error, try to write something back to the client assuming we haven’t closed the socket, but don’t get hung up if the socket is already closed or broken.
- #inherit_listeners! private Internal use only
-
#init_worker_process(worker)
private
Internal use only
gets rid of stuff the worker has no business keeping track of to free some resources and drops all sig handlers.
-
#kill_each_worker(signal)
private
Internal use only
delivers a signal to each worker.
-
#kill_worker(signal, wpid)
private
Internal use only
delivers a signal to a worker and fails gracefully if the worker is no longer running.
-
#listener_names(listeners = LISTENERS)
private
Internal use only
returns an array of string names for the given listener array.
- #listener_sockets private Internal use only
- #load_config! private Internal use only
- #maintain_worker_count private Internal use only
-
#master_sleep(sec)
private
Internal use only
wait for a signal hander to wake us up and then consume the pipe.
-
#murder_lazy_workers
private
Internal use only
forcibly terminate all workers that haven’t checked in in timeout seconds.
- #nuke_listeners!(readers) private Internal use only
- #prep_readers(readers) private Internal use only
- #proc_name(tag) private Internal use only
-
#process_client(client, ai)
private
Internal use only
once a client is accepted, it is processed in its entirety here in 3 easy steps: read request, call app, write app response.
-
#reap_all_workers
private
Internal use only
reaps all unreaped workers.
- #redirect_io(io, path) private Internal use only
-
#reexec
private
Internal use only
reexecutes the START_CTX with a new binary.
- #reopen_worker_logs(worker_nr) private Internal use only
- #soft_kill_each_worker(signal) private Internal use only
- #spawn_missing_workers private Internal use only
-
#unlink_pid_safe(path)
private
Internal use only
unlinks a PID file at given
path
if it contains the current PID still potentially racy without locking the directory (which is non-portable and may interact badly with other programs), but the window for hitting the race condition is small. -
#valid_pid?(path) ⇒ Boolean
private
Internal use only
returns a PID if a given path contains a non-stale PID file, nil otherwise.
-
#worker_loop(worker)
private
Internal use only
runs inside each forked worker, this sits around and waits for connections and doesn’t die until the parent dies (or is given a INT, QUIT, or TERM signal).
- #worker_spawn(worker) private Internal use only
HttpResponse
- Included
#httpdate | Returns a string which represents the time as rfc1123-date of HTTP-date defined by RFC 2616: |
#append_header, | |
#err_response | internal API, code will always be common-enough-for-even-old-Rack. |
#http_response_write | writes the rack_response to socket as an HTTP response. |
SocketHelper
- Included
#bind_listen | creates a new server, socket. |
#log_buffer_sizes, #new_tcp_server, #set_server_sockopt, #set_tcp_sockopt, | |
#sock_name | Returns the configuration name of a socket as a string. |
#tcp_name | returns rfc2732-style (e.g. |
Constructor Details
.new(app, options = {}) ⇒ HttpServer
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 71
def initialize(app, = {}) @app = app @reexec_pid = 0 @default_middleware = true = .dup @ready_pipe = .delete(:ready_pipe) @init_listeners = [:listeners] ? [:listeners].dup : [] [:use_defaults] = true self.config = Unicorn::Configurator.new( ) self.listener_opts = {} @immortal = [] # immortal inherited sockets from systemd # We use @self_pipe differently in the master and worker processes: # # * The master process never closes or reinitializes this once # initialized. Signal handlers in the master process will write to # it to wake up the master from IO.select in exactly the same manner # djb describes in https://cr.yp.to/docs/selfpipe.html # # * The workers immediately close the pipe they inherit. See the # Unicorn::Worker class for the pipe workers use. @self_pipe = [] @workers = {} # hash maps PIDs to Workers @sig_queue = [] # signal queue used for self-piping @pid = nil # 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]) @orig_app = app # list of signals we care about and trap in master. @queue_sigs = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ] @worker_data = if worker_data = ENV['UNICORN_WORKER'] worker_data = worker_data.split(',').map!(&:to_i) worker_data[1] = worker_data.slice!(1..2).map { |i| IO.for_fd(i) } worker_data end end
Instance Attribute Details
#after_fork (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#after_worker_exit=(value) (writeonly)
# File 'lib/unicorn/http_server.rb', line 20
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
#after_worker_ready=(value) (writeonly)
# File 'lib/unicorn/http_server.rb', line 20
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
#app (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#before_exec (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#before_fork (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#check_client_connection (rw)
# File 'lib/unicorn/http_server.rb', line 370
def check_client_connection Unicorn::HttpRequest.check_client_connection end
#check_client_connection=(bool) (rw)
# File 'lib/unicorn/http_server.rb', line 374
def check_client_connection=(bool) Unicorn::HttpRequest.check_client_connection = bool end
#client_body_buffer_size (rw)
# File 'lib/unicorn/http_server.rb', line 362
def client_body_buffer_size Unicorn::TeeInput.client_body_buffer_size end
#client_body_buffer_size=(bytes) (rw)
# File 'lib/unicorn/http_server.rb', line 366
def client_body_buffer_size=(bytes) Unicorn::TeeInput.client_body_buffer_size = bytes end
#config (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#default_middleware (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#early_hints (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#listener_opts (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#listeners=(listeners) (writeonly)
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 149
def listeners=(listeners) cur_names, dead_names = [], [] listener_names.each do |name| if name.start_with?('/') # 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! dead_names -= @immortal.map { |io| sock_name(io) } LISTENERS.delete_if do |io| if dead_names.include?(sock_name(io)) (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
#logger (rw)
# File 'lib/unicorn/http_server.rb', line 22
attr_reader :pid, :logger
#logger=(obj) (rw)
# File 'lib/unicorn/http_server.rb', line 178
def logger=(obj) Unicorn::HttpRequest::DEFAULTS["rack.logger"] = @logger = obj end
#orig_app (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#pid (rw)
# File 'lib/unicorn/http_server.rb', line 22
attr_reader :pid, :logger
#pid=(path) (rw)
sets the path for the PID file of the master process
# File 'lib/unicorn/http_server.rb', line 199
def pid=(path) if path if x = valid_pid?(path) return path if pid && path == pid && x == $$ if x == @reexec_pid && pid.end_with?('.oldbin') 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 # rename the old pid if possible if @pid && path begin File.rename(@pid, path) rescue Errno::ENOENT, Errno::EXDEV # a user may have accidentally removed the original, # obviously cross-FS renames don't work, either. clobber_pid(path) end else clobber_pid(path) end @pid = path end
#preload_app (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#ready_pipe (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#rewindable_input (rw)
# File 'lib/unicorn/http_server.rb', line 353
def rewindable_input Unicorn::HttpRequest.input_class.method_defined?(:rewind) end
#rewindable_input=(bool) (rw)
# File 'lib/unicorn/http_server.rb', line 357
def rewindable_input=(bool) Unicorn::HttpRequest.input_class = bool ? Unicorn::TeeInput : Unicorn::StreamInput end
#stderr_path=(path) (writeonly)
# File 'lib/unicorn/http_server.rb', line 176
def stderr_path=(path); redirect_io($stderr, path); end
#stdout_path=(path) (writeonly)
# File 'lib/unicorn/http_server.rb', line 175
def stdout_path=(path); redirect_io($stdout, path); end
#timeout (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#user (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
#worker_exec=(value) (writeonly)
# File 'lib/unicorn/http_server.rb', line 20
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
#worker_processes (rw)
# File 'lib/unicorn/http_server.rb', line 15
attr_accessor :app, :timeout, :worker_processes, :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user, :default_middleware, :early_hints
Instance Method Details
#after_fork_internal (private)
# File 'lib/unicorn/http_server.rb', line 498
def after_fork_internal @self_pipe.each(&:close).clear # this is master-only, now @ready_pipe.close if @ready_pipe Unicorn::Configurator::RACKUP.clear @ready_pipe = @init_listeners = @before_exec = @before_fork = nil # The OpenSSL PRNG is seeded with only the pid, and apps with frequently # dying workers can recycle pids OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random) end
#awaken_master (private)
# File 'lib/unicorn/http_server.rb', line 390
def awaken_master return if $$ != @master_pid # wakeup master process from select @self_pipe[1].write_nonblock('.', exception: false) end
#bind_new_listeners! (private)
call only after calling inherit_listeners! This binds any listeners we did NOT inherit from the parent
# File 'lib/unicorn/http_server.rb', line 843
def bind_new_listeners! NEW_LISTENERS.each { |addr| listen(addr) }.clear raise ArgumentError, "no listeners" if LISTENERS.empty? end
#build_app! (private)
#clobber_pid(path)
# File 'lib/unicorn/http_server.rb', line 182
def clobber_pid(path) 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.sync = true fp.write("#$$\n") File.rename(fp.path, path) fp.close end end
#e100_response_write(client, env) (private)
# File 'lib/unicorn/http_server.rb', line 579
def e100_response_write(client, env) # We use String#freeze to avoid allocations under Ruby 2.1+ # Not many users hit this code path, so it's better to reduce the # constant table sizes even for Ruby 2.0 users who'll hit extra # allocations here. client.write(@request.response_start_sent ? "100 Continue\r\n\r\nHTTP/1.1 ".freeze : "HTTP/1.1 100 Continue\r\n\r\n".freeze) env.delete('HTTP_EXPECT'.freeze) end
#e103_response_write(client, headers) (private)
# File 'lib/unicorn/http_server.rb', line 571
def e103_response_write(client, headers) rss = @request.response_start_sent buf = rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n" headers.each { |key, value| append_header(buf, key, value) } buf << (rss ? "\r\nHTTP/1.1 ".freeze : "\r\n".freeze) client.write(buf) end
#handle_error(client, e) (private)
if we get any error, try to write something back to the client assuming we haven’t closed the socket, but don’t get hung up if the socket is already closed or broken. We’ll always ensure the socket is closed at the end of this function
# File 'lib/unicorn/http_server.rb', line 549
def handle_error(client, e) code = case e when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::ENOTCONN # client disconnected on us and there's nothing we can do when Unicorn::RequestURITooLongError 414 when Unicorn::RequestEntityTooLargeError 413 when Unicorn::HttpParserError # try to tell the client they're bad 400 else Unicorn.log_error(@logger, "app error", e) 500 end if code code = err_response(code, @request.response_start_sent) client.write_nonblock(code, exception: false) end client.close rescue end
#inherit_listeners! (private)
# File 'lib/unicorn/http_server.rb', line 804
def inherit_listeners! # inherit sockets from parents, they need to be plain Socket objects inherited = ENV['UNICORN_FD'].to_s.split(',') immortal = [] # emulate sd_listen_fds() for systemd sd_pid, sd_fds = ENV.values_at('LISTEN_PID', 'LISTEN_FDS') if sd_pid.to_i == $$ # n.b. $$ can never be zero # 3 = SD_LISTEN_FDS_START immortal = (3...(3 + sd_fds.to_i)).to_a inherited.concat(immortal) end # to ease debugging, we will not unset LISTEN_PID and LISTEN_FDS inherited.map! do |fd| io = Socket.for_fd(fd.to_i) @immortal << io if immortal.include?(fd) set_server_sockopt(io, listener_opts[sock_name(io)]) logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}" io end config_listeners = config[:listeners].dup LISTENERS.replace(inherited) # we only use generic Socket objects for aggregate Socket#accept_nonblock # return value [ Socket, Addrinfo ]. This allows us to avoid having to # make getpeername(2) syscalls later on to fill in env['REMOTE_ADDR'] 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 NEW_LISTENERS.replace(config_listeners) end
#init_worker_process(worker) (private)
gets rid of stuff the worker has no business keeping track of to free some resources and drops all sig handlers. traps for USR1, USR2, and HUP may be set in the after_fork Proc by the user.
# File 'lib/unicorn/http_server.rb', line 641
def init_worker_process(worker) worker.atfork_child # we'll re-trap :QUIT later for graceful shutdown iff we accept clients exit_sigs = [ :QUIT, :TERM, :INT ] exit_sigs.each { |sig| trap(sig) { exit!(0) } } exit!(0) if (@sig_queue & exit_sigs)[0] (@queue_sigs - exit_sigs).each { |sig| trap(sig, nil) } trap(:CHLD, 'DEFAULT') @sig_queue.clear proc_name "worker[#{worker.nr}]" START_CTX.clear @workers.clear after_fork.call(self, worker) # can drop perms and create listeners LISTENERS.each { |sock| sock.close_on_exec = true } worker.user(*user) if user.kind_of?(Array) && ! worker.switched @config = nil build_app! unless preload_app @after_fork = @listener_opts = @orig_app = nil readers = LISTENERS.dup readers << worker trap(:QUIT) { nuke_listeners!(readers) } readers end
#join
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 266
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 begin @ready_pipe.syswrite($$.to_s) rescue => e logger.warn("grandparent died too soon?: #{e.} (#{e.class})") end @ready_pipe = @ready_pipe.close rescue 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 sleep_time = @timeout/2.0 + 1 @logger.debug("waiting #{sleep_time}s after suspend/hibernation") 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" soft_kill_each_worker(:USR1) when :USR2 # exec binary, stay alive in case something went wrong reexec when :WINCH if $stdin.tty? logger.info "SIGWINCH ignored because we're not daemonized" else respawn = false logger.info "gracefully stopping all workers" soft_kill_each_worker(:QUIT) self.worker_processes = 0 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 => e Unicorn.log_error(@logger, "master loop error", e) end while true stop # gracefully shutdown all workers on our way out logger.info "master complete" unlink_pid_safe(pid) if pid end
#kill_each_worker(signal) (private)
delivers a signal to each worker
# File 'lib/unicorn/http_server.rb', line 732
def kill_each_worker(signal) @workers.keys.each { |wpid| kill_worker(signal, wpid) } end
#kill_worker(signal, wpid) (private)
delivers a signal to a worker and fails gracefully if the worker is no longer running.
# File 'lib/unicorn/http_server.rb', line 725
def kill_worker(signal, wpid) Process.kill(signal, wpid) rescue Errno::ESRCH worker = @workers.delete(wpid) and worker.close rescue nil end
#listen(address, opt = {}.merge(listener_opts[address] || {}))
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 237
def listen(address, opt = {}.merge(listener_opts[address] || {})) address = config. (address) return if String === address && listener_names.include?(address) delay = opt[:delay] || 0.5 tries = opt[:tries] || 5 begin io = bind_listen(address, opt) 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
#listener_names(listeners = LISTENERS) (private)
returns an array of string names for the given listener array
# File 'lib/unicorn/http_server.rb', line 780
def listener_names(listeners = LISTENERS) listeners.map { |io| sock_name(io) } end
#listener_sockets (private)
# File 'lib/unicorn/http_server.rb', line 471
def listener_sockets listener_fds = {} LISTENERS.each { |sock| listener_fds[sock.fileno] = sock } listener_fds end
#load_config! (private)
# File 'lib/unicorn/http_server.rb', line 762
def load_config! loaded_app = app logger.info "reloading config_file=#{config.config_file}" config[:listeners].replace(@init_listeners) config.reload config.commit!(self) soft_kill_each_worker(:QUIT) Unicorn::Util.reopen_logs self.app = @orig_app build_app! if preload_app logger.info "done reloading config_file=#{config.config_file}" rescue StandardError, LoadError, SyntaxError => e Unicorn.log_error(@logger, "error reloading config_file=#{config.config_file}", e) self.app = loaded_app end
#maintain_worker_count (private)
# File 'lib/unicorn/http_server.rb', line 539
def maintain_worker_count (off = @workers.size - worker_processes) == 0 and return off < 0 and return spawn_missing_workers @workers.each_value { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) } end
#master_sleep(sec) (private)
wait for a signal hander to wake us up and then consume the pipe
# File 'lib/unicorn/http_server.rb', line 381
def master_sleep(sec) @self_pipe[0].wait(sec) or return # 11 bytes is the maximum string length which can be embedded within # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+). # Most reads are only one byte here and uncommon, so it's not worth a # persistent buffer, either: @self_pipe[0].read_nonblock(11, exception: false) end
#murder_lazy_workers (private)
forcibly terminate all workers that haven’t checked in in timeout seconds. The timeout is implemented using an unlinked File
# File 'lib/unicorn/http_server.rb', line 478
def murder_lazy_workers next_sleep = @timeout - 1 now = time_now.to_i @workers.dup.each_pair do |wpid, worker| tick = worker.tick 0 == tick and next # skip workers that haven't processed any clients diff = now - tick tmp = @timeout - diff if tmp >= 0 next_sleep > tmp and next_sleep = tmp next end next_sleep = 0 logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \ "(#{diff}s > #{@timeout}s), killing" kill_worker(:KILL, wpid) # take no prisoners for timeout violations end next_sleep <= 0 ? 1 : next_sleep end
#nuke_listeners!(readers) (private)
# File 'lib/unicorn/http_server.rb', line 630
def nuke_listeners!(readers) # only called from the worker, ordering is important here tmp = readers.dup readers.replace([false]) # ensure worker does not continue ASAP tmp.each { |io| io.close rescue nil } # break out of IO.select end
#prep_readers(readers) (private)
# File 'lib/unicorn/http_server.rb', line 677
def prep_readers(readers) wtr = Unicorn::Waiter.prep_readers(readers) @timeout *= 500 # to milliseconds for epoll, but halved wtr rescue require_relative 'select_waiter' @timeout /= 2.0 # halved for IO.select Unicorn::SelectWaiter.new end
#proc_name(tag) (private)
#process_client(client, ai) (private)
once a client is accepted, it is processed in its entirety here in 3 easy steps: read request, call app, write app response
# File 'lib/unicorn/http_server.rb', line 592
def process_client(client, ai) @request = Unicorn::HttpRequest.new env = @request.read_headers(client, ai) if early_hints env["rack.early_hints"] = lambda do |headers| e103_response_write(client, headers) end end env["rack.after_reply"] = [] status, headers, body = @app.call(env) begin return if @request.hijacked? if 100 == status.to_i e100_response_write(client, env) status, headers, body = @app.call(env) return if @request.hijacked? end @request.headers? or headers = nil http_response_write(client, status, headers, body, @request) ensure body.respond_to?(:close) and body.close end unless client.closed? # rack.hijack may've close this for us client.shutdown # in case of fork() in Rack app client.close # flush and uncork socket immediately, no keepalive end rescue => e handle_error(client, e) ensure env["rack.after_reply"].each(&:call) if env end
#reap_all_workers (private)
reaps all unreaped workers
# File 'lib/unicorn/http_server.rb', line 397
def reap_all_workers begin wpid, status = Process.waitpid2(-1, Process::WNOHANG) wpid or return if @reexec_pid == wpid logger.error "reaped #{status.inspect} exec()-ed" @reexec_pid = 0 self.pid = pid.chomp('.oldbin') if pid proc_name 'master' else worker = @workers.delete(wpid) and worker.close rescue nil @after_worker_exit.call(self, worker, status) end rescue Errno::ECHILD break end while true end
#redirect_io(io, path) (private)
# File 'lib/unicorn/http_server.rb', line 799
def redirect_io(io, path) File.open(path, 'ab') { |fp| io.reopen(fp) } if path io.sync = true end
#reexec (private)
reexecutes the START_CTX with a new binary
# File 'lib/unicorn/http_server.rb', line 416
def reexec if @reexec_pid > 0 begin Process.kill(0, @reexec_pid) logger.error "reexec-ed child already running PID:#@reexec_pid" return rescue Errno::ESRCH @reexec_pid = 0 end end if pid old_pid = "#{pid}.oldbin" begin self.pid = old_pid # clear the path for a new pid file rescue ArgumentError logger.error "old PID:#{valid_pid?(old_pid)} running with " \ "existing pid=#{old_pid}, refusing rexec" return rescue => e logger.error "error writing pid=#{old_pid} #{e.class} #{e.}" return end end @reexec_pid = fork do listener_fds = listener_sockets ENV['UNICORN_FD'] = listener_fds.keys.join(',') Dir.chdir(START_CTX[:cwd]) cmd = [ START_CTX[0] ].concat(START_CTX[:argv]) # exec(command, hash) works in at least 1.9.1+, but will only be # required in 1.9.4/2.0.0 at earliest. cmd << listener_fds logger.info "executing #{cmd.inspect} (in #{Dir.pwd})" before_exec.call(self) exec(*cmd) end proc_name 'master (old)' end
#reopen_worker_logs(worker_nr) (private)
#soft_kill_each_worker(signal) (private)
# File 'lib/unicorn/http_server.rb', line 736
def soft_kill_each_worker(signal) @workers.each_value { |worker| worker.soft_kill(signal) } end
#spawn_missing_workers (private)
# File 'lib/unicorn/http_server.rb', line 509
def spawn_missing_workers if @worker_data worker = Unicorn::Worker.new(*@worker_data) after_fork_internal worker_loop(worker) exit end worker_nr = -1 until (worker_nr += 1) == @worker_processes @workers.value?(worker_nr) and next worker = Unicorn::Worker.new(worker_nr) before_fork.call(self, worker) pid = @worker_exec ? worker_spawn(worker) : fork unless pid after_fork_internal worker_loop(worker) exit end @workers[pid] = worker worker.atfork_parent end rescue => e @logger.error(e) rescue nil exit! end
#start
Runs the thing. Returns self so you can run join on it
# File 'lib/unicorn/http_server.rb', line 121
def start inherit_listeners! # this pipe is used to wake us up from select(2) in #join when signals # are trapped. See trap_deferred. @self_pipe.replace(Unicorn.pipe) @master_pid = @worker_data ? Process.ppid : $$ # 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 } # write pid early for Mongrel compatibility if we're not inheriting sockets # This is needed for compatibility some Monit setups at least. # This unfortunately has the side effect of clobbering valid PID if # we upgrade and the upgrade breaks during preload_app==true && build_app! self.pid = config[:pid] build_app! if preload_app bind_new_listeners! spawn_missing_workers self end
#stop(graceful = true)
Terminates all workers, but does not exit master process
# File 'lib/unicorn/http_server.rb', line 338
def stop(graceful = true) self.listeners = [] limit = time_now + timeout until @workers.empty? || time_now > limit if graceful soft_kill_each_worker(:QUIT) else kill_each_worker(:TERM) end sleep(0.1) reap_all_workers end kill_each_worker(:KILL) end
#unlink_pid_safe(path) (private)
unlinks a PID file at given path
if it contains the current PID still potentially racy without locking the directory (which is non-portable and may interact badly with other programs), but the window for hitting the race condition is small
# File 'lib/unicorn/http_server.rb', line 744
def unlink_pid_safe(path) (File.read(path).to_i == $$ and File.unlink(path)) rescue nil end
#valid_pid?(path) ⇒ Boolean
(private)
returns a PID if a given path contains a non-stale PID file, nil otherwise.
# File 'lib/unicorn/http_server.rb', line 750
def valid_pid?(path) wpid = File.read(path).to_i wpid <= 0 and return Process.kill(0, wpid) wpid rescue Errno::EPERM logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}" nil rescue Errno::ESRCH, Errno::ENOENT # don't unlink stale pid files, racy without non-portable locking... end
#worker_loop(worker) (private)
runs inside each forked worker, this sits around and waits for connections and doesn’t die until the parent dies (or is given a INT, QUIT, or TERM signal)
# File 'lib/unicorn/http_server.rb', line 690
def worker_loop(worker) readers = init_worker_process(worker) waiter = prep_readers(readers) reopen = false # this only works immediately if the master sent us the signal # (which is the normal case) trap(:USR1) { reopen = true } ready = readers.dup @after_worker_ready.call(self, worker) begin reopen = reopen_worker_logs(worker.nr) if reopen worker.tick = time_now.to_i while sock = ready.shift client_ai = sock.accept_nonblock(exception: false) if client_ai != :wait_readable process_client(*client_ai) worker.tick = time_now.to_i end break if reopen end # timeout so we can .tick and keep parent from SIGKILL-ing us worker.tick = time_now.to_i waiter.get_readers(ready, readers, @timeout) rescue => e redo if reopen && readers[0] Unicorn.log_error(@logger, "listen loop error", e) if readers[0] end while readers[0] end
#worker_spawn(worker) (private)
# File 'lib/unicorn/http_server.rb', line 457
def worker_spawn(worker) listener_fds = listener_sockets env = {} env['UNICORN_FD'] = listener_fds.keys.join(',') listener_fds[worker.to_io.fileno] = worker.to_io listener_fds[worker.master.fileno] = worker.master worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno] env['UNICORN_WORKER'] = worker_info.join(',') Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds) end