diff --git a/examples/config.rb b/examples/config.rb new file mode 100644 index 00000000..ec18b8e2 --- /dev/null +++ b/examples/config.rb @@ -0,0 +1,5 @@ +state_path "puma.state" +activate_control_app + +rackup "test/lobster.ru" +threads 3, 10 diff --git a/lib/puma/cli.rb b/lib/puma/cli.rb index 20279c5e..8c903cb3 100644 --- a/lib/puma/cli.rb +++ b/lib/puma/cli.rb @@ -3,6 +3,7 @@ require 'uri' require 'puma/server' require 'puma/const' +require 'puma/config' require 'rack/commonlogger' @@ -10,9 +11,7 @@ module Puma # Handles invoke a Puma::Server in a command line style. # class CLI - DefaultTCPHost = "0.0.0.0" - DefaultTCPPort = 9292 - + DefaultRackup = "config.ru" IS_JRUBY = defined?(JRUBY_VERSION) # Create a new CLI object using +argv+ as the command line @@ -109,14 +108,17 @@ module Puma @options = { :min_threads => 0, :max_threads => 16, - :quiet => false + :quiet => false, + :binds => [] } - @binds = [] - @parser = OptionParser.new do |o| o.on "-b", "--bind URI", "URI to bind to (tcp:// and unix:// only)" do |arg| - @binds << arg + @options[:binds] << arg + end + + o.on "-C", "--config PATH", "Load PATH as a config file" do |arg| + @options[:config_file] = arg end o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg| @@ -127,6 +129,19 @@ module Puma @options[:quiet] = true end + o.on "-S", "--state PATH", "Where to store the state details" do |arg| + @options[:state] = arg + end + + o.on "--control URL", "The bind url to use for the control server", + "Use 'auto' to use temp unix server" do |arg| + if arg + @options[:control_url] = arg + elsif IS_JRUBY + raise NotImplementedError, "No default url available on JRuby" + end + end + o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg| min, max = arg.split(":") if max @@ -138,27 +153,6 @@ module Puma end end - o.on "-S", "--state PATH", "Where to store the state details" do |arg| - @options[:state] = arg - end - - o.on "--status [URL]", "The bind url to use for the status server" do |arg| - if arg and arg != "@" - @options[:status_address] = arg - elsif IS_JRUBY - raise NotImplementedError, "No default url available on JRuby" - else - require 'tmpdir' - - 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 - end @parser.banner = "puma " @@ -173,7 +167,7 @@ module Puma # the rackup file, and set @app. # def load_rackup - @app, options = Rack::Builder.parse_file @rackup + @app, options = Rack::Builder.parse_file @options[:rackup] @options.merge! options options.each do |key,val| @@ -200,9 +194,7 @@ module Puma if path = @options[:state] state = { "pid" => Process.pid } - if url = @options[:status_address] - state["status_address"] = url - end + state["config"] = @config File.open(path, "w") do |f| f.write state.to_yaml @@ -213,6 +205,18 @@ module Puma # :nodoc: def parse_options @parser.parse! @argv + + @config = Puma::Configuration.new @options + @config.load + + unless @options[:rackup] + @options[:rackup] = @argv.shift || DefaultRackup + end + + if @options[:control_url] == "auto" + path = @temp_status_path = Configuration.temp_path + @options[:control_url] = "unix://#{path}" + end end # Parse the options, load the rackup, start the server and wait @@ -221,10 +225,10 @@ module Puma def run parse_options - @rackup = @argv.shift || "config.ru" + rackup = @options[:rackup] - unless File.exists?(@rackup) - raise "Missing rackup file '#{@rackup}'" + unless File.exists?(rackup) + raise "Missing rackup file '#{rackup}'" end load_rackup @@ -235,11 +239,6 @@ module Puma @app = Rack::CommonLogger.new(@app, STDOUT) end - if @binds.empty? - @options[:Host] ||= DefaultTCPHost - @options[:Port] ||= DefaultTCPPort - end - min_t = @options[:min_threads] max_t = @options[:max_threads] @@ -250,12 +249,7 @@ module Puma log "Puma #{Puma::Const::PUMA_VERSION} starting..." log "* Min threads: #{min_t}, max threads: #{max_t}" - if @options[:Host] - log "* Listening on tcp://#{@options[:Host]}:#{@options[:Port]}" - server.add_tcp_listener @options[:Host], @options[:Port] - end - - @binds.each do |str| + @options[:binds].each do |str| uri = URI.parse str case uri.scheme when "tcp" @@ -273,7 +267,7 @@ module Puma @server = server - if str = @options[:status_address] + if str = @options[:control_url] require 'puma/app/status' uri = URI.parse str diff --git a/lib/puma/config.rb b/lib/puma/config.rb new file mode 100644 index 00000000..9381f7eb --- /dev/null +++ b/lib/puma/config.rb @@ -0,0 +1,98 @@ +module Puma + class Configuration + DefaultTCPHost = "0.0.0.0" + DefaultTCPPort = 9292 + + def initialize(options) + @options = options + @options[:binds] ||= [] + end + + attr_reader :options + + def load + if path = @options[:config_file] + instance_eval File.read(path), path, 1 + end + + # Rakeup default option support + if host = @options[:Host] + port = @options[:Port] || DefaultTCPPort + + @options[:binds] << "tcp://#{host}:#{port}" + end + + if @options[:binds].empty? + @options[:binds] << "tcp://#{DefaultTCPHost}:#{DefaultTCPPort}" + end + end + + def self.temp_path + require 'tmpdir' + + t = (Time.now.to_f * 1000).to_i + "#{Dir.tmpdir}/puma-status-#{t}-#{$$}" + end + + # Use +obj+ or +block+ as the Rack app. This allows a config file to + # be the app itself. + # + def app(obj=nil, &block) + obj ||= block + + raise "Provide either a #call'able or a block" unless obj + + @options[:app] = obj + end + + # Start the Puma control rack app on +url+. This app can be communicated + # with to control the main server. + # + def activate_control_app(url="auto") + @options[:control_url] = url + end + + # Bind the server to +url+. tcp:// and unix:// are the only accepted + # protocols. + # + def bind(url) + @options[:binds] << url + end + + # Store the pid of the server in the file at +path+. + def pidfile(path) + @options[:pidfile] = path + end + + # Disable request logging. + # + def quiet + @options[:quiet] = true + end + + # Load +path+ as a rackup file. + # + def rackup(path) + @options[:rackup] = path.to_s + end + + # Configure +min+ to be the minimum number of threads to use to answer + # requests and +max+ the maximum. + # + def threads(min, max) + if min > max + raise "The minimum number of threads must be less than the max" + end + + @options[:min_threads] = min + @options[:max_threads] = max + end + + # Use +path+ as the file to store the server info state. This is + # used by pumactl to query and control the server. + # + def state_path(path) + @options[:state] = path.to_s + end + end +end diff --git a/lib/puma/const.rb b/lib/puma/const.rb index 801dba33..97c6344d 100644 --- a/lib/puma/const.rb +++ b/lib/puma/const.rb @@ -71,7 +71,7 @@ module Puma PATH_INFO = 'PATH_INFO'.freeze - PUMA_VERSION = VERSION = "0.9.0".freeze + PUMA_VERSION = VERSION = "0.9.1".freeze PUMA_TMP_BASE = "puma".freeze diff --git a/lib/puma/control_cli.rb b/lib/puma/control_cli.rb index a0b9ed9f..4e5b20b5 100644 --- a/lib/puma/control_cli.rb +++ b/lib/puma/control_cli.rb @@ -1,6 +1,8 @@ require 'optparse' require 'puma/const' +require 'puma/config' + require 'yaml' require 'uri' @@ -22,7 +24,7 @@ module Puma end def connect - if str = @state['status_address'] + if str = @config.options[:control_url] uri = URI.parse str case uri.scheme when "tcp" @@ -44,6 +46,7 @@ module Puma @parser.parse! @argv @state = YAML.load_file(@path) + @config = @state['config'] cmd = @argv.shift @@ -101,8 +104,8 @@ module Puma def command_stats sock = connect - s << "GET /stats HTTP/1.0\r\n\r\n" - rep = s.read + sock << "GET /stats HTTP/1.0\r\n\r\n" + rep = sock.read body = rep.split("\r\n").last diff --git a/lib/puma/thread_pool.rb b/lib/puma/thread_pool.rb index 0641fce3..dd56285d 100644 --- a/lib/puma/thread_pool.rb +++ b/lib/puma/thread_pool.rb @@ -137,7 +137,9 @@ module Puma @todo << Stop end - @workers.each { |w| w.join } + # Use this instead of #each so that we don't stop in the middle + # of each and see a mutated object mid #each + @workers.first.join until @workers.empty? @spawned = 0 @workers = [] diff --git a/test/test_cli.rb b/test/test_cli.rb index 4f093102..c8059844 100644 --- a/test/test_cli.rb +++ b/test/test_cli.rb @@ -28,13 +28,13 @@ class TestCLI < Test::Unit::TestCase end unless defined? JRUBY_VERSION - def test_status + def test_control url = "unix://#{@tmp_path}" sin = StringIO.new sout = StringIO.new - cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--status", url, "test/lobster.ru"], sin, sout + cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--control", url, "test/lobster.ru"], sin, sout cli.parse_options t = Thread.new { cli.run } @@ -51,13 +51,13 @@ class TestCLI < Test::Unit::TestCase t.join end - def test_status_stop + def test_control_stop url = "unix://#{@tmp_path}" sin = StringIO.new sout = StringIO.new - cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--status", url, "test/lobster.ru"], sin, sout + cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--control", url, "test/lobster.ru"], sin, sout cli.parse_options t = Thread.new { cli.run } @@ -73,9 +73,9 @@ class TestCLI < Test::Unit::TestCase t.join end - def test_tmp_status + def test_tmp_control url = "tcp://127.0.0.1:8232" - cli = Puma::CLI.new ["--state", @tmp_path, "--status"] + cli = Puma::CLI.new ["--state", @tmp_path, "--control", "auto"] cli.parse_options cli.write_state @@ -83,7 +83,7 @@ class TestCLI < Test::Unit::TestCase assert_equal Process.pid, data["pid"] - url = data["status_address"] + url = data["config"].options[:control_url] m = %r!unix://(.*)!.match(url) @@ -93,13 +93,13 @@ class TestCLI < Test::Unit::TestCase def test_state url = "tcp://127.0.0.1:8232" - cli = Puma::CLI.new ["--state", @tmp_path, "--status", url] + cli = Puma::CLI.new ["--state", @tmp_path, "--control", url] cli.parse_options cli.write_state data = YAML.load_file(@tmp_path) assert_equal Process.pid, data["pid"] - assert_equal url, data["status_address"] + assert_equal url, data["config"].options[:control_url] end end