Class: Unicorn::HttpServer

Inherits:
Struct
  • Object
show all
Includes:
SocketHelper
Defined in:
lib/unicorn.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.

Defined Under Namespace

Classes: Worker

Constant Summary collapse

IO_PURGATORY =

prevents IO objects in here from being GC-ed

[]
LISTENERS =

all bound listener sockets

[]
WORKERS =

This hash maps PIDs to Workers

{}
SELF_PIPE =

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 cr.yp.to/docs/selfpipe.html

  • The workers immediately close the pipe they inherit from the

master and replace it with a new pipe after forking. This new pipe is also used to wakeup from IO.select from inside (worker) signal handlers. However, workers close the pipe descriptors in the signal handlers to raise EBADF in IO.select instead of writing like we do in the master. We cannot easily use the reader set for IO.select because LISTENERS is already that set, and it’s extra work (and cycles) to distinguish the pipe FD from the reader set once IO.select returns. So we’re lazy and just close the pipe when a (rare) signal arrives in the worker and reinitialize the pipe later.

[]
SIG_QUEUE =

signal queue used for self-piping

[]
REQUEST =

constant lookups are faster and we’re single-threaded/non-reentrant

HttpRequest.new
START_CTX =

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.

The following example may be used in your Unicorn config file to change your working directory during a config reload (HUP) without upgrading or restarting:

Dir.chdir(Unicorn::HttpServer::START_CTX[:cwd] = path)

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"
{
  :argv => ARGV.map { |arg| arg.dup },
  :cwd => lambda {
      # favor ENV['PWD'] since it is (usually) symlink aware for
      # Capistrano and like systems
      begin
        a = File.stat(pwd = ENV['PWD'])
        b = File.stat(Dir.pwd)
        a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
      rescue
        Dir.pwd
      end
    }.call,
  0 => $0.dup,
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from SocketHelper

#bind_listen, #log_buffer_sizes, #server_cast, #set_server_sockopt, #set_tcp_sockopt, #sock_name

Constructor Details

#initialize(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.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/unicorn.rb', line 126

def initialize(app, options = {})
  self.app = app
  self.reexec_pid = 0
  self.init_listeners = options[:listeners] ? options[:listeners].dup : []
  self.config = Configurator.new(options.merge(:use_defaults => true))
  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

Instance Attribute Details

#after_forkObject

Returns the value of attribute after_fork

Returns:

  • (Object)

    the current value of after_fork



29
30
31
# File 'lib/unicorn.rb', line 29

def after_fork
  @after_fork
end

#appObject

Returns the value of attribute app

Returns:

  • (Object)

    the current value of app



29
30
31
# File 'lib/unicorn.rb', line 29

def app
  @app
end

#before_execObject

Returns the value of attribute before_exec

Returns:

  • (Object)

    the current value of before_exec



29
30
31
# File 'lib/unicorn.rb', line 29

def before_exec
  @before_exec
end

#before_forkObject

Returns the value of attribute before_fork

Returns:

  • (Object)

    the current value of before_fork



29
30
31
# File 'lib/unicorn.rb', line 29

def before_fork
  @before_fork
end

#configObject

Returns the value of attribute config

Returns:

  • (Object)

    the current value of config



29
30
31
# File 'lib/unicorn.rb', line 29

def config
  @config
end

#init_listenersObject

Returns the value of attribute init_listeners

Returns:

  • (Object)

    the current value of init_listeners



29
30
31
# File 'lib/unicorn.rb', line 29

def init_listeners
  @init_listeners
end

#listener_optsObject

Returns the value of attribute listener_opts

Returns:

  • (Object)

    the current value of listener_opts



29
30
31
# File 'lib/unicorn.rb', line 29

def listener_opts
  @listener_opts
end

#loggerObject

Returns the value of attribute logger

Returns:

  • (Object)

    the current value of logger



29
30
31
# File 'lib/unicorn.rb', line 29

def logger
  @logger
end

#master_pidObject

Returns the value of attribute master_pid

Returns:

  • (Object)

    the current value of master_pid



29
30
31
# File 'lib/unicorn.rb', line 29

def master_pid
  @master_pid
end

#orig_appObject

Returns the value of attribute orig_app

Returns:

  • (Object)

    the current value of orig_app



29
30
31
# File 'lib/unicorn.rb', line 29

def orig_app
  @orig_app
end

#pidObject

Returns the value of attribute pid

Returns:

  • (Object)

    the current value of pid



29
30
31
# File 'lib/unicorn.rb', line 29

def pid
  @pid
end

#preload_appObject

Returns the value of attribute preload_app

Returns:

  • (Object)

    the current value of preload_app



29
30
31
# File 'lib/unicorn.rb', line 29

def preload_app
  @preload_app
end

#reexec_pidObject

Returns the value of attribute reexec_pid

Returns:

  • (Object)

    the current value of reexec_pid



29
30
31
# File 'lib/unicorn.rb', line 29

def reexec_pid
  @reexec_pid
end

#timeoutObject

Returns the value of attribute timeout

Returns:

  • (Object)

    the current value of timeout



29
30
31
# File 'lib/unicorn.rb', line 29

def timeout
  @timeout
end

#worker_processesObject

Returns the value of attribute worker_processes

Returns:

  • (Object)

    the current value of worker_processes



29
30
31
# File 'lib/unicorn.rb', line 29

def worker_processes
  @worker_processes
end

Instance Method Details

#joinObject

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.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/unicorn.rb', line 283

def join
  # this pipe is used to wake us up from select(2) in #join when signals
  # are trapped.  See trap_deferred
  init_self_pipe!
  respawn = true
  last_check = Time.now

  QUEUE_SIGS.each { |sig| trap_deferred(sig) }
  trap(:CHLD) { |sig_nr| awaken_master }
  proc_name 'master'
  logger.info "master process ready" # test_exec.rb relies on this message
  begin
    loop do
      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)
          murder_lazy_workers
        end
        maintain_worker_count if respawn
        master_sleep
      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)
        else
          logger.info "SIGWINCH ignored because we're not daemonized"
        end
      when :TTIN
        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!
          redo # immediate reaping since we may have QUIT workers
        else # exec binary and exit if there's no config file
          logger.info "config_file not present, reexecuting binary"
          reexec
          break
        end
      end
    end
  rescue Errno::EINTR
    retry
  rescue Object => e
    logger.error "Unhandled master loop exception #{e.inspect}."
    logger.error e.backtrace.join("\n")
    retry
  end
  stop # gracefully shutdown all workers on our way out
  logger.info "master complete"
  unlink_pid_safe(pid) if pid
