Parent

Namespace

Files

Puma::Cluster

Public Class Methods

new(cli) click to toggle source
# File lib/puma/cluster.rb, line 5
def initialize(cli)
  super cli

  @phase = 0
  @workers = []

  @phased_state = :idle
  @phased_restart = false
end

Public Instance Methods

all_workers_booted?() click to toggle source
# File lib/puma/cluster.rb, line 81
def all_workers_booted?
  @workers.count { |w| !w.booted? } == 0
end
check_workers() click to toggle source
# File lib/puma/cluster.rb, line 85
def check_workers
  while @workers.any?
    pid = Process.waitpid(-1, Process::WNOHANG)
    break unless pid

    @workers.delete_if { |w| w.pid == pid }
  end

  spawn_workers

  if all_workers_booted?
    # If we're running at proper capacity, check to see if
    # we need to phase any workers out (which will restart
    # in the right phase).
    #
    w = @workers.find { |x| x.phase != @phase }

    if w
      if @phased_state == :idle
        @phased_state = :waiting
        log "- Stopping #{w.pid} for phased upgrade..."
      end

      w.term
      log "- #{w.signal} sent to #{w.pid}..."
    end
  end
end
halt() click to toggle source
# File lib/puma/cluster.rb, line 193
def halt
  @status = :halt
  wakeup!
end
phased_restart() click to toggle source
# File lib/puma/cluster.rb, line 172
def phased_restart
  return false if @options[:preload_app]

  @phased_restart = true
  wakeup!

  true
end
preload?() click to toggle source
# File lib/puma/cluster.rb, line 202
def preload?
  @options[:preload_app]
end
restart() click to toggle source
# File lib/puma/cluster.rb, line 167
def restart
  @restart = true
  stop
end
run() click to toggle source
# File lib/puma/cluster.rb, line 206
def run
  @status = :run

  output_header "cluster"

  log "* Process workers: #{@options[:workers]}"

  if preload?
    log "* Preloading application"
    load_and_bind
  else
    log "* Phased restart available"

    unless @cli.config.app_configured?
      error "No application configured, nothing to run"
      exit 1
    end

    @cli.binder.parse @options[:binds], self
  end

  read, @wakeup = Puma::Util.pipe

  Signal.trap "SIGCHLD" do
    wakeup!
  end

  master_pid = Process.pid

  Signal.trap "SIGTERM" do
    # The worker installs their own SIGTERM when booted.
    # Until then, this is run by the worker and the worker
    # should just exit if they get it.
    if Process.pid != master_pid
      log "Early termination of worker"
      exit! 0
    else
      stop
    end
  end

  # Used by the workers to detect if the master process dies.
  # If select says that @check_pipe is ready, it's because the
  # master has exited and @suicide_pipe has been automatically
  # closed.
  #
  @check_pipe, @suicide_pipe = Puma::Util.pipe

  if daemon?
    log "* Daemonizing..."
    Process.daemon(true)
  else
    log "Use Ctrl-C to stop"
  end

  redirect_io

  start_control

  @cli.write_state

  @master_read, @worker_write = read, @wakeup
  spawn_workers

  Signal.trap "SIGINT" do
    stop
  end

  @cli.events.fire_on_booted!

  begin
    while @status == :run
      begin
        res = IO.select([read], nil, nil, 5)

        if res
          req = read.read_nonblock(1)

          if req == "b"
            pid = read.gets.to_i
            w = @workers.find { |x| x.pid == pid }
            if w
              w.boot!
              log "- Worker #{pid} booted, phase: #{w.phase}"
            else
              log "! Out-of-sync worker list, no #{pid} worker"
            end
          end
        end

        if @phased_restart
          start_phased_restart
          @phased_restart = false
        end

        check_workers

      rescue Interrupt
        @status = :stop
      end
    end

    stop_workers unless @status == :halt
  ensure
    @check_pipe.close
    @suicide_pipe.close
    read.close
    @wakeup.close
  end
end
spawn_workers() click to toggle source
# File lib/puma/cluster.rb, line 63
def spawn_workers
  diff = @options[:workers] - @workers.size

  upgrade = (@phased_state == :waiting)

  master = Process.pid

  diff.times do
    pid = fork { worker(upgrade, master) }
    @cli.debug "Spawned worker: #{pid}"
    @workers << Worker.new(pid, @phase)
  end

  if diff > 0
    @phased_state = :idle
  end
end
start_phased_restart() click to toggle source
# File lib/puma/cluster.rb, line 26
def start_phased_restart
  @phase += 1
  log "- Starting phased worker restart, phase: #{@phase}"
end
stats() click to toggle source
# File lib/puma/cluster.rb, line 198
def stats
  %{ "workers": #{@workers.size}, "phase": #{@phase} }!
end
stop() click to toggle source
# File lib/puma/cluster.rb, line 181
def stop
  @status = :stop
  wakeup!
end
stop_blocked() click to toggle source
# File lib/puma/cluster.rb, line 186
def stop_blocked
  @status = :stop if @status == :run
  wakeup!
  @control.stop(true) if @control
  Process.waitall
end
stop_workers() click to toggle source
# File lib/puma/cluster.rb, line 15
def stop_workers
  log "- Gracefully shutting down workers..."
  @workers.each { |x| x.term }

  begin
    Process.waitall
  rescue Interrupt
    log "! Cancelled waiting for workers"
  end
end
wakeup!() click to toggle source
# File lib/puma/cluster.rb, line 114
def wakeup!
  begin
    @wakeup.write "!" unless @wakeup.closed?
  rescue SystemCallError, IOError
  end
end
worker(upgrade, master) click to toggle source
# File lib/puma/cluster.rb, line 121
def worker(upgrade, master)
  $0 = "puma: cluster worker: #{master}"
  Signal.trap "SIGINT", "IGNORE"

  @master_read.close
  @suicide_pipe.close

  Thread.new do
    IO.select [@check_pipe]
    log "! Detected parent died, dying"
    exit! 1
  end

  # Be sure to change the directory again before loading
  # the app. This way we can pick up new code.
  if upgrade
    if dir = @options[:worker_directory]
      log "+ Changing to #{dir}"
      Dir.chdir dir
    end
  end

  # Invoke any worker boot hooks so they can get
  # things in shape before booting the app.
  hooks = @options[:worker_boot]
  hooks.each { |h| h.call }

  server = start_server

  Signal.trap "SIGTERM" do
    server.stop
  end

  begin
    @worker_write << "b#{Process.pid}\n"
  rescue SystemCallError, IOError
    STDERR.puts "Master seems to have exitted, exitting."
    return
  end

  server.run.join

ensure
  @worker_write.close
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.