diff --git a/benchmarks/local/puma_info.rb b/benchmarks/local/puma_info.rb index adfd346d98..5a4c7d7efa 100644 --- a/benchmarks/local/puma_info.rb +++ b/benchmarks/local/puma_info.rb @@ -90,7 +90,7 @@ def initialize(argv, stdout=STDOUT, stderr=STDERR) if @config_file config = Puma::Configuration.new({ config_files: [@config_file] }, {}) - config.load + config.clamp @state ||= config.options[:state] @control_url ||= config.options[:control_url] @control_auth_token ||= config.options[:control_auth_token] diff --git a/lib/puma/binder.rb b/lib/puma/binder.rb index 44e923ff7d..34f444e7bd 100644 --- a/lib/puma/binder.rb +++ b/lib/puma/binder.rb @@ -5,7 +5,6 @@ require_relative 'const' require_relative 'util' -require_relative 'configuration' module Puma @@ -19,9 +18,9 @@ class Binder RACK_VERSION = [1,6].freeze - def initialize(log_writer, conf = Configuration.new, env: ENV) + def initialize(log_writer, options, env: ENV) @log_writer = log_writer - @conf = conf + @options = options @listeners = [] @inherited_fds = {} @activated_sockets = {} @@ -31,10 +30,10 @@ def initialize(log_writer, conf = Configuration.new, env: ENV) @proto_env = { "rack.version".freeze => RACK_VERSION, "rack.errors".freeze => log_writer.stderr, - "rack.multithread".freeze => conf.options[:max_threads] > 1, - "rack.multiprocess".freeze => conf.options[:workers] >= 1, + "rack.multithread".freeze => options[:max_threads] > 1, + "rack.multiprocess".freeze => options[:workers] >= 1, "rack.run_once".freeze => false, - RACK_URL_SCHEME => conf.options[:rack_url_scheme], + RACK_URL_SCHEME => options[:rack_url_scheme], "SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "", # I'd like to set a default CONTENT_TYPE here but some things @@ -246,7 +245,7 @@ def parse(binds, log_writer = nil, log_msg = 'Listening') cert_key.each do |v| if params[v]&.start_with?('store:') index = Integer(params.delete(v).split('store:').last) - params["#{v}_pem"] = @conf.options[:store][index] + params["#{v}_pem"] = @options[:store][index] end end MiniSSL::ContextBuilder.new(params, @log_writer).context diff --git a/lib/puma/cli.rb b/lib/puma/cli.rb index 8f0c7a8f27..20dfb325cc 100644 --- a/lib/puma/cli.rb +++ b/lib/puma/cli.rb @@ -93,7 +93,7 @@ def configure_control_url(command_line_arg) # def setup_options(env = ENV) - @conf = Configuration.new({}, {events: @events}, env) do |user_config, file_config| + @conf = Configuration.new({}, { events: @events }, env) do |user_config, file_config| @parser = OptionParser.new do |o| o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg| user_config.bind arg diff --git a/lib/puma/configuration.rb b/lib/puma/configuration.rb index 73d7660d23..7ce2b5b710 100644 --- a/lib/puma/configuration.rb +++ b/lib/puma/configuration.rb @@ -3,6 +3,7 @@ require_relative 'plugin' require_relative 'const' require_relative 'dsl' +require_relative 'events' module Puma # A class used for storing "leveled" configuration options. @@ -112,7 +113,7 @@ def final_options # config = Configuration.new({}) do |user_config, file_config, default_config| # user_config.port 3003 # end - # config.load + # config.clamp # puts config.options[:port] # # => 3003 # @@ -125,6 +126,9 @@ def final_options # is done because an environment variable may have been modified while loading # configuration files. class Configuration + class NotLoadedError < StandardError; end + class NotClampedError < StandardError; end + DEFAULTS = { auto_trim_time: 30, binds: ['tcp://0.0.0.0:9292'.freeze], @@ -174,14 +178,16 @@ class Configuration def initialize(user_options={}, default_options = {}, env = ENV, &block) default_options = self.puma_default_options(env).merge(default_options) - @options = UserFileDefaultOptions.new(user_options, default_options) + @_options = UserFileDefaultOptions.new(user_options, default_options) @plugins = PluginLoader.new - @user_dsl = DSL.new(@options.user_options, self) - @file_dsl = DSL.new(@options.file_options, self) - @default_dsl = DSL.new(@options.default_options, self) - - if !@options[:prune_bundler] - default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable? + @events = @_options[:events] || Events.new + @hooks = {} + @user_dsl = DSL.new(@_options.user_options, self) + @file_dsl = DSL.new(@_options.file_options, self) + @default_dsl = DSL.new(@_options.default_options, self) + + if !@_options[:prune_bundler] + default_options[:preload_app] = (@_options[:workers] > 1) && Puma.forkable? end @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED' @@ -189,9 +195,18 @@ def initialize(user_options={}, default_options = {}, env = ENV, &block) if block configure(&block) end + + @loaded = false + @clamped = false end - attr_reader :options, :plugins + attr_reader :plugins, :events, :hooks + + def options + raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped + + @_options + end def configure yield @user_dsl, @file_dsl, @default_dsl @@ -204,7 +219,7 @@ def configure def initialize_copy(other) @conf = nil @cli_options = nil - @options = @options.dup + @_options = @_options.dup end def flatten @@ -212,7 +227,7 @@ def flatten end def flatten! - @options = @options.flatten + @_options = @_options.flatten self end @@ -241,18 +256,20 @@ def puma_options_from_env(env = ENV) end def load + @loaded = true config_files.each { |config_file| @file_dsl._load_from(config_file) } - - @options + @_options end def config_files - files = @options.all_of(:config_files) + raise NotLoadedError, "ensure load is called before accessing config_files" unless @loaded + + files = @_options.all_of(:config_files) return [] if files == ['-'] return files if files.any? - first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f| + first_default_file = %W(config/puma/#{@_options[:environment]}.rb config/puma.rb).find do |f| File.exist?(f) end @@ -260,9 +277,15 @@ def config_files end # Call once all configuration (included from rackup files) - # is loaded to flesh out any defaults + # is loaded to finalize defaults and lock in the configuration. + # + # This also calls load if it hasn't been called yet. def clamp - @options.finalize_values + load unless @loaded + @clamped = true + options.finalize_values + warn_hooks + options end # Injects the Configuration object into the env @@ -281,11 +304,11 @@ def call(env) # Indicate if there is a properly configured app # def app_configured? - @options[:app] || File.exist?(rackup) + options[:app] || File.exist?(rackup) end def rackup - @options[:rackup] + options[:rackup] end # Load the specified rackup file, pull options from @@ -294,9 +317,9 @@ def rackup def app found = options[:app] || load_rackup - if @options[:log_requests] + if options[:log_requests] require_relative 'commonlogger' - logger = @options[:custom_logger] ? options[:custom_logger] : @options[:logger] + logger = options[:custom_logger] ? options[:custom_logger] : options[:logger] found = CommonLogger.new(found, logger) end @@ -305,7 +328,7 @@ def app # Return which environment we're running in def environment - @options[:environment] + options[:environment] end def load_plugin(name) @@ -318,13 +341,14 @@ def load_plugin(name) def run_hooks(key, arg, log_writer, hook_data = nil) log_writer.debug "Running #{key} hooks" - @options.all_of(key).each do |b| + options.all_of(key).each do |hook_options| begin - if Array === b - hook_data[b[1]] ||= Hash.new - b[0].call arg, hook_data[b[1]] + block = hook_options[:block] + if id = hook_options[:id] + hook_data[id] ||= Hash.new + block.call arg, hook_data[id] else - b.call arg + block.call arg end rescue => e log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}" @@ -334,7 +358,7 @@ def run_hooks(key, arg, log_writer, hook_data = nil) end def final_options - @options.final_options + options.final_options end def self.temp_path @@ -344,6 +368,12 @@ def self.temp_path "#{Dir.tmpdir}/puma-status-#{t}-#{$$}" end + def self.random_token + require 'securerandom' unless defined?(SecureRandom) + + SecureRandom.hex(16) + end + private def require_processor_counter @@ -384,22 +414,34 @@ def load_rackup rack_app, rack_options = rack_builder.parse_file(rackup) rack_options = rack_options || {} - @options.file_options.merge!(rack_options) + options.file_options.merge!(rack_options) config_ru_binds = [] rack_options.each do |k, v| config_ru_binds << v if k.to_s.start_with?("bind") end - @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty? + options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty? rack_app end - def self.random_token - require 'securerandom' unless defined?(SecureRandom) + def warn_hooks + return if options[:workers] > 0 + return if options[:silence_fork_callback_warning] - SecureRandom.hex(16) + @hooks.each do |key, method| + options.all_of(key).each do |hook_options| + next unless hook_options[:cluster_only] + + LogWriter.stdio.log(<<~MSG.tr("\n", " ")) + Warning: The code in the `#{method}` block will not execute + in the current Puma configuration. The `#{method}` block only + executes in Puma's cluster mode. To fix this, either remove the + `#{method}` call or increase Puma's worker count above zero. + MSG + end + end end end end diff --git a/lib/puma/control_cli.rb b/lib/puma/control_cli.rb index f2a0da3a85..bd9c64b20d 100644 --- a/lib/puma/control_cli.rb +++ b/lib/puma/control_cli.rb @@ -128,7 +128,8 @@ def initialize(argv, stdout=STDOUT, stderr=STDERR, env: ENV) require_relative 'log_writer' config = Puma::Configuration.new({ config_files: [@config_file] }, {} , env) - config.load + config.clamp + @state ||= config.options[:state] @control_url ||= config.options[:control_url] @control_auth_token ||= config.options[:control_auth_token] diff --git a/lib/puma/dsl.rb b/lib/puma/dsl.rb index 9e8ef71b9c..189d3a19dd 100644 --- a/lib/puma/dsl.rb +++ b/lib/puma/dsl.rb @@ -13,7 +13,7 @@ module Puma # config = Configuration.new({}) do |user_config| # user_config.port 3001 # end - # config.load + # config.clamp # # puts config.options[:binds] # => "tcp://127.0.0.1:3001" # @@ -25,7 +25,7 @@ module Puma # Resulting configuration: # # config = Configuration.new(config_file: "puma_config.rb") - # config.load + # config.clamp # # puts config.options[:binds] # => "tcp://127.0.0.1:3002" # @@ -733,9 +733,7 @@ def silence_fork_callback_warning # end # def before_fork(&block) - warn_if_in_single_mode('before_fork') - - process_hook :before_fork, nil, block, 'before_fork' + process_hook :before_fork, nil, block, 'before_fork', cluster_only: true end # Code to run in a worker when it boots to setup @@ -755,9 +753,7 @@ def before_worker_boot(key = nil, &block) warn "on_worker_boot is deprecated, use before_worker_boot instead" end - warn_if_in_single_mode('before_worker_boot') - - process_hook :before_worker_boot, key, block, 'before_worker_boot' + process_hook :before_worker_boot, key, block, 'before_worker_boot', cluster_only: true end alias_method :on_worker_boot, :before_worker_boot @@ -782,9 +778,7 @@ def before_worker_shutdown(key = nil, &block) warn "on_worker_shutdown is deprecated, use before_worker_shutdown instead" end - warn_if_in_single_mode('before_worker_shutdown') - - process_hook :before_worker_shutdown, key, block, 'before_worker_shutdown' + process_hook :before_worker_shutdown, key, block, 'before_worker_shutdown', cluster_only: true end alias_method :on_worker_shutdown, :before_worker_shutdown @@ -806,9 +800,7 @@ def before_worker_fork(&block) warn "on_worker_fork is deprecated, use before_worker_fork instead" end - warn_if_in_single_mode('before_worker_fork') - - process_hook :before_worker_fork, nil, block, 'before_worker_fork' + process_hook :before_worker_fork, nil, block, 'before_worker_fork', cluster_only: true end alias_method :on_worker_fork, :before_worker_fork @@ -826,9 +818,7 @@ def before_worker_fork(&block) # end # def after_worker_fork(&block) - warn_if_in_single_mode('after_worker_fork') - - process_hook :after_worker_fork, nil, block, 'after_worker_fork' + process_hook :after_worker_fork, nil, block, 'after_worker_fork', cluster_only: true end alias_method :after_worker_boot, :after_worker_fork @@ -845,7 +835,7 @@ def after_booted(&block) warn "on_booted is deprecated, use after_booted instead" end - @config.options[:events].after_booted(&block) + @config.events.after_booted(&block) end alias_method :on_booted, :after_booted @@ -862,7 +852,7 @@ def after_stopped(&block) warn "on_stopped is deprecated, use after_stopped instead" end - @config.options[:events].after_stopped(&block) + @config.events.after_stopped(&block) end alias_method :on_stopped, :after_stopped @@ -891,9 +881,7 @@ def before_refork(key = nil, &block) warn "on_refork is deprecated, use before_refork instead" end - warn_if_in_single_mode('before_refork') - - process_hook :before_refork, key, block, 'before_refork' + process_hook :before_refork, key, block, 'before_refork', cluster_only: true end alias_method :on_refork, :before_refork @@ -1472,32 +1460,21 @@ def add_pem_values_to_options_store(opts) end end - def process_hook(options_key, key, block, meth) - raise ArgumentError, "expected #{meth} to be given a block" unless block + def process_hook(options_key, key, block, method, cluster_only: false) + raise ArgumentError, "expected #{method} to be given a block" unless block + + @config.hooks[options_key] = method @options[options_key] ||= [] - if ON_WORKER_KEY.include? key.class - @options[options_key] << [block, key.to_sym] + hook_options = { block: block, cluster_only: cluster_only } + hook_options[:id] = if ON_WORKER_KEY.include? key.class + key.to_sym elsif key.nil? - @options[options_key] << block + nil else - raise "'#{meth}' key must be String or Symbol" - end - end - - def warn_if_in_single_mode(hook_name) - return if @options[:silence_fork_callback_warning] - # user_options (CLI) have precedence over config file - workers_val = @config.options.user_options[:workers] || @options[:workers] || - @config.puma_default_options[:workers] || 0 - if workers_val == 0 - log_string = - "Warning: You specified code to run in a `#{hook_name}` block, " \ - "but Puma is not configured to run in cluster mode (worker count > 0), " \ - "so your `#{hook_name}` block will not run." - - LogWriter.stdio.log(log_string) + raise "'#{method}' key must be String or Symbol" end + @options[options_key] << hook_options end end end diff --git a/lib/puma/launcher.rb b/lib/puma/launcher.rb index 8ac957a5d2..f9f7152d06 100644 --- a/lib/puma/launcher.rb +++ b/lib/puma/launcher.rb @@ -22,12 +22,15 @@ class Launcher # # +conf+ A Puma::Configuration object indicating how to run the server. # - # +launcher_args+ A Hash that currently has one required key `:events`, - # this is expected to hold an object similar to an `Puma::LogWriter.stdio`, - # this object will be responsible for broadcasting Puma's internal state - # to a logging destination. An optional key `:argv` can be supplied, - # this should be an array of strings, these arguments are re-used when - # restarting the puma server. + # +launcher_args+ A Hash that has a few optional keys. + # - +:log_writer+:: Expected to hold an object similar to `Puma::LogWriter.stdio`. + # This object will be responsible for broadcasting Puma's internal state + # to a logging destination. + # - +:events+:: Expected to hold an object similar to `Puma::Events`. + # - +:argv+:: Expected to be an array of strings. + # - +:env+:: Expected to hold a hash of environment variables. + # + # These arguments are re-used when restarting the puma server. # # Examples: # @@ -48,18 +51,21 @@ def initialize(conf, launcher_args={}) env = launcher_args.delete(:env) || ENV - @config.options[:log_writer] = @log_writer + @config.clamp + @options = @config.options + + @options[:log_writer] = @log_writer + @options[:logger] = @log_writer if clustered? # Advertise the Configuration Puma.cli_config = @config if defined?(Puma.cli_config) + log_config if env['PUMA_LOG_CONFIG'] - @config.load - - @binder = Binder.new(@log_writer, conf) - @binder.create_inherited_fds(ENV).each { |k| ENV.delete k } - @binder.create_activated_fds(ENV).each { |k| ENV.delete k } + @binder = Binder.new(@log_writer, @options) + @binder.create_inherited_fds(env).each { |k| env.delete k } + @binder.create_activated_fds(env).each { |k| env.delete k } - @environment = conf.environment + @environment = @config.environment # Load the systemd integration if we detect systemd's NOTIFY_SOCKET. # Skip this on JRuby though, because it is incompatible with the systemd @@ -68,20 +74,17 @@ def initialize(conf, launcher_args={}) @config.plugins.create('systemd') end - if @config.options[:bind_to_activated_sockets] - @config.options[:binds] = @binder.synthesize_binds_from_activated_fs( - @config.options[:binds], - @config.options[:bind_to_activated_sockets] == 'only' + if @options[:bind_to_activated_sockets] + @options[:binds] = @binder.synthesize_binds_from_activated_fs( + @options[:binds], + @options[:bind_to_activated_sockets] == 'only' ) end - @options = @config.options - @config.clamp - @log_writer.formatter = LogWriter::PidFormatter.new if clustered? - @log_writer.formatter = options[:log_formatter] if @options[:log_formatter] + @log_writer.formatter = @options[:log_formatter] if @options[:log_formatter] - @log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger] + @log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger] generate_restart_data @@ -97,8 +100,6 @@ def initialize(conf, launcher_args={}) set_rack_environment if clustered? - @options[:logger] = @log_writer - @runner = Cluster.new(self) else @runner = Single.new(self) @@ -106,8 +107,6 @@ def initialize(conf, launcher_args={}) Puma.stats_object = @runner @status = :run - - log_config if env['PUMA_LOG_CONFIG'] end attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir diff --git a/lib/puma/server.rb b/lib/puma/server.rb index cb958e642a..62c628ba15 100644 --- a/lib/puma/server.rb +++ b/lib/puma/server.rb @@ -114,7 +114,7 @@ def initialize(app, events = nil, options = {}) temp = !!(@options[:environment] =~ /\A(development|test)\z/) @leak_stack_on_error = @options[:environment] ? temp : true - @binder = Binder.new(log_writer) + @binder = Binder.new(log_writer, @options) ENV['RACK_ENV'] ||= "development" diff --git a/lib/rack/handler/puma.rb b/lib/rack/handler/puma.rb index 7b3ce3db83..25aaba4c87 100644 --- a/lib/rack/handler/puma.rb +++ b/lib/rack/handler/puma.rb @@ -32,7 +32,7 @@ def config(app, options = {}) @events = options[:events] || ::Puma::Events.new - conf = ::Puma::Configuration.new(options, default_options.merge({events: @events})) do |user_config, file_config, default_config| + conf = ::Puma::Configuration.new(options, default_options.merge({ events: @events })) do |user_config, file_config, default_config| if options.delete(:Verbose) begin require 'rack/commonlogger' # Rack 1.x @@ -72,7 +72,7 @@ def run(app, **options) log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio - launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer, events: @events) + launcher = ::Puma::Launcher.new(conf, log_writer: log_writer, events: @events) yield launcher if block_given? begin diff --git a/test/test_binder.rb b/test/test_binder.rb index 0ed194b76a..96dd5aad85 100644 --- a/test/test_binder.rb +++ b/test/test_binder.rb @@ -14,7 +14,9 @@ class TestBinderBase < PumaTest def setup @log_writer = Puma::LogWriter.strings - @binder = Puma::Binder.new(@log_writer) + @config = Puma::Configuration.new + @config.clamp + @binder = Puma::Binder.new(@log_writer, @config.options) end def teardown @@ -424,29 +426,27 @@ def test_socket_activation_unix end def test_rack_multithread_default_configuration - binder = Puma::Binder.new(@log_writer) - - assert binder.proto_env["rack.multithread"] + assert @binder.proto_env["rack.multithread"] end def test_rack_multithread_custom_configuration conf = Puma::Configuration.new(max_threads: 1) + conf.clamp - binder = Puma::Binder.new(@log_writer, conf) + binder = Puma::Binder.new(@log_writer, conf.options) refute binder.proto_env["rack.multithread"] end def test_rack_multiprocess_default_configuration - binder = Puma::Binder.new(@log_writer) - - refute binder.proto_env["rack.multiprocess"] + refute @binder.proto_env["rack.multiprocess"] end def test_rack_multiprocess_custom_configuration conf = Puma::Configuration.new(workers: 1) + conf.clamp - binder = Puma::Binder.new(@log_writer, conf) + binder = Puma::Binder.new(@log_writer, conf.options) assert binder.proto_env["rack.multiprocess"] end diff --git a/test/test_cli.rb b/test/test_cli.rb index effd4fc1f6..09f6ac6395 100644 --- a/test/test_cli.rb +++ b/test/test_cli.rb @@ -334,8 +334,11 @@ def test_load_path def test_extra_runtime_dependencies cli = Puma::CLI.new ['--extra-runtime-dependencies', 'a,b'] - extra_dependencies = cli.instance_variable_get(:@conf) - .instance_variable_get(:@options)[:extra_runtime_dependencies] + + conf = cli.instance_variable_get(:@conf) + conf.clamp + + extra_dependencies = conf.options[:extra_runtime_dependencies] assert_equal %w[a b], extra_dependencies end @@ -348,7 +351,10 @@ def test_environment_app_env cli = Puma::CLI.new [] cli.send(:setup_options) - assert_equal 'test', cli.instance_variable_get(:@conf).environment + conf = cli.instance_variable_get(:@conf) + conf.clamp + + assert_equal 'test', conf.environment ensure ENV.delete 'APP_ENV' ENV.delete 'RAILS_ENV' @@ -360,7 +366,10 @@ def test_environment_rack_env cli = Puma::CLI.new [] cli.send(:setup_options) - assert_equal @environment, cli.instance_variable_get(:@conf).environment + conf = cli.instance_variable_get(:@conf) + conf.clamp + + assert_equal @environment, conf.environment end def test_environment_rails_env @@ -370,7 +379,10 @@ def test_environment_rails_env cli = Puma::CLI.new [] cli.send(:setup_options) - assert_equal @environment, cli.instance_variable_get(:@conf).environment + conf = cli.instance_variable_get(:@conf) + conf.clamp + + assert_equal @environment, conf.environment ensure ENV.delete 'RAILS_ENV' end diff --git a/test/test_config.rb b/test/test_config.rb index 68a86cb8ad..8a341673b1 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -12,7 +12,9 @@ class TestConfigFile < PumaTest def test_default_max_threads max_threads = 16 max_threads = 5 if RUBY_ENGINE.nil? || RUBY_ENGINE == 'ruby' - assert_equal max_threads, Puma::Configuration.new.options.default_options[:max_threads] + conf = Puma::Configuration.new + conf.clamp + assert_equal max_threads, conf.options.default_options[:max_threads] end def test_app_from_rackup @@ -27,7 +29,7 @@ def test_app_from_rackup conf = Puma::Configuration.new do |c| c.rackup fn end - conf.load + conf.clamp # suppress deprecation warning of Rack (>= 2.2.0) # > Parsing options from the first comment line is deprecated!\n @@ -44,7 +46,7 @@ def test_app_from_app_DSL conf = Puma::Configuration.new do |c| c.load "test/config/app.rb" end - conf.load + conf.clamp app = conf.app @@ -57,7 +59,7 @@ def test_ssl_configuration_from_DSL config.load "test/config/ssl_config.rb" end - conf.load + conf.clamp bind_configuration = conf.options.file_options[:binds].first app = conf.app @@ -75,7 +77,7 @@ def test_ssl_self_signed_configuration_from_DSL config.load "test/config/ssl_self_signed_config.rb" end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?&verify_mode=none" assert_equal [ssl_binding], conf.options[:binds] @@ -93,7 +95,7 @@ def test_ssl_bind } end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?cert=%2Fpath%2Fto%2Fcert&key=%2Fpath%2Fto%2Fkey&verify_mode=the_verify_mode" assert_equal [ssl_binding], conf.options[:binds] @@ -112,7 +114,7 @@ def test_ssl_bind_with_escaped_filenames } end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?cert=%2Fpath%2Fto%2Fcert%2B1&key=%2Fpath%2Fto%2Fkey%2B1&verify_mode=peer&ca=%2Fpath%2Fto%2Fca%2B1" assert_equal [ssl_binding], conf.options[:binds] @@ -134,7 +136,7 @@ def test_ssl_bind_with_cert_and_key_pem } end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?cert=store%3A0&key=store%3A1&verify_mode=the_verify_mode" assert_equal [ssl_binding], conf.options[:binds] @@ -149,7 +151,7 @@ def test_ssl_bind_with_backlog } end - conf.load + conf.clamp ssl_binding = conf.options[:binds].first assert ssl_binding.include?('&backlog=2048') @@ -165,7 +167,7 @@ def test_ssl_bind_with_low_latency_true } end - conf.load + conf.clamp ssl_binding = conf.options[:binds].first assert ssl_binding.include?('&low_latency=true') @@ -181,7 +183,7 @@ def test_ssl_bind_with_low_latency_false } end - conf.load + conf.clamp ssl_binding = conf.options[:binds].first assert ssl_binding.include?('&low_latency=false') @@ -203,7 +205,7 @@ def test_ssl_bind_jruby } end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?keystore=/path/to/keystore" \ "&keystore-pass=password&cipher_suites=#{ciphers}&protocols=TLSv1.2" \ @@ -226,7 +228,7 @@ def test_ssl_bind_jruby_with_ssl_cipher_list } end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?keystore=/path/to/keystore" \ "&keystore-pass=password&ssl_cipher_list=#{cipher_list}" \ @@ -249,7 +251,7 @@ def test_ssl_bind_jruby_with_truststore } end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?keystore=/path/to/keystore" \ "&keystore-pass=password&keystore-type=pkcs12" \ @@ -271,7 +273,7 @@ def test_ssl_bind_no_tlsv1_1 } end - conf.load + conf.clamp ssl_binding = "ssl://0.0.0.0:9292?cert=%2Fpath%2Fto%2Fcert&key=%2Fpath%2Fto%2Fkey&verify_mode=the_verify_mode&no_tlsv1_1=true" assert_equal [ssl_binding], conf.options[:binds] @@ -290,7 +292,7 @@ def test_ssl_bind_with_cipher_filter } end - conf.load + conf.clamp ssl_binding = conf.options[:binds].first assert ssl_binding.include?("&ssl_cipher_filter=#{cipher_filter}") @@ -310,7 +312,7 @@ def test_ssl_bind_with_ciphersuites } end - conf.load + conf.clamp ssl_binding = conf.options[:binds].first assert ssl_binding.include?("&ssl_ciphersuites=#{ciphersuites}") @@ -328,7 +330,7 @@ def test_ssl_bind_with_verification_flags } end - conf.load + conf.clamp ssl_binding = conf.options[:binds].first assert ssl_binding.include?("&verification_flags=TRUSTED_FIRST,NO_CHECK_TIME") @@ -345,7 +347,7 @@ def test_ssl_bind_with_ca } end - conf.load + conf.clamp ssl_binding = conf.options[:binds].first assert_includes ssl_binding, Puma::Util.escape("/path/to/ca") @@ -356,7 +358,7 @@ def test_lowlevel_error_handler_DSL conf = Puma::Configuration.new do |c| c.load "test/config/app.rb" end - conf.load + conf.clamp app = conf.options[:lowlevel_error_handler] @@ -365,6 +367,7 @@ def test_lowlevel_error_handler_DSL def test_allow_users_to_override_default_options conf = Puma::Configuration.new(restart_cmd: 'bin/rails server') + conf.clamp assert_equal 'bin/rails server', conf.options[:restart_cmd] end @@ -373,7 +376,7 @@ def test_overwrite_options conf = Puma::Configuration.new do |c| c.workers 3 end - conf.load + conf.clamp assert_equal conf.options[:workers], 3 conf.options[:workers] += 1 @@ -381,9 +384,8 @@ def test_overwrite_options end def test_explicit_config_files - conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do |c| - end - conf.load + conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) + conf.clamp assert_match(/:3000$/, conf.options[:binds].first) end @@ -391,7 +393,7 @@ def test_parameters_overwrite_files conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do |c| c.port 3030 end - conf.load + conf.clamp assert_match(/:3030$/, conf.options[:binds].first) assert_equal 3, conf.options[:min_threads] @@ -399,37 +401,36 @@ def test_parameters_overwrite_files end def test_config_files_default - conf = Puma::Configuration.new do - end + conf = Puma::Configuration.new + conf.clamp assert_equal [nil], conf.config_files end def test_config_files_with_dash - conf = Puma::Configuration.new(config_files: ['-']) do - end + conf = Puma::Configuration.new(config_files: ['-']) + conf.clamp assert_equal [], conf.config_files end def test_config_files_with_existing_path - conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do - end + conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) + conf.clamp assert_equal ['test/config/settings.rb'], conf.config_files end def test_config_files_with_non_existing_path - conf = Puma::Configuration.new(config_files: ['test/config/typo/settings.rb']) do + conf = Puma::Configuration.new(config_files: ['test/config/typo/settings.rb']) + assert_raises Errno::ENOENT do + conf.clamp end - - assert_equal ['test/config/typo/settings.rb'], conf.config_files end def test_config_files_with_integer_convert - conf = Puma::Configuration.new(config_files: ['test/config/with_integer_convert.rb']) do - end - conf.load + conf = Puma::Configuration.new(config_files: ['test/config/with_integer_convert.rb']) + conf.clamp assert_equal 6, conf.options[:persistent_timeout] assert_equal 3, conf.options[:first_data_timeout] @@ -444,7 +445,7 @@ def test_config_files_with_integer_convert def test_config_files_max_fast_inline_infinity conf = Puma::Configuration.new(config_files: ['test/config/max_fast_inline_infinity.rb']) do end - conf.load + conf.clamp assert_equal Float::INFINITY, conf.options[:max_keep_alive] end @@ -452,15 +453,14 @@ def test_config_files_max_fast_inline_infinity def test_config_max_keep_alive_infinity conf = Puma::Configuration.new(config_files: ['test/config/max_keep_alive_infinity.rb']) do end - conf.load + conf.clamp assert_equal Float::INFINITY, conf.options[:max_keep_alive] end def test_config_files_with_symbol_convert - conf = Puma::Configuration.new(config_files: ['test/config/with_symbol_convert.rb']) do - end - conf.load + conf = Puma::Configuration.new(config_files: ['test/config/with_symbol_convert.rb']) + conf.clamp assert_equal :ruby, conf.options[:io_selector_backend] end @@ -469,7 +469,7 @@ def test_config_raise_exception_on_sigterm conf = Puma::Configuration.new do |c| c.raise_exception_on_sigterm false end - conf.load + conf.clamp assert_equal conf.options[:raise_exception_on_sigterm], false conf.options[:raise_exception_on_sigterm] = true @@ -551,7 +551,7 @@ def test_run_hooks_and_exception raise RuntimeError, 'Error from hook' end end - conf.load + conf.clamp log_writer = Puma::LogWriter.strings conf.run_hooks(:before_restart, 'ARG', log_writer) @@ -560,11 +560,14 @@ def test_run_hooks_and_exception end def test_config_does_not_load_workers_by_default - assert_equal 0, Puma::Configuration.new.options.default_options[:workers] + conf = Puma::Configuration.new + conf.clamp + assert_equal 0, conf.options.default_options[:workers] end def test_final_options_returns_merged_options conf = Puma::Configuration.new({ min_threads: 1, max_threads: 2 }, { min_threads: 2 }) + conf.clamp assert_equal 1, conf.final_options[:min_threads] assert_equal 2, conf.final_options[:max_threads] @@ -572,7 +575,7 @@ def test_final_options_returns_merged_options def test_silence_single_worker_warning_default conf = Puma::Configuration.new - conf.load + conf.clamp assert_equal false, conf.options[:silence_single_worker_warning] end @@ -581,14 +584,14 @@ def test_silence_single_worker_warning_overwrite conf = Puma::Configuration.new do |c| c.silence_single_worker_warning end - conf.load + conf.clamp assert_equal true, conf.options[:silence_single_worker_warning] end def test_silence_fork_callback_warning_default conf = Puma::Configuration.new - conf.load + conf.clamp assert_equal false, conf.options[:silence_fork_callback_warning] end @@ -597,19 +600,57 @@ def test_silence_fork_callback_warning_overwrite conf = Puma::Configuration.new do |c| c.silence_fork_callback_warning end - conf.load + conf.clamp assert_equal true, conf.options[:silence_fork_callback_warning] end def test_http_content_length_limit - assert_nil Puma::Configuration.new.options.default_options[:http_content_length_limit] + conf = Puma::Configuration.new + conf.clamp + + assert_nil conf.options.default_options[:http_content_length_limit] conf = Puma::Configuration.new({ http_content_length_limit: 10000}) + conf.clamp assert_equal 10000, conf.final_options[:http_content_length_limit] end + def test_options_raises_not_clamped_error_when_not_clamped + conf = Puma::Configuration.new + + error = assert_raises(Puma::Configuration::NotClampedError) do + conf.options + end + + assert_equal "ensure clamp is called before accessing options", error.message + end + + def test_options_succeeds_when_clamped + conf = Puma::Configuration.new + conf.clamp + + assert_kind_of Puma::UserFileDefaultOptions, conf.options + end + + def test_config_files_raises_not_loaded_error_when_not_loaded + conf = Puma::Configuration.new + + error = assert_raises(Puma::Configuration::NotLoadedError) do + conf.config_files + end + + assert_equal "ensure load is called before accessing config_files", error.message + end + + def test_config_files_succeeds_when_clamped + conf = Puma::Configuration.new + conf.clamp + + assert_kind_of Array, conf.config_files + end + private def assert_run_hooks(hook_name, options = {}) @@ -619,13 +660,14 @@ def assert_run_hooks(hook_name, options = {}) messages = [] conf = Puma::Configuration.new do |c| c.silence_fork_callback_warning + c.send(configured_with) do |a| + messages << "#{hook_name} is called with #{a}" + end end - conf.options[hook_name] = -> (a) { - messages << "#{hook_name} is called with #{a}" - } + conf.clamp conf.run_hooks(hook_name, 'ARG', Puma::LogWriter.strings) - assert_equal messages, ["#{hook_name} is called with ARG"] + assert_equal ["#{hook_name} is called with ARG"], messages # test multiple messages = [] @@ -640,29 +682,30 @@ def assert_run_hooks(hook_name, options = {}) messages << "#{hook_name} is called with #{a} a second time" end end - conf.load + conf.clamp conf.run_hooks(hook_name, 'ARG', Puma::LogWriter.strings) - assert_equal messages, ["#{hook_name} is called with ARG one time", "#{hook_name} is called with ARG a second time"] + assert_equal ["#{hook_name} is called with ARG one time", "#{hook_name} is called with ARG a second time"], messages end def assert_raise_on_hooks_without_block(hook_name) error = assert_raises ArgumentError do - Puma::Configuration.new { |c| c.send(hook_name) }.load + Puma::Configuration.new { |c| c.send(hook_name) }.clamp end assert_equal "expected #{hook_name} to be given a block", error.message end def assert_warning_for_hooks_defined_in_single_mode(hook_name) out, _ = capture_io do - Puma::Configuration.new do |c| + conf = Puma::Configuration.new do |c| c.send(hook_name) do # noop end end + conf.clamp end - assert_match "your `#{hook_name}` block will not run.\n", out + assert_match /Warning: The code in the `#{hook_name}` block will not execute in the current Puma configuration/, out end end @@ -670,8 +713,8 @@ def assert_warning_for_hooks_defined_in_single_mode(hook_name) class TestConfigFileSingle < PumaTest def test_custom_logger_from_DSL conf = Puma::Configuration.new { |c| c.load 'test/config/custom_logger.rb' } + conf.clamp - conf.load out, _ = capture_subprocess_io { conf.options[:custom_logger].write 'test' } assert_equal "Custom logging: test\n", out @@ -687,61 +730,74 @@ def test_double_bind_port user_config.bind "tcp://#{Puma::Configuration::DEFAULTS[:tcp_host]}:#{port}" file_config.load "test/config/app.rb" end + conf.clamp - conf.load assert_equal ["tcp://0.0.0.0:#{port}"], conf.options[:binds] end end class TestConfigEnvVariables < PumaTest def test_config_loads_correct_min_threads - assert_equal 0, Puma::Configuration.new.options.default_options[:min_threads] + conf = Puma::Configuration.new + conf.clamp + assert_equal 0, conf.options.default_options[:min_threads] env = { "MIN_THREADS" => "7" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal 7, conf.options.default_options[:min_threads] env = { "PUMA_MIN_THREADS" => "8" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal 8, conf.options.default_options[:min_threads] env = { "PUMA_MIN_THREADS" => "" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal 0, conf.options.default_options[:min_threads] end def test_config_loads_correct_max_threads default_max_threads = Puma.mri? ? 5 : 16 - assert_equal default_max_threads, Puma::Configuration.new.options.default_options[:max_threads] + conf = Puma::Configuration.new + conf.clamp + assert_equal default_max_threads, conf.options.default_options[:max_threads] env = { "MAX_THREADS" => "7" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal 7, conf.options.default_options[:max_threads] env = { "PUMA_MAX_THREADS" => "8" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal 8, conf.options.default_options[:max_threads] env = { "PUMA_MAX_THREADS" => "" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal default_max_threads, conf.options.default_options[:max_threads] end def test_config_loads_workers_from_env env = { "WEB_CONCURRENCY" => "9" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal 9, conf.options.default_options[:workers] end def test_config_ignores_blank_workers_from_env env = { "WEB_CONCURRENCY" => "" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal 0, conf.options.default_options[:workers] end def test_config_does_not_preload_app_if_not_using_workers env = { "WEB_CONCURRENCY" => "0" } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal false, conf.options.default_options[:preload_app] end @@ -749,6 +805,7 @@ def test_config_preloads_app_if_using_workers env = { "WEB_CONCURRENCY" => "2" } preload = Puma.forkable? conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal preload, conf.options.default_options[:preload_app] end end @@ -767,6 +824,7 @@ def test_config_files_with_app_env env = { 'APP_ENV' => 'fake-env' } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal ['config/puma/fake-env.rb'], conf.config_files end @@ -775,6 +833,7 @@ def test_config_files_with_rack_env env = { 'RACK_ENV' => 'fake-env' } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal ['config/puma/fake-env.rb'], conf.config_files end @@ -783,21 +842,23 @@ def test_config_files_with_rails_env env = { 'RAILS_ENV' => 'fake-env', 'RACK_ENV' => nil } conf = Puma::Configuration.new({}, {}, env) + conf.clamp assert_equal ['config/puma/fake-env.rb'], conf.config_files end def test_config_files_with_specified_environment - conf = Puma::Configuration.new - - conf.options[:environment] = 'fake-env' + conf = Puma::Configuration.new do |c| + c.environment 'fake-env' + end + conf.clamp assert_equal ['config/puma/fake-env.rb'], conf.config_files end def test_enable_keep_alives_by_default conf = Puma::Configuration.new - conf.load + conf.clamp assert_equal conf.options[:enable_keep_alives], true end @@ -806,7 +867,7 @@ def test_enable_keep_alives_true conf = Puma::Configuration.new do |c| c.enable_keep_alives true end - conf.load + conf.clamp assert_equal conf.options[:enable_keep_alives], true end @@ -815,7 +876,7 @@ def test_enable_keep_alives_false conf = Puma::Configuration.new do |c| c.enable_keep_alives false end - conf.load + conf.clamp assert_equal conf.options[:enable_keep_alives], false end diff --git a/test/test_launcher.rb b/test/test_launcher.rb index b3ec0b88db..5720611995 100644 --- a/test/test_launcher.rb +++ b/test/test_launcher.rb @@ -137,7 +137,9 @@ def test_fire_after_stopped private def create_launcher(config = Puma::Configuration.new, lw = Puma::LogWriter.strings, **kw) - config.options[:binds] = ["tcp://127.0.0.1:#{UniquePort.call}"] + config.configure do |c| + c.bind "tcp://127.0.0.1:#{UniquePort.call}" + end Puma::Launcher.new(config, log_writer: lw, **kw) end end diff --git a/test/test_rack_handler.rb b/test/test_rack_handler.rb index 9632bc93fc..922430cc6d 100644 --- a/test/test_rack_handler.rb +++ b/test/test_rack_handler.rb @@ -107,7 +107,7 @@ def test_port_wins_over_config @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end @@ -128,7 +128,7 @@ def test_host_uses_supplied_port_default @options[:Host] = user_host @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://#{user_host}:#{user_port}"], conf.options[:binds] end @@ -136,7 +136,7 @@ def test_host_uses_supplied_port_default def test_ipv6_host_supplied_port_default @options[:Host] = "::1" conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://[::1]:9292"], conf.options[:binds] end @@ -144,7 +144,7 @@ def test_ipv6_host_supplied_port_default def test_ssl_host_supplied_port_default @options[:Host] = "ssl://127.0.0.1" conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["ssl://127.0.0.1:9292"], conf.options[:binds] end @@ -152,7 +152,7 @@ def test_ssl_host_supplied_port_default def test_relative_unix_host @options[:Host] = "./relative.sock" conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["unix://./relative.sock"], conf.options[:binds] end @@ -160,7 +160,7 @@ def test_relative_unix_host def test_absolute_unix_host @options[:Host] = "/absolute.sock" conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["unix:///absolute.sock"], conf.options[:binds] end @@ -168,7 +168,7 @@ def test_absolute_unix_host def test_abstract_unix_host @options[:Host] = "@abstract.sock" conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["unix://@abstract.sock"], conf.options[:binds] end @@ -191,7 +191,7 @@ def test_config_file_wins_over_port @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://0.0.0.0:#{file_port}"], conf.options[:binds] end @@ -210,7 +210,7 @@ def test_default_host_when_using_config_file @options[:Host] = "localhost" @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://localhost:#{file_port}"], conf.options[:binds] end @@ -229,7 +229,7 @@ def test_default_host_when_using_config_file_with_explicit_host @options[:Host] = "localhost" @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://1.2.3.4:#{file_port}"], conf.options[:binds] end @@ -244,7 +244,7 @@ def setup def test_default_port_when_no_config_file conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://0.0.0.0:9292"], conf.options[:binds] end @@ -258,7 +258,7 @@ def test_config_wins_over_default File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" } conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://0.0.0.0:#{file_port}"], conf.options[:binds] end @@ -270,7 +270,7 @@ def test_user_port_wins_over_default_when_user_supplied_is_blank @options[:user_supplied_options] = [] @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end @@ -279,7 +279,7 @@ def test_user_port_wins_over_default user_port = 5001 @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end @@ -295,7 +295,7 @@ def test_user_port_wins_over_config @options[:Port] = user_port conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end @@ -304,7 +304,7 @@ def test_user_port_wins_over_config def test_default_log_request_when_no_config_file conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal false, conf.options[:log_requests] end @@ -317,7 +317,7 @@ def test_file_log_requests_wins_over_default_config ] conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal file_log_requests_config, conf.options[:log_requests] end @@ -331,7 +331,7 @@ def test_user_log_requests_wins_over_file_config ] conf = ::Rack::Handler::Puma.config(->{}, @options) - conf.load + conf.clamp assert_equal user_log_requests_config, conf.options[:log_requests] end diff --git a/test/test_web_concurrency_auto.rb b/test/test_web_concurrency_auto.rb index 84981bad1f..a32724b827 100644 --- a/test/test_web_concurrency_auto.rb +++ b/test/test_web_concurrency_auto.rb @@ -61,7 +61,10 @@ def test_web_concurrency_with_concurrent_ruby_unavailable end _, err = capture_io do - assert_raises(LoadError) { Puma::Configuration.new({}, {}, ENV_WC_TEST) } + assert_raises(LoadError) do + conf = Puma::Configuration.new({}, {}, ENV_WC_TEST) + conf.clamp + end end assert_includes err, 'Please add "concurrent-ruby" to your Gemfile'