end

#listen(address, opt = {}.merge(listener_opts[address] || {})) ⇒ Object

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.



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/unicorn.rb', line 253

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 TCPServer === io || UNIXServer === io
      IO_PURGATORY << io
      io = server_cast(io)
    end
    logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
    LISTENERS << io
    return 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
  end
end

#listeners=(listeners) ⇒ Object

replaces current listener set with listeners. This will close the socket if it will not exist in the new listener set



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/unicorn.rb', line 185

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

#set_pidObject

Sets the attribute pid

Parameters:

  • value (Object)

    the value to set the attribute pid to.

Returns:

  • (Object)

    the newly set value



216
217
218
# File 'lib/unicorn.rb', line 216

def pid=(value)
  @pid = value
end

#startObject

Runs the thing. Returns self so you can run join on it

Raises:

  • (ArgumentError)


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/unicorn.rb', line 148

def start
  BasicSocket.do_not_reverse_lookup = true

  # inherit sockets from parents, they need to be plain Socket objects
  # before they become UNIXServer or 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
  # TCPServer or 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?
  self.pid = config[:pid]
  self.master_pid = $$
  build_app! if preload_app
  maintain_worker_count
  self
end

#stderr_path=(path) ⇒ Object



214
# File 'lib/unicorn.rb', line 214

def stderr_path=(path); redirect_io($stderr, path); end

#stdout_path=(path) ⇒ Object



213
# File 'lib/unicorn.rb', line 213

def stdout_path=(path); redirect_io($stdout, path); end

#stop(graceful = true) ⇒ Object

Terminates all workers, but does not exit master process



355
356
357
358
359
360
361
362
363
364
# File 'lib/unicorn.rb', line 355

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