mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Add ability to restart by reexecing and pumactl to use it
This allows all existing requests to finish, but does not keep the same socket alive across the exec, so this is not a graceful as it could be.
This commit is contained in:
parent
8184b0804e
commit
d8026e87f4
6 changed files with 199 additions and 4 deletions
1
bin/puma
1
bin/puma
|
@ -10,6 +10,7 @@ cli = Puma::CLI.new ARGV
|
|||
begin
|
||||
cli.run
|
||||
rescue => e
|
||||
raise e if $DEBUG
|
||||
STDERR.puts e.message
|
||||
exit 1
|
||||
end
|
||||
|
|
12
bin/pumactl
Normal file
12
bin/pumactl
Normal file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'puma/control_cli'
|
||||
|
||||
cli = Puma::ControlCLI.new ARGV
|
||||
|
||||
begin
|
||||
cli.run
|
||||
rescue => e
|
||||
STDERR.puts e.message
|
||||
exit 1
|
||||
end
|
|
@ -1,8 +1,9 @@
|
|||
module Puma
|
||||
module App
|
||||
class Status
|
||||
def initialize(server)
|
||||
def initialize(server, cli)
|
||||
@server = server
|
||||
@cli = cli
|
||||
end
|
||||
|
||||
def call(env)
|
||||
|
@ -15,6 +16,14 @@ module Puma
|
|||
@server.halt
|
||||
return [200, {}, ['{ "status": "ok" }']]
|
||||
|
||||
when "/restart"
|
||||
if @cli and @cli.restart_on_stop!
|
||||
@server.stop
|
||||
return [200, {}, ['{ "status": "ok" }']]
|
||||
else
|
||||
return [200, {}, ['{ "status": "not configured" }']]
|
||||
end
|
||||
|
||||
when "/stats"
|
||||
b = @server.backlog
|
||||
r = @server.running
|
||||
|
|
|
@ -31,7 +31,58 @@ module Puma
|
|||
@server = nil
|
||||
@status = nil
|
||||
|
||||
@restart = false
|
||||
@temp_status_path = nil
|
||||
|
||||
setup_options
|
||||
|
||||
generate_restart_data
|
||||
end
|
||||
|
||||
def restart_on_stop!
|
||||
if @restart_argv
|
||||
@restart = true
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def generate_restart_data
|
||||
# Use the same trick as unicorn, namely favor PWD because
|
||||
# it will contain an unresolved symlink, useful for when
|
||||
# the pwd is /data/releases/current.
|
||||
if dir = ENV['PWD']
|
||||
s_env = File.stat(dir)
|
||||
s_pwd = File.stat(Dir.pwd)
|
||||
|
||||
if s_env.ino == s_pwd.ino and s_env.dev == s_pwd.dev
|
||||
@restart_dir = dir
|
||||
end
|
||||
end
|
||||
|
||||
@restart_dir ||= Dir.pwd
|
||||
|
||||
if defined? Rubinius::OS_ARGV
|
||||
@restart_argv = Rubinius::OS_ARGV
|
||||
else
|
||||
require 'rubygems'
|
||||
|
||||
# if $0 is a file in the current directory, then restart
|
||||
# it the same, otherwise add -S on there because it was
|
||||
# picked up in PATH.
|
||||
#
|
||||
if File.exists?($0)
|
||||
@restart_argv = [Gem.ruby, $0] + ARGV
|
||||
else
|
||||
@restart_argv = [Gem.ruby, "-S", $0] + ARGV
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def restart!
|
||||
Dir.chdir @restart_dir
|
||||
Kernel.exec(*@restart_argv)
|
||||
end
|
||||
|
||||
# Write +str+ to +@stdout+
|
||||
|
@ -87,7 +138,7 @@ module Puma
|
|||
end
|
||||
|
||||
o.on "--status [URL]", "The bind url to use for the status server" do |arg|
|
||||
if arg
|
||||
if arg and arg != "@"
|
||||
@options[:status_address] = arg
|
||||
elsif IS_JRUBY
|
||||
raise NotImplementedError, "No default url available on JRuby"
|
||||
|
@ -97,6 +148,8 @@ module Puma
|
|||
t = (Time.now.to_f * 1000).to_i
|
||||
path = "#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
|
||||
|
||||
@temp_status_path = path
|
||||
|
||||
@options[:status_address] = "unix://#{path}"
|
||||
end
|
||||
end
|
||||
|
@ -171,6 +224,7 @@ module Puma
|
|||
|
||||
load_rackup
|
||||
write_pid
|
||||
write_state
|
||||
|
||||
unless @options[:quiet]
|
||||
@app = Rack::CommonLogger.new(@app, STDOUT)
|
||||
|
@ -219,7 +273,7 @@ module Puma
|
|||
|
||||
uri = URI.parse str
|
||||
|
||||
app = Puma::App::Status.new server
|
||||
app = Puma::App::Status.new server, self
|
||||
status = Puma::Server.new app, @events
|
||||
status.min_threads = 0
|
||||
status.max_threads = 1
|
||||
|
@ -250,6 +304,13 @@ module Puma
|
|||
server.stop(true)
|
||||
log " - Goodbye!"
|
||||
end
|
||||
|
||||
File.unlink @temp_status_path if @temp_status_path
|
||||
|
||||
if @restart
|
||||
log "* Restarting..."
|
||||
restart!
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
|
|
112
lib/puma/control_cli.rb
Normal file
112
lib/puma/control_cli.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
require 'optparse'
|
||||
|
||||
require 'puma/const'
|
||||
require 'yaml'
|
||||
require 'uri'
|
||||
|
||||
require 'socket'
|
||||
|
||||
module Puma
|
||||
class ControlCLI
|
||||
|
||||
def initialize(argv)
|
||||
@argv = argv
|
||||
end
|
||||
|
||||
def setup_options
|
||||
@parser = OptionParser.new do |o|
|
||||
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
||||
@path = arg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def connect
|
||||
if str = @state['status_address']
|
||||
uri = URI.parse str
|
||||
case uri.scheme
|
||||
when "tcp"
|
||||
return TCPSocket.new uri.host, uri.port
|
||||
when "unix"
|
||||
path = "#{uri.host}#{uri.path}"
|
||||
return UNIXSocket.new path
|
||||
else
|
||||
raise "Invalid URI: #{str}"
|
||||
end
|
||||
end
|
||||
|
||||
raise "No status address configured"
|
||||
end
|
||||
|
||||
def run
|
||||
setup_options
|
||||
|
||||
@parser.parse! @argv
|
||||
|
||||
@state = YAML.load_file(@path)
|
||||
|
||||
cmd = @argv.shift
|
||||
|
||||
meth = "command_#{cmd}"
|
||||
|
||||
if respond_to?(meth)
|
||||
__send__(meth)
|
||||
else
|
||||
raise "Unknown command: #{cmd}"
|
||||
end
|
||||
end
|
||||
|
||||
def command_pid
|
||||
puts "#{@state['pid']}"
|
||||
end
|
||||
|
||||
def command_stop
|
||||
sock = connect
|
||||
sock << "GET /stop HTTP/1.0\r\n\r\n"
|
||||
rep = sock.read
|
||||
|
||||
body = rep.split("\r\n").last
|
||||
if body != '{ "status": "ok" }'
|
||||
raise "Invalid response: '#{body}'"
|
||||
else
|
||||
puts "Requested stop from server"
|
||||
end
|
||||
end
|
||||
|
||||
def command_halt
|
||||
sock = connect
|
||||
s << "GET /halt HTTP/1.0\r\n\r\n"
|
||||
rep = s.read
|
||||
|
||||
body = rep.split("\r\n").last
|
||||
if body != '{ "status": "ok" }'
|
||||
raise "Invalid response: '#{body}'"
|
||||
else
|
||||
puts "Requested halt from server"
|
||||
end
|
||||
end
|
||||
|
||||
def command_restart
|
||||
sock = connect
|
||||
sock << "GET /restart HTTP/1.0\r\n\r\n"
|
||||
rep = sock.read
|
||||
|
||||
body = rep.split("\r\n").last
|
||||
if body != '{ "status": "ok" }'
|
||||
raise "Invalid response: '#{body}'"
|
||||
else
|
||||
puts "Requested restart from server"
|
||||
end
|
||||
end
|
||||
|
||||
def command_stats
|
||||
sock = connect
|
||||
s << "GET /stats HTTP/1.0\r\n\r\n"
|
||||
rep = s.read
|
||||
|
||||
body = rep.split("\r\n").last
|
||||
|
||||
puts body
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,7 +23,7 @@ class TestAppStatus < Test::Unit::TestCase
|
|||
|
||||
def setup
|
||||
@server = FakeServer.new
|
||||
@app = Puma::App::Status.new(@server)
|
||||
@app = Puma::App::Status.new(@server, @server)
|
||||
end
|
||||
|
||||
def test_unsupported
|
||||
|
|
Loading…
Reference in a new issue