diff --git a/lib/puma.rb b/lib/puma.rb index 49792e14..678415c1 100644 --- a/lib/puma.rb +++ b/lib/puma.rb @@ -12,3 +12,4 @@ require 'thread' # Ruby Puma require 'puma/const' require 'puma/server' +require 'puma/launcher' diff --git a/lib/puma/cli.rb b/lib/puma/cli.rb index ce8b25a4..8e5b7bda 100644 --- a/lib/puma/cli.rb +++ b/lib/puma/cli.rb @@ -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 diff --git a/lib/puma/launcher.rb b/lib/puma/launcher.rb new file mode 100644 index 00000000..10584104 --- /dev/null +++ b/lib/puma/launcher.rb @@ -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 \ No newline at end of file diff --git a/test/test_cli.rb b/test/test_cli.rb index c9e4e5f3..47cbbfe7 100644 --- a/test/test_cli.rb +++ b/test/test_cli.rb @@ -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