Class: Puma::Cluster

### Home Classes Methods

### In Files

- lib/puma/cluster.rb

### Parent

Runner

### Namespace

- CLASS Puma::Cluster::Worker

### Methods

- ::new
- #all_workers_booted?
- #check_workers
- #halt
- #next_worker_index
- #phased_restart
- #preload?
- #redirect_io
- #reload_worker_directory
- #restart
- #run
- #spawn_workers
- #start_phased_restart
- #stats
- #stop
- #stop_blocked
- #stop_workers
- #wakeup!
- #worker

### Files

- Manifest.txt

### Class/Module Index

Quicksearch

- Puma
- Puma::App
- Puma::App::Status
- Puma::Binder
- Puma::CLI
- Puma::Client
- Puma::Cluster
- Puma::Cluster::Worker
- Puma::CommonLogger
- Puma::ConfigDefault
- Puma::Configuration
- Puma::Configuration::ConfigMiddleware
- Puma::ConnectionError
- Puma::Const
- Puma::ControlCLI
- Puma::DSL
- Puma::Delegation
- Puma::Events
- Puma::Events::DefaultFormatter
- Puma::Events::PidFormatter
- Puma::IOBuffer
- Puma::JRubyRestart
- Puma::JavaIOBuffer
- Puma::MiniSSL
- Puma::MiniSSL::Context
- Puma::MiniSSL::SSLError
- Puma::MiniSSL::Server
- Puma::MiniSSL::Socket
- Puma::NullIO
- Puma::Rack
- Puma::Rack::Builder
- Puma::Rack::Options
- Puma::Rack::URLMap
- Puma::Reactor
- Puma::Runner
- Puma::Server
- Puma::Single
- Puma::TCPLogger
- Puma::ThreadPool
- Puma::ThreadPool::AutoTrim
- Puma::ThreadPool::Reaper
- Puma::UnsupportedOption
- Puma::Util
- Puma::Util::HeaderHash
- OpenSSL
- OpenSSL::SSL
- OpenSSL::SSL::SSLServer
- Rack
- Rack::Handler
- Rack::Handler::Puma
- IO
- IO::WaitReadable
- Object
- Process
- String

No matching classes.

# 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 = []
      @next_check = nil

      @phased_state = :idle
      @phased_restart = false
    end

### Public Instance Methods

all_workers_booted?() click to toggle source

    # File lib/puma/cluster.rb, line 127
    def all_workers_booted?
      @workers.count { |w| !w.booted? } == 0
    end

check_workers(force=false) click to toggle source

    # File lib/puma/cluster.rb, line 131
    def check_workers(force=false)
      return if !force && @next_check && @next_check >= Time.now

      @next_check = Time.now + 5

      any = false

      @workers.each do |w|
        if w.ping_timeout?(@options[:worker_timeout])
          log "! Terminating timed out worker: #{w.pid}"
          w.kill
          any = true
        end
      end

      # If we killed any timed out workers, try to catch them
      # during this loop by giving the kernel time to kill them.
      sleep 1 if any

      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 273
    def halt
      @status = :halt
      wakeup!
    end

next_worker_index() click to toggle source

    # File lib/puma/cluster.rb, line 120
    def next_worker_index
      all_positions =  0...@options[:workers]
      occupied_positions = @workers.map { |w| w.index }
      available_positions = all_positions.to_a - occupied_positions
      available_positions.first
    end

phased_restart() click to toggle source

    # File lib/puma/cluster.rb, line 252
    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 289
    def preload?
      @options[:preload_app]
    end

redirect_io() click to toggle source

    # File lib/puma/cluster.rb, line 39
    def redirect_io
      super

      @workers.each { |x| x.hup }
    end

reload_worker_directory() click to toggle source

    # File lib/puma/cluster.rb, line 278
    def reload_worker_directory
      if dir = @options[:worker_directory]
        log "+ Changing to #{dir}"
        Dir.chdir dir
      end
    end

restart() click to toggle source

    # File lib/puma/cluster.rb, line 247
    def restart
      @restart = true
      stop
    end

run() click to toggle source

    # File lib/puma/cluster.rb, line 293
    def run
      @status = :run

      output_header "cluster"

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

      before = Thread.list

      if preload?
        log "* Preloading application"
        load_and_bind

        after = Thread.list

        if after.size > before.size
          threads = (after - before)
          if threads.first.respond_to? :backtrace
            log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot:"
            threads.each do |t|
              log "! #{t.inspect} - #{t.backtrace ? t.backtrace.first : ''}"
            end
          else
            log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot"
          end
        end
      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

      Signal.trap "TTIN" do
        @options[:workers] += 1
        wakeup!
      end

      Signal.trap "TTOU" do
        @options[:workers] -= 1 if @options[:workers] >= 2
        @workers.last.term
        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

      hooks = @options[:before_fork]
      hooks.each { |h| h.call }

      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)

            force_check = false

            if res
              req = read.read_nonblock(1)

              next if !req || req == "!"

              pid = read.gets.to_i

              if w = @workers.find { |x| x.pid == pid }
                case req
                when "b"
                  w.boot!
                  log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
                  force_check = true
                when "p"
                  w.ping!
                end
              else
                log "! Out-of-sync worker list, no #{pid} worker"
              end
            end

            if @phased_restart
              start_phased_restart
              @phased_restart = false
            end

            check_workers force_check

          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 100
    def spawn_workers
      diff = @options[:workers] - @workers.size

      master = Process.pid

      diff.times do
        idx = next_worker_index
        @options[:before_worker_fork].each { |h| h.call(idx) }

        pid = fork { worker(idx, master) }
        @cli.debug "Spawned worker: #{pid}"
        @workers << Worker.new(idx, pid, @phase, @options)
        @options[:after_worker_boot].each { |h| h.call }
      end

      if diff > 0
        @phased_state = :idle
      end
    end

start_phased_restart() click to toggle source

    # File lib/puma/cluster.rb, line 27
    def start_phased_restart
      @phase += 1
      log "- Starting phased worker restart, phase: #{@phase}"

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

stats() click to toggle source

    # File lib/puma/cluster.rb, line 285
    def stats
      %{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{@workers.count{|w| w.booted?}} }!
    end

stop() click to toggle source

    # File lib/puma/cluster.rb, line 261
    def stop
      @status = :stop
      wakeup!
    end

stop_blocked() click to toggle source

    # File lib/puma/cluster.rb, line 266
    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 16
    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 178
    def wakeup!
      return unless @wakeup

      begin
        @wakeup.write "!" unless @wakeup.closed?
      rescue SystemCallError, IOError
      end
    end

worker(index, master) click to toggle source

    # File lib/puma/cluster.rb, line 187
    def worker(index, master)
      title = "puma: cluster worker #{index}: #{master}"
      title << " [#{@options[:tag]}]" if @options[:tag]
      $0 = title

      Signal.trap "SIGINT", "IGNORE"

      @workers = []
      @master_read.close
      @suicide_pipe.close

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

      # If we're not running under a Bundler context, then
      # report the info about the context we will be using
      if !ENV['BUNDLE_GEMFILE'] and File.exist?("Gemfile")
        log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
      end

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

      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

      Thread.new(@worker_write) do |io|
        payload = "p#{Process.pid}\n"

        while true
          sleep 5
          io << payload
        end
      end

      server.run.join

      # Invoke any worker shutdown hooks so they can prevent the worker
      # exiting until any background operations are completed
      hooks = @options[:before_worker_shutdown]
      hooks.each { |h| h.call(index) }
    ensure
      @worker_write.close
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.