1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Initial Seperation of CLI and Server Launcher work

This is a WIP. This was the minimum I could do to get all tests to pass without changing any tests. Eventually I think we want all high level process controls to come from launcher, I also think we want another separate object that gets passed to Runner/Single/Cluster that will maintain a relationship with the Launcher. We could use this as the object that also gets exposed to the app like the Embeddable class we talked about earlier. 

Moving forwards i'm planning to port out the CLI tests to only test that they are parsing the correct config and launching servers. I'll port all low level unit tests over to the launcher. Making this change we could either keep all the public methods in CLI that delegate to `@launcher`, I'm guessing not many people are using the internals of CLI and we can take them out. It's your call though.

Wanted to kick this over the fence and see if you had any strong reactions or feelings about this approach.
This commit is contained in:
schneems 2016-01-14 09:41:55 -06:00
parent 15e736de68
commit 182c836869
4 changed files with 354 additions and 179 deletions

View file

@ -12,3 +12,4 @@ require 'thread'
# Ruby Puma
require 'puma/const'
require 'puma/server'
require 'puma/launcher'

View file

@ -12,6 +12,7 @@ require 'puma/single'
require 'puma/cluster'
require 'puma/commonlogger'
require 'puma/launcher'
module Puma
class << self
@ -53,8 +54,61 @@ module Puma
@binder = Binder.new(@events)
@binder.import_from_env
begin
@parser.parse! @argv
@cli_options[:rackup] = @argv.shift if @argv.last
rescue UnsupportedOption
exit 1
end
@launcher = Puma::Launcher.new(@cli_options)
@launcher.events = self.events
@launcher.config = self.config
@launcher.binder = self.binder
@launcher.setup(@options)
end
## BACKWARDS COMPAT FOR TESTS
def delete_pidfile
@launcher.delete_pidfile
end
def log(string)
@launcher.log(string)
end
def stop
@launcher.stop
end
def restart
@launcher.restart
end
def write_state
@launcher.write_state
end
def write_pid
@launcher.write_pid
end
private
def parse_options
@launcher.send(:parse_options)
end
def set_rack_environment
@launcher.send(:set_rack_environment)
end
public
## BACKWARDS COMPAT FOR TESTS
# The Binder object containing the sockets bound to.
attr_reader :binder
@ -67,12 +121,6 @@ module Puma
# The Events object used to output information.
attr_reader :events
# Delegate +log+ to +@events+
#
def log(str)
@events.log str
end
# Delegate +error+ to +@events+
#
def error(str)
@ -84,21 +132,21 @@ module Puma
end
def clustered?
# remove eventually
@options[:workers] > 0
end
def prune_bundler?
@options[:prune_bundler] && clustered? && !@options[:preload_app]
end
def jruby?
# remove eventually
IS_JRUBY
end
def windows?
# remove eventually
RUBY_PLATFORM =~ /mswin32|ming32/
end
<<<<<<< HEAD
def env
@options[:environment] || @cli_options[:environment] || ENV['RACK_ENV'] || 'development'
end
@ -144,6 +192,8 @@ module Puma
log "- Goodbye!"
end
=======
>>>>>>> Initial Seperation of CLI and Server Launcher work
def jruby_daemon_start
require 'puma/jruby_restart'
JRubyRestart.daemon_start(@restart_dir, restart_args)
@ -184,57 +234,8 @@ module Puma
# for it to finish.
#
def run
begin
parse_options
rescue UnsupportedOption
exit 1
end
dir = @options[:directory]
Dir.chdir(dir) if dir
prune_bundler if prune_bundler?
set_rack_environment
if clustered?
@events.formatter = Events::PidFormatter.new
@options[:logger] = @events
@runner = Cluster.new(self)
else
@runner = Single.new(self)
end
setup_signals
set_process_title
@status = :run
@runner.run
case @status
when :halt
log "* Stopping immediately!"
when :run, :stop
graceful_stop
when :restart
log "* Restarting..."
@runner.before_restart
restart!
when :exit
# nothing
end
end
def stop
@status = :stop
@runner.stop
end
def restart
@status = :restart
@runner.restart
@runner = @launcher.runner
@launcher.run
end
def reload_worker_directory
@ -254,7 +255,7 @@ module Puma
end
def stats
@runner.stats
@launcher.stats
end
def halt
@ -263,12 +264,6 @@ module Puma
end
private
def title
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
buffer << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
buffer
end
def unsupported(str)
@events.error(str)
raise UnsupportedOption
@ -283,18 +278,6 @@ module Puma
end
end
def set_process_title
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
end
def find_config
if @cli_options[:config_file] == '-'
@cli_options[:config_file] = nil
else
@cli_options[:config_file] ||= %W(config/puma/#{env}.rb config/puma.rb).find { |f| File.exist?(f) }
end
end
# Build the OptionParser object to handle the available options.
#
@ -471,53 +454,6 @@ module Puma
end
end
def set_rack_environment
@options[:environment] = env
ENV['RACK_ENV'] = env
end
def setup_signals
begin
Signal.trap "SIGUSR2" do
restart
end
rescue Exception
log "*** SIGUSR2 not implemented, signal based restart unavailable!"
end
begin
Signal.trap "SIGUSR1" do
phased_restart
end
rescue Exception
log "*** SIGUSR1 not implemented, signal based restart unavailable!"
end
begin
Signal.trap "SIGTERM" do
stop
end
rescue Exception
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
end
begin
Signal.trap "SIGHUP" do
redirect_io
end
rescue Exception
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
end
if jruby?
Signal.trap("INT") do
@status = :exit
graceful_stop
exit
end
end
end
def close_binder_listeners
@binder.listeners.each do |l, io|
io.close
@ -526,57 +462,5 @@ module Puma
File.unlink("#{uri.host}#{uri.path}")
end
end
def parse_options
@parser.parse! @argv
@cli_options[:rackup] = @argv.shift if @argv.last
find_config
@config = Puma::Configuration.new @cli_options
# Advertise the Configuration
Puma.cli_config = @config
@config.load
@options = @config.options
if clustered? && (jruby? || windows?)
unsupported 'worker mode not supported on JRuby or Windows'
end
if @options[:daemon] && windows?
unsupported 'daemon mode not supported on Windows'
end
end
def prune_bundler
return unless defined?(Bundler)
puma = Bundler.rubygems.loaded_specs("puma")
dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
unless puma_lib_dir
log "! Unable to prune Bundler environment, continuing"
return
end
deps = puma.runtime_dependencies.map do |d|
spec = Bundler.rubygems.loaded_specs(d.name)
"#{d.name}:#{spec.version.to_s}"
end
log '* Pruning Bundler environment'
home = ENV['GEM_HOME']
Bundler.with_clean_env do
ENV['GEM_HOME'] = home
ENV['PUMA_BUNDLER_PRUNED'] = '1'
wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
Kernel.exec(*args)
end
end
end
end

288
lib/puma/launcher.rb Normal file
View file

@ -0,0 +1,288 @@
module Puma
class Launcher
def initialize(cli_options = {})
@cli_options = cli_options
@runner = nil
end
## THIS STUFF IS NEEDED FOR RUNNER
# Delegate +log+ to +@events+
#
def log(str)
@events.log str
end
def config
@config
end
def stats
@runner.stats
end
def halt
@status = :halt
@runner.halt
end
def binder
@binder
end
def events
@events
end
def write_state
write_pid
path = @options[:state]
return unless path
state = { 'pid' => Process.pid }
cfg = @config.dup
[
:logger,
:before_worker_shutdown, :before_worker_boot, :before_worker_fork,
:after_worker_boot,
:on_restart, :lowlevel_error_handler
].each { |k| cfg.options.delete(k) }
state['config'] = cfg
require 'yaml'
File.open(path, 'w') { |f| f.write state.to_yaml }
end
def delete_pidfile
path = @options[:pidfile]
File.unlink(path) if path && File.exist?(path)
end
# If configured, write the pid of the current process out
# to a file.
#
def write_pid
path = @options[:pidfile]
return unless path
File.open(path, 'w') { |f| f.puts Process.pid }
cur = Process.pid
at_exit do
delete_pidfile if cur == Process.pid
end
end
attr_accessor :options, :binder, :config, :events
## THIS STUFF IS NEEDED FOR RUNNER
def setup(options)
@options = options
parse_options
dir = @options[:directory]
Dir.chdir(dir) if dir
prune_bundler if prune_bundler?
set_rack_environment
if clustered?
@events.formatter = Events::PidFormatter.new
@options[:logger] = @events
@runner = Cluster.new(self)
else
@runner = Single.new(self)
end
@status = :run
end
attr_accessor :runner
def stop
@status = :stop
@runner.stop
end
def restart
@status = :restart
@runner.restart
end
def run
setup_signals
set_process_title
@runner.run
case @status
when :halt
log "* Stopping immediately!"
when :run, :stop
graceful_stop
when :restart
log "* Restarting..."
@runner.before_restart
restart!
when :exit
# nothing
end
end
def clustered?
@options[:workers] > 0
end
def jruby?
# remove eventually
IS_JRUBY
end
def windows?
# remove eventually
RUBY_PLATFORM =~ /mswin32|ming32/
end
def prune_bundler
return unless defined?(Bundler)
puma = Bundler.rubygems.loaded_specs("puma")
dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
unless puma_lib_dir
log "! Unable to prune Bundler environment, continuing"
return
end
deps = puma.runtime_dependencies.map do |d|
spec = Bundler.rubygems.loaded_specs(d.name)
"#{d.name}:#{spec.version.to_s}"
end
log '* Pruning Bundler environment'
home = ENV['GEM_HOME']
Bundler.with_clean_env do
ENV['GEM_HOME'] = home
ENV['PUMA_BUNDLER_PRUNED'] = '1'
wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
Kernel.exec(*args)
end
end
private
def unsupported(str)
@events.error(str)
raise UnsupportedOption
end
def parse_options
find_config
@config = Puma::Configuration.new @cli_options
# Advertise the Configuration
Puma.cli_config = @config
@config.load
@options = @config.options
if clustered? && (jruby? || windows?)
unsupported 'worker mode not supported on JRuby or Windows'
end
if @options[:daemon] && windows?
unsupported 'daemon mode not supported on Windows'
end
end
def find_config
if @cli_options[:config_file] == '-'
@cli_options[:config_file] = nil
else
@cli_options[:config_file] ||= %W(config/puma/#{env}.rb config/puma.rb).find { |f| File.exist?(f) }
end
end
def graceful_stop
@runner.stop_blocked
log "=== puma shutdown: #{Time.now} ==="
log "- Goodbye!"
end
def set_process_title
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
end
def title
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
buffer << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
buffer
end
def set_rack_environment
@options[:environment] = env
ENV['RACK_ENV'] = env
end
def env
@options[:environment] ||
@cli_options[:environment] ||
ENV['RACK_ENV'] ||
'development'
end
def prune_bundler?
@options[:prune_bundler] && clustered? && !@options[:preload_app]
end
def setup_signals
begin
Signal.trap "SIGUSR2" do
restart
end
rescue Exception
log "*** SIGUSR2 not implemented, signal based restart unavailable!"
end
begin
Signal.trap "SIGUSR1" do
phased_restart
end
rescue Exception
log "*** SIGUSR1 not implemented, signal based restart unavailable!"
end
begin
Signal.trap "SIGTERM" do
stop
end
rescue Exception
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
end
begin
Signal.trap "SIGHUP" do
redirect_io
end
rescue Exception
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
end
if jruby?
Signal.trap("INT") do
@status = :exit
graceful_stop
exit
end
end
end
end
end

View file

@ -82,6 +82,7 @@ class TestCLI < Test::Unit::TestCase
cli.send(:parse_options)
t = Thread.new { cli.run }
t.abort_on_exception = true
wait_booted
@ -105,6 +106,7 @@ class TestCLI < Test::Unit::TestCase
cli.send(:parse_options)
t = Thread.new { cli.run }
t.abort_on_exception = true
wait_booted