Class: Unicorn::HttpServer

Inherits:
Object
  • Object
show all
Includes:
HttpResponse, SocketHelper
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 Unicorn::Configurator for information on how to configure unicorn.

Constant Summary collapse

LISTENERS =

all bound listener sockets note: this is public used by raindrops, but not recommended for use in new projects

[]
NEW_LISTENERS =

listeners we have yet to bind

[]
START_CTX =

:startdoc: 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 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/2.3.0/bin/unicorn"
{
  :argv => ARGV.map(&:dup),
  0 => $0.dup,
}

Instance Attribute Summary collapse

Instance Method Summary collapse

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.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/unicorn/http_server.rb', line 70

def initialize(app, options = {})
  @app = app
  @reexec_pid = 0
  @default_middleware = true
  options = options.dup
  @ready_pipe = options.delete(:ready_pipe)
  @init_listeners = options[:listeners] ? options[:listeners].dup : []
  options[:use_defaults] = true
  self.config = Unicorn::Configurator.new(options)
  self.listener_opts = {}

  # 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 do |i|
      Kgio::Pipe.for_fd(i)
    end
    worker_data
  end
end

Instance Attribute Details

#after_forkObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def after_fork
  @after_fork
end

#after_worker_exit=(value) ⇒ Object (writeonly)

Sets the attribute after_worker_exit

Parameters:

  • value

    the value to set the attribute after_worker_exit to.



19
20
21
# File 'lib/unicorn/http_server.rb', line 19

def after_worker_exit=(value)
  @after_worker_exit = value
end

#after_worker_ready=(value) ⇒ Object (writeonly)

Sets the attribute after_worker_ready

Parameters:

  • value

    the value to set the attribute after_worker_ready to.



19
20
21
# File 'lib/unicorn/http_server.rb', line 19

def after_worker_ready=(value)
  @after_worker_ready = value
end

#appObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def app
  @app
end

#before_execObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def before_exec
  @before_exec
end

#before_forkObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def before_fork
  @before_fork
end

#configObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def config
  @config
end

#default_middlewareObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def default_middleware
  @default_middleware
end

#early_hintsObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def early_hints
  @early_hints
end

#listener_optsObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def listener_opts
  @listener_opts
end

#loggerObject

Returns the value of attribute logger.



21
22
23
# File 'lib/unicorn/http_server.rb', line 21

def logger
  @logger
end

#orig_appObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def orig_app
  @orig_app
end

#pidObject

Returns the value of attribute pid.



21
22
23
# File 'lib/unicorn/http_server.rb', line 21

def pid
  @pid
end

#preload_appObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def preload_app
  @preload_app
end

#ready_pipeObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def ready_pipe
  @ready_pipe
end

#timeoutObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def timeout
  @timeout
end

#userObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def user
  @user
end

#worker_exec=(value) ⇒ Object (writeonly)

Sets the attribute worker_exec

Parameters:

  • value

    the value to set the attribute worker_exec to.



19
20
21
# File 'lib/unicorn/http_server.rb', line 19

def worker_exec=(value)
  @worker_exec = value
end

#worker_processesObject

:stopdoc:



14
15
16
# File 'lib/unicorn/http_server.rb', line 14

def worker_processes
  @worker_processes
end

Instance Method Details

#check_client_connectionObject



372
373
374
# File 'lib/unicorn/http_server.rb', line 372

def check_client_connection
  Unicorn::HttpRequest.check_client_connection
end

#check_client_connection=(bool) ⇒ Object



376
377
378
# File 'lib/unicorn/http_server.rb', line 376

def check_client_connection=(bool)
  Unicorn::HttpRequest.check_client_connection = bool
end

#client_body_buffer_sizeObject



364
365
366
# File 'lib/unicorn/http_server.rb', line 364

def client_body_buffer_size
  Unicorn::TeeInput.client_body_buffer_size
end

#client_body_buffer_size=(bytes) ⇒ Object



368
369
370
# File 'lib/unicorn/http_server.rb', line 368

def client_body_buffer_size=(bytes)
  Unicorn::TeeInput.client_body_buffer_size = bytes
end

#clobber_pid(path) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/unicorn/http_server.rb', line 181

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.syswrite("#$$\n")
    File.rename(fp.path, path)
    fp.close
  end
end

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



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
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
# File 'lib/unicorn/http_server.rb', line 268

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.message} (#{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

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



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/unicorn/http_server.rb', line 235

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.autoclose = false
      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

#listeners=(listeners) ⇒ Object

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



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# 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!

  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

#rewindable_inputObject



355
356
357
# File 'lib/unicorn/http_server.rb', line 355

def rewindable_input
  Unicorn::HttpRequest.input_class.method_defined?(:rewind)
end

#rewindable_input=(bool) ⇒ Object



359
360
361
362
# File 'lib/unicorn/http_server.rb', line 359

def rewindable_input=(bool)
  Unicorn::HttpRequest.input_class = bool ?
                              Unicorn::TeeInput : Unicorn::StreamInput
end

#startObject

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



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# 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

#stderr_path=(path) ⇒ Object



175
# File 'lib/unicorn/http_server.rb', line 175

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

#stdout_path=(path) ⇒ Object



174
# File 'lib/unicorn/http_server.rb', line 174

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

#stop(graceful = true) ⇒ Object

Terminates all workers, but does not exit master process



340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/unicorn/http_server.rb', line 340

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