-
Notifications
You must be signed in to change notification settings - Fork 1
Ensure and enforce that configs are loaded before options are accesse… #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: pr_058_before
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,24 +178,35 @@ 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' | ||
|
|
||
| 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,15 +219,15 @@ def configure | |
| def initialize_copy(other) | ||
| @conf = nil | ||
| @cli_options = nil | ||
| @options = @options.dup | ||
| @_options = @_options.dup | ||
| end | ||
|
|
||
| def flatten | ||
| dup.flatten! | ||
| end | ||
|
|
||
| def flatten! | ||
| @options = @options.flatten | ||
| @_options = @_options.flatten | ||
| self | ||
| end | ||
|
|
||
|
|
@@ -241,28 +256,36 @@ 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 | ||
|
Comment on lines
258
to
262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid marking config as loaded before load completes. ✅ Safer load sequencing def load
- `@loaded` = true
- config_files.each { |config_file| `@file_dsl._load_from`(config_file) }
- `@_options`
+ `@loading` = true
+ config_files.each { |config_file| `@file_dsl._load_from`(config_file) }
+ `@loaded` = true
+ `@_options`
+ensure
+ `@loading` = false
end
def config_files
- raise NotLoadedError, "ensure load is called before accessing config_files" unless `@loaded`
+ raise NotLoadedError, "ensure load is called before accessing config_files" unless `@loaded` || `@loading`Also applies to: 264-276 🤖 Prompt for AI Agents |
||
|
|
||
| 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 | ||
|
|
||
| [first_default_file] | ||
| 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 | ||
|
Comment on lines
279
to
289
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set ✅ Suggested sequencing def clamp
load unless `@loaded`
- `@clamped` = true
- options.finalize_values
- warn_hooks
- options
+ `@_options.finalize_values`
+ `@clamped` = true
+ warn_hooks
+ `@_options`
end🤖 Prompt for AI Agents |
||
|
|
||
| # 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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
State flags not copied in
initialize_copy.The
@loadedand@clampedstate flags are not copied when duplicating aConfigurationobject. If a clamped configuration is duplicated, the copy will have@clamped = false(from original initialization), causingNotClampedErrorwhen accessingoptionson the duplicate.🔧 Proposed fix to copy state flags
🤖 Prompt for AI Agents