lib/puma/control_cli.rb in piesync-puma-3.12.6.1 vs lib/puma/control_cli.rb in piesync-puma-5.4.0.1

- old
+ new

@@ -9,29 +9,55 @@ require 'socket' module Puma class ControlCLI - COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats} + # values must be string or nil + # value of `nil` means command cannot be processed via signal + # @version 5.0.3 + CMD_PATH_SIG_MAP = { + 'gc' => nil, + 'gc-stats' => nil, + 'halt' => 'SIGQUIT', + 'phased-restart' => 'SIGUSR1', + 'refork' => 'SIGURG', + 'reload-worker-directory' => nil, + 'restart' => 'SIGUSR2', + 'start' => nil, + 'stats' => nil, + 'status' => '', + 'stop' => 'SIGTERM', + 'thread-backtraces' => nil + }.freeze + # @deprecated 6.0.0 + COMMANDS = CMD_PATH_SIG_MAP.keys.freeze + + # commands that cannot be used in a request + NO_REQ_COMMANDS = %w{refork}.freeze + + # @version 5.0.0 + PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze + def initialize(argv, stdout=STDOUT, stderr=STDERR) @state = nil @quiet = false @pidfile = nil @pid = nil @control_url = nil @control_auth_token = nil @config_file = nil @command = nil + @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV'] @argv = argv.dup @stdout = stdout @stderr = stderr @cli_options = {} opts = OptionParser.new do |o| - o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})" + o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})" o.on "-S", "--state PATH", "Where the state file to use is" do |arg| @state = arg end @@ -57,29 +83,47 @@ o.on "-F", "--config-file PATH", "Puma config script" do |arg| @config_file = arg end + o.on "-e", "--environment ENVIRONMENT", + "The environment to run the Rack app on (default development)" do |arg| + @environment = arg + end + o.on_tail("-H", "--help", "Show this message") do @stdout.puts o exit end o.on_tail("-V", "--version", "Show version") do - puts Const::PUMA_VERSION + @stdout.puts Const::PUMA_VERSION exit end end opts.order!(argv) { |a| opts.terminate a } opts.parse! @command = argv.shift + # check presence of command + unless @command + raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}" + end + + unless CMD_PATH_SIG_MAP.key? @command + raise "Invalid command: #{@command}" + end + unless @config_file == '-' - if @config_file.nil? and File.exist?('config/puma.rb') - @config_file = 'config/puma.rb' + environment = @environment || 'development' + + if @config_file.nil? + @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f| + File.exist?(f) + end end if @config_file config = Puma::Configuration.new({ config_files: [@config_file] }, {}) config.load @@ -87,23 +131,12 @@ @control_url ||= config.options[:control_url] @control_auth_token ||= config.options[:control_auth_token] @pidfile ||= config.options[:pidfile] end end - - # check present of command - unless @command - raise "Available commands: #{COMMANDS.join(", ")}" - end - - unless COMMANDS.include? @command - raise "Invalid command: #{@command}" - end - rescue => e @stdout.puts e.message - @stdout.puts e.backtrace exit 1 end def message(msg) @stdout.puts msg unless @quiet @@ -121,126 +154,134 @@ @control_url = sf.control_url @control_auth_token = sf.control_auth_token @pid = sf.pid elsif @pidfile # get pid from pid_file - @pid = File.open(@pidfile).gets.to_i + @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i end end def send_request uri = URI.parse @control_url # create server object by scheme - server = case uri.scheme - when "tcp" - TCPSocket.new uri.host, uri.port - when "unix" - UNIXSocket.new "#{uri.host}#{uri.path}" - else - raise "Invalid scheme: #{uri.scheme}" - end + server = + case uri.scheme + when 'ssl' + require 'openssl' + OpenSSL::SSL::SSLSocket.new( + TCPSocket.new(uri.host, uri.port), + OpenSSL::SSL::SSLContext.new) + .tap { |ssl| ssl.sync_close = true } # default is false + .tap(&:connect) + when 'tcp' + TCPSocket.new uri.host, uri.port + when 'unix' + # check for abstract UNIXSocket + UNIXSocket.new(@control_url.start_with?('unix://@') ? + "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}") + else + raise "Invalid scheme: #{uri.scheme}" + end - if @command == "status" - message "Puma is started" + if @command == 'status' + message 'Puma is started' + elsif NO_REQ_COMMANDS.include? @command + raise "Invalid request command: #{@command}" else url = "/#{@command}" if @control_auth_token url = url + "?token=#{@control_auth_token}" end - server << "GET #{url} HTTP/1.0\r\n\r\n" + server.syswrite "GET #{url} HTTP/1.0\r\n\r\n" unless data = server.read - raise "Server closed connection before responding" + raise 'Server closed connection before responding' end response = data.split("\r\n") if response.empty? raise "Server sent empty response" end - (@http,@code,@message) = response.first.split(" ",3) + @http, @code, @message = response.first.split(' ',3) - if @code == "403" - raise "Unauthorized access to server (wrong auth token)" - elsif @code == "404" + if @code == '403' + raise 'Unauthorized access to server (wrong auth token)' + elsif @code == '404' raise "Command error: #{response.last}" - elsif @code != "200" + elsif @code != '200' raise "Bad response from server: #{@code}" end message "Command #{@command} sent success" - message response.last if @command == "stats" || @command == "gc-stats" + message response.last if PRINTABLE_COMMANDS.include?(@command) end ensure - server.close if server && !server.closed? + if server + if uri.scheme == 'ssl' + server.sysclose + else + server.close unless server.closed? + end + end end def send_signal unless @pid - raise "Neither pid nor control url available" + raise 'Neither pid nor control url available' end begin + sig = CMD_PATH_SIG_MAP[@command] - case @command - when "restart" - Process.kill "SIGUSR2", @pid - - when "halt" - Process.kill "QUIT", @pid - - when "stop" - Process.kill "SIGTERM", @pid - - when "stats" - puts "Stats not available via pid only" + if sig.nil? + @stdout.puts "'#{@command}' not available via pid only" + @stdout.flush unless @stdout.sync return - - when "reload-worker-directory" - puts "reload-worker-directory not available via pid only" + elsif sig.start_with? 'SIG' + Process.kill sig, @pid + elsif @command == 'status' + begin + Process.kill 0, @pid + @stdout.puts 'Puma is started' + @stdout.flush unless @stdout.sync + rescue Errno::ESRCH + raise 'Puma is not running' + end return - - when "phased-restart" - Process.kill "SIGUSR1", @pid - - else - return end - rescue SystemCallError - if @command == "restart" + if @command == 'restart' start else raise "No pid '#{@pid}' found" end end message "Command #{@command} sent success" end def run - return start if @command == "start" - + return start if @command == 'start' prepare_configuration - if Puma.windows? + if Puma.windows? || @control_url send_request else - @control_url ? send_request : send_signal + send_signal end rescue => e message e.message - message e.backtrace exit 1 end - private + private def start require 'puma/cli' run_args = [] @@ -248,9 +289,10 @@ run_args += ["-q"] if @quiet run_args += ["--pidfile", @pidfile] if @pidfile run_args += ["--control-url", @control_url] if @control_url run_args += ["--control-token", @control_auth_token] if @control_auth_token run_args += ["-C", @config_file] if @config_file + run_args += ["-e", @environment] if @environment events = Puma::Events.new @stdout, @stderr # replace $0 because puma use it to generate restart command puma_cmd = $0.gsub(/pumactl$/, 'puma')