class Puma::Cluster
def run
def run @status = :run output_header "cluster" # This is aligned with the output from Runner, see Runner#output_header log "* Workers: #{@options[:workers]}" if preload? # Threads explicitly marked as fork safe will be ignored. Used in Rails, # but may be used by anyone. Note that we need to explicit # Process::Waiter check here because there's a bug in Ruby 2.6 and below # where calling thread_variable_get on a Process::Waiter will segfault. # We can drop that clause once those versions of Ruby are no longer # supported. fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) } before = Thread.list.reject(&fork_safe) log "* Restarts: (\u2714) hot (\u2716) phased" log "* Preloading application" load_and_bind after = Thread.list.reject(&fork_safe) 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 "* Restarts: (\u2714) hot (\u2714) phased" unless @config.app_configured? error "No application configured, nothing to run" exit 1 end @launcher.binder.parse @options[:binds] end read, @wakeup = Puma::Util.pipe setup_signals # 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 # Separate pipe used by worker 0 to receive commands to # fork new worker processes. @fork_pipe, @fork_writer = Puma::Util.pipe log "Use Ctrl-C to stop" single_worker_warning redirect_io Plugins.fire_background @launcher.write_state start_control @master_read, @worker_write = read, @wakeup @config.run_hooks(:before_fork, nil, @log_writer) spawn_workers Signal.trap "SIGINT" do stop end begin booted = false in_phased_restart = false workers_not_booted = @options[:workers] while @status == :run begin if @phased_restart start_phased_restart @phased_restart = false in_phased_restart = true workers_not_booted = @options[:workers] end check_workers if read.wait_readable([0, @next_check - Time.now].max) req = read.read_nonblock(1) @next_check = Time.now if req == "!" next if !req || req == "!" result = read.gets pid = result.to_i if req == "b" || req == "f" pid, idx = result.split(':').map(&:to_i) w = @workers.find {|x| x.index == idx} w.pid = pid if w.pid.nil? end if w = @workers.find { |x| x.pid == pid } case req when "b" w.boot! log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}" @next_check = Time.now workers_not_booted -= 1 when "e" # external term, see worker method, Signal.trap "SIGTERM" w.term! when "t" w.term unless w.term? when "p" w.ping!(result.sub(/^\d+/,'').chomp) @events.fire(:ping!, w) if !booted && @workers.none? {|worker| worker.last_status.empty?} @events.fire_on_booted! debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug? booted = true end end else log "! Out-of-sync worker list, no #{pid} worker" end end if in_phased_restart && workers_not_booted.zero? @events.fire_on_booted! debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug? in_phased_restart = false end rescue Interrupt @status = :stop end end stop_workers unless @status == :halt ensure @check_pipe.close @suicide_pipe.close read.close @wakeup.close end end