1
0
Fork 0
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:
Evan Phoenix 2011-12-05 15:58:23 -08:00
parent 8184b0804e
commit d8026e87f4
6 changed files with 199 additions and 4 deletions

View file

@ -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
View 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

View file

@ -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

View file

@ -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
View 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

View file

@ -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