ed-precompiled_puma 7.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +3172 -0
  3. data/LICENSE +29 -0
  4. data/README.md +477 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +25 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +74 -0
  9. data/docs/compile_options.md +55 -0
  10. data/docs/deployment.md +102 -0
  11. data/docs/fork_worker.md +41 -0
  12. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  13. data/docs/images/puma-connection-flow.png +0 -0
  14. data/docs/images/puma-general-arch.png +0 -0
  15. data/docs/java_options.md +54 -0
  16. data/docs/jungle/README.md +9 -0
  17. data/docs/jungle/rc.d/README.md +74 -0
  18. data/docs/jungle/rc.d/puma +61 -0
  19. data/docs/jungle/rc.d/puma.conf +10 -0
  20. data/docs/kubernetes.md +80 -0
  21. data/docs/nginx.md +80 -0
  22. data/docs/plugins.md +42 -0
  23. data/docs/rails_dev_mode.md +28 -0
  24. data/docs/restart.md +65 -0
  25. data/docs/signals.md +98 -0
  26. data/docs/stats.md +148 -0
  27. data/docs/systemd.md +253 -0
  28. data/docs/testing_benchmarks_local_files.md +150 -0
  29. data/docs/testing_test_rackup_ci_files.md +36 -0
  30. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  31. data/ext/puma_http11/ext_help.h +15 -0
  32. data/ext/puma_http11/extconf.rb +65 -0
  33. data/ext/puma_http11/http11_parser.c +1057 -0
  34. data/ext/puma_http11/http11_parser.h +65 -0
  35. data/ext/puma_http11/http11_parser.java.rl +145 -0
  36. data/ext/puma_http11/http11_parser.rl +149 -0
  37. data/ext/puma_http11/http11_parser_common.rl +54 -0
  38. data/ext/puma_http11/mini_ssl.c +852 -0
  39. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  40. data/ext/puma_http11/org/jruby/puma/Http11.java +257 -0
  41. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  42. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
  43. data/ext/puma_http11/puma_http11.c +507 -0
  44. data/lib/puma/app/status.rb +96 -0
  45. data/lib/puma/binder.rb +511 -0
  46. data/lib/puma/cli.rb +245 -0
  47. data/lib/puma/client.rb +720 -0
  48. data/lib/puma/cluster/worker.rb +182 -0
  49. data/lib/puma/cluster/worker_handle.rb +127 -0
  50. data/lib/puma/cluster.rb +635 -0
  51. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  52. data/lib/puma/commonlogger.rb +115 -0
  53. data/lib/puma/configuration.rb +452 -0
  54. data/lib/puma/const.rb +307 -0
  55. data/lib/puma/control_cli.rb +320 -0
  56. data/lib/puma/detect.rb +47 -0
  57. data/lib/puma/dsl.rb +1480 -0
  58. data/lib/puma/error_logger.rb +115 -0
  59. data/lib/puma/events.rb +72 -0
  60. data/lib/puma/io_buffer.rb +50 -0
  61. data/lib/puma/jruby_restart.rb +11 -0
  62. data/lib/puma/json_serialization.rb +96 -0
  63. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  64. data/lib/puma/launcher.rb +496 -0
  65. data/lib/puma/log_writer.rb +147 -0
  66. data/lib/puma/minissl/context_builder.rb +96 -0
  67. data/lib/puma/minissl.rb +463 -0
  68. data/lib/puma/null_io.rb +101 -0
  69. data/lib/puma/plugin/systemd.rb +90 -0
  70. data/lib/puma/plugin/tmp_restart.rb +36 -0
  71. data/lib/puma/plugin.rb +111 -0
  72. data/lib/puma/rack/builder.rb +297 -0
  73. data/lib/puma/rack/urlmap.rb +93 -0
  74. data/lib/puma/rack_default.rb +24 -0
  75. data/lib/puma/reactor.rb +140 -0
  76. data/lib/puma/request.rb +701 -0
  77. data/lib/puma/runner.rb +211 -0
  78. data/lib/puma/sd_notify.rb +146 -0
  79. data/lib/puma/server.rb +734 -0
  80. data/lib/puma/single.rb +72 -0
  81. data/lib/puma/state_file.rb +69 -0
  82. data/lib/puma/thread_pool.rb +402 -0
  83. data/lib/puma/util.rb +134 -0
  84. data/lib/puma.rb +93 -0
  85. data/lib/rack/handler/puma.rb +144 -0
  86. data/tools/Dockerfile +18 -0
  87. data/tools/trickletest.rb +44 -0
  88. metadata +152 -0
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ # Calculate a delay value for sleeping when running in clustered mode
5
+ #
6
+ # The main reason this is a class is so it can be unit tested independently.
7
+ # This makes modification easier in the future if we can encode properties of the
8
+ # delay into a test instead of relying on end-to-end testing only.
9
+ #
10
+ # This is an imprecise mechanism to address specific goals:
11
+ #
12
+ # - Evenly distribute requests across all workers at start
13
+ # - Evenly distribute CPU resources across all workers
14
+ #
15
+ # ## Goal: Distribute requests across workers at start
16
+ #
17
+ # There was a perf bug in Puma where one worker would wake up slightly before the rest and accept
18
+ # all the requests on the socket even though it didn't have enough resources to process all of them.
19
+ # This was originally fixed by never calling accept when a worker had more requests than threads
20
+ # already https://github.com/puma/puma/pull/3678/files/2736ebddb3fc8528e5150b5913fba251c37a8bf7#diff-a95f46e7ce116caddc9b9a9aa81004246d5210d5da5f4df90a818c780630166bL251-L291
21
+ #
22
+ # With the introduction of true keepalive support, there are two ways a request can come in:
23
+ # - A new request from a new client comes into the socket and it must be "accept"-ed
24
+ # - A keepalive request is served and the connection is retained. Another request is then accepted
25
+ #
26
+ # Ideally the server handles requests in the order they come in, and ideally it doesn't accept more requests than it can handle.
27
+ # These goals are contradictory, because when the server is at maximum capacity due to keepalive connections, it could mean we
28
+ # block all new requests, even if those came in before the new request on the older keepalive connection.
29
+ #
30
+ # ## Goal: Distribute CPU resources across all workers
31
+ #
32
+ # - This issue was opened https://github.com/puma/puma/issues/2078
33
+ #
34
+ # There are several entangled issues and it's not exactly clear what the root cause is, but the observable outcome
35
+ # was that performance was better with a small sleep, and that eventually became the default.
36
+ #
37
+ # An attempt to describe why this works is here: https://github.com/puma/puma/issues/2078#issuecomment-3287032470.
38
+ #
39
+ # Summarizing: The delay is for tuning the rate at which "accept" is called on the socket.
40
+ # Puma works by calling "accept" nonblock on the socket in a loop. When there are multiple workers
41
+ # (processes), they will "race" to accept a request at roughly the same rate. However, if one
42
+ # worker has all threads busy processing requests, then accepting a new request might "steal" it from
43
+ # a less busy worker. If a worker has no work to do, it should loop as fast as possible.
44
+ #
45
+ # ## Solution: Distribute requests across workers at start
46
+ #
47
+ # For now, both goals are framed as "load balancing" across workers (processes) and achieved through
48
+ # the same mechanism of sleeping longer to delay busier workers. Rather than the prior Puma 6.x
49
+ # and earlier behavior of using a binary on/off sleep value, we increase it an amount proportional
50
+ # to the load the server is under, capping the maximum delay to the scenario where all threads are busy
51
+ # and the todo list has reached a multiplier of the maximum number of threads.
52
+ #
53
+ # Private: API may change unexpectedly
54
+ class ClusterAcceptLoopDelay
55
+ attr_reader :max_delay
56
+
57
+ # Initialize happens once, `call` happens often. Perform global calculations here.
58
+ def initialize(
59
+ # Number of workers in the cluster
60
+ workers: ,
61
+ # Maximum delay in seconds i.e. 0.005 is 5 milliseconds
62
+ max_delay:
63
+ )
64
+ @on = max_delay > 0 && workers >= 2
65
+ @max_delay = max_delay.to_f
66
+
67
+ # Reach maximum delay when `max_threads * overload_multiplier` is reached in the system
68
+ @overload_multiplier = 25.0
69
+ end
70
+
71
+ def on?
72
+ @on
73
+ end
74
+
75
+ # We want the extreme values of this delay to be known (minimum and maximum) as well as
76
+ # a predictable curve between the two. i.e. no step functions or hard cliffs.
77
+ #
78
+ # Return value is always numeric. Returns 0 if there should be no delay.
79
+ def calculate(
80
+ # Number of threads working right now, plus number of requests in the todo list
81
+ busy_threads_plus_todo:,
82
+ # Maximum number of threads in the pool, note that the busy threads (alone) may go over this value at times
83
+ # if the pool needs to be reaped. The busy thread plus todo count may go over this value by a large amount.
84
+ max_threads:
85
+ )
86
+ max_value = @overload_multiplier * max_threads
87
+ # Approaches max delay when `busy_threads_plus_todo` approaches `max_value`
88
+ return max_delay * busy_threads_plus_todo.clamp(0, max_value) / max_value
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ # Rack::CommonLogger forwards every request to the given +app+, and
5
+ # logs a line in the
6
+ # {Apache common log format}[https://httpd.apache.org/docs/2.4/logs.html#common]
7
+ # to the +logger+.
8
+ #
9
+ # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
10
+ # an instance of Rack::NullLogger.
11
+ #
12
+ # +logger+ can be any class, including the standard library Logger, and is
13
+ # expected to have either +write+ or +<<+ method, which accepts the CommonLogger::FORMAT.
14
+ # According to the SPEC, the error stream must also respond to +puts+
15
+ # (which takes a single argument that responds to +to_s+), and +flush+
16
+ # (which is called without arguments in order to make the error appear for
17
+ # sure)
18
+ class CommonLogger
19
+ # Common Log Format: https://httpd.apache.org/docs/2.4/logs.html#common
20
+ #
21
+ # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
22
+ #
23
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
24
+ FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
25
+
26
+ HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
27
+
28
+ LOG_TIME_FORMAT = '%d/%b/%Y:%H:%M:%S %z'
29
+
30
+ CONTENT_LENGTH = 'Content-Length' # should be lower case from app,
31
+ # Util::HeaderHash allows mixed
32
+ HTTP_X_FORWARDED_FOR = Const::HTTP_X_FORWARDED_FOR
33
+ PATH_INFO = Const::PATH_INFO
34
+ QUERY_STRING = Const::QUERY_STRING
35
+ REMOTE_ADDR = Const::REMOTE_ADDR
36
+ REMOTE_USER = 'REMOTE_USER'
37
+ REQUEST_METHOD = Const::REQUEST_METHOD
38
+ SERVER_PROTOCOL = Const::SERVER_PROTOCOL
39
+
40
+ def initialize(app, logger=nil)
41
+ @app = app
42
+ @logger = logger
43
+ end
44
+
45
+ def call(env)
46
+ began_at = Time.now
47
+ status, header, body = @app.call(env)
48
+ header = Util::HeaderHash.new(header)
49
+
50
+ # If we've been hijacked, then output a special line
51
+ if env['rack.hijack_io']
52
+ log_hijacking(env, 'HIJACK', header, began_at)
53
+ else
54
+ ary = env['rack.after_reply']
55
+ ary << lambda { log(env, status, header, began_at) }
56
+ end
57
+
58
+ [status, header, body]
59
+ end
60
+
61
+ private
62
+
63
+ def log_hijacking(env, status, header, began_at)
64
+ now = Time.now
65
+
66
+ msg = HIJACK_FORMAT % [
67
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
68
+ env[REMOTE_USER] || "-",
69
+ now.strftime(LOG_TIME_FORMAT),
70
+ env[REQUEST_METHOD],
71
+ env[PATH_INFO],
72
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
73
+ env[SERVER_PROTOCOL],
74
+ now - began_at ]
75
+
76
+ write(msg)
77
+ end
78
+
79
+ def log(env, status, header, began_at)
80
+ now = Time.now
81
+ length = extract_content_length(header)
82
+
83
+ msg = FORMAT % [
84
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
85
+ env[REMOTE_USER] || "-",
86
+ now.strftime(LOG_TIME_FORMAT),
87
+ env[REQUEST_METHOD],
88
+ env[PATH_INFO],
89
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
90
+ env[SERVER_PROTOCOL],
91
+ status.to_s[0..3],
92
+ length,
93
+ now - began_at ]
94
+
95
+ write(msg)
96
+ end
97
+
98
+ def write(msg)
99
+ logger = @logger || env['rack.errors']
100
+
101
+ # Standard library logger doesn't support write but it supports << which actually
102
+ # calls to write on the log device without formatting
103
+ if logger.respond_to?(:write)
104
+ logger.write(msg)
105
+ else
106
+ logger << msg
107
+ end
108
+ end
109
+
110
+ def extract_content_length(headers)
111
+ value = headers[CONTENT_LENGTH] or return '-'
112
+ value.to_s == '0' ? '-' : value
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,452 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'plugin'
4
+ require_relative 'const'
5
+ require_relative 'dsl'
6
+ require_relative 'events'
7
+
8
+ module Puma
9
+ # A class used for storing "leveled" configuration options.
10
+ #
11
+ # In this class any "user" specified options take precedence over any
12
+ # "file" specified options, take precedence over any "default" options.
13
+ #
14
+ # User input is preferred over "defaults":
15
+ # user_options = { foo: "bar" }
16
+ # default_options = { foo: "zoo" }
17
+ # options = UserFileDefaultOptions.new(user_options, default_options)
18
+ # puts options[:foo]
19
+ # # => "bar"
20
+ #
21
+ # All values can be accessed via `all_of`
22
+ #
23
+ # puts options.all_of(:foo)
24
+ # # => ["bar", "zoo"]
25
+ #
26
+ # A "file" option can be set. This config will be preferred over "default" options
27
+ # but will defer to any available "user" specified options.
28
+ #
29
+ # user_options = { foo: "bar" }
30
+ # default_options = { rackup: "zoo.rb" }
31
+ # options = UserFileDefaultOptions.new(user_options, default_options)
32
+ # options.file_options[:rackup] = "sup.rb"
33
+ # puts options[:rackup]
34
+ # # => "sup.rb"
35
+ #
36
+ # The "default" options can be set via procs. These are resolved during runtime
37
+ # via calls to `finalize_values`
38
+ class UserFileDefaultOptions
39
+ def initialize(user_options, default_options)
40
+ @user_options = user_options
41
+ @file_options = {}
42
+ @default_options = default_options
43
+ end
44
+
45
+ attr_reader :user_options, :file_options, :default_options
46
+
47
+ def [](key)
48
+ fetch(key)
49
+ end
50
+
51
+ def []=(key, value)
52
+ user_options[key] = value
53
+ end
54
+
55
+ def fetch(key, default_value = nil)
56
+ return user_options[key] if user_options.key?(key)
57
+ return file_options[key] if file_options.key?(key)
58
+ return default_options[key] if default_options.key?(key)
59
+
60
+ default_value
61
+ end
62
+
63
+ def all_of(key)
64
+ user = user_options[key]
65
+ file = file_options[key]
66
+ default = default_options[key]
67
+
68
+ user = [user] unless user.is_a?(Array)
69
+ file = [file] unless file.is_a?(Array)
70
+ default = [default] unless default.is_a?(Array)
71
+
72
+ user.compact!
73
+ file.compact!
74
+ default.compact!
75
+
76
+ user + file + default
77
+ end
78
+
79
+ def finalize_values
80
+ @default_options.each do |k,v|
81
+ if v.respond_to? :call
82
+ @default_options[k] = v.call
83
+ end
84
+ end
85
+ end
86
+
87
+ def final_options
88
+ default_options
89
+ .merge(file_options)
90
+ .merge(user_options)
91
+ end
92
+ end
93
+
94
+ # The main configuration class of Puma.
95
+ #
96
+ # It can be initialized with a set of "user" options and "default" options.
97
+ # Defaults will be merged with `Configuration.puma_default_options`.
98
+ #
99
+ # This class works together with 2 main other classes the `UserFileDefaultOptions`
100
+ # which stores configuration options in order so the precedence is that user
101
+ # set configuration wins over "file" based configuration wins over "default"
102
+ # configuration. These configurations are set via the `DSL` class. This
103
+ # class powers the Puma config file syntax and does double duty as a configuration
104
+ # DSL used by the `Puma::CLI` and Puma rack handler.
105
+ #
106
+ # It also handles loading plugins.
107
+ #
108
+ # [Note:]
109
+ # `:port` and `:host` are not valid keys. By the time they make it to the
110
+ # configuration options they are expected to be incorporated into a `:binds` key.
111
+ # Under the hood the DSL maps `port` and `host` calls to `:binds`
112
+ #
113
+ # config = Configuration.new({}) do |user_config, file_config, default_config|
114
+ # user_config.port 3003
115
+ # end
116
+ # config.clamp
117
+ # puts config.options[:port]
118
+ # # => 3003
119
+ #
120
+ # It is expected that `load` is called on the configuration instance after setting
121
+ # config. This method expands any values in `config_file` and puts them into the
122
+ # correct configuration option hash.
123
+ #
124
+ # Once all configuration is complete it is expected that `clamp` will be called
125
+ # on the instance. This will expand any procs stored under "default" values. This
126
+ # is done because an environment variable may have been modified while loading
127
+ # configuration files.
128
+ class Configuration
129
+ class NotLoadedError < StandardError; end
130
+ class NotClampedError < StandardError; end
131
+
132
+ DEFAULTS = {
133
+ auto_trim_time: 30,
134
+ binds: ['tcp://0.0.0.0:9292'.freeze],
135
+ fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
136
+ debug: false,
137
+ enable_keep_alives: true,
138
+ early_hints: nil,
139
+ environment: 'development'.freeze,
140
+ # Number of seconds to wait until we get the first data for the request.
141
+ first_data_timeout: 30,
142
+ # Number of seconds to wait until the next request before shutting down.
143
+ idle_timeout: nil,
144
+ io_selector_backend: :auto,
145
+ log_requests: false,
146
+ logger: STDOUT,
147
+ # Limits how many requests a keep alive connection can make.
148
+ # The connection will be closed after it reaches `max_keep_alive`
149
+ # requests.
150
+ max_keep_alive: 999,
151
+ max_threads: Puma.mri? ? 5 : 16,
152
+ min_threads: 0,
153
+ mode: :http,
154
+ mutate_stdout_and_stderr_to_sync_on_write: true,
155
+ out_of_band: [],
156
+ # Number of seconds for another request within a persistent session.
157
+ persistent_timeout: 65, # PUMA_PERSISTENT_TIMEOUT
158
+ queue_requests: true,
159
+ rackup: 'config.ru'.freeze,
160
+ raise_exception_on_sigterm: true,
161
+ reaping_time: 1,
162
+ remote_address: :socket,
163
+ silence_single_worker_warning: false,
164
+ silence_fork_callback_warning: false,
165
+ tag: File.basename(Dir.getwd),
166
+ tcp_host: '0.0.0.0'.freeze,
167
+ tcp_port: 9292,
168
+ wait_for_less_busy_worker: 0.005,
169
+ worker_boot_timeout: 60,
170
+ worker_check_interval: 5,
171
+ worker_culling_strategy: :youngest,
172
+ worker_shutdown_timeout: 30,
173
+ worker_timeout: 60,
174
+ workers: 0,
175
+ http_content_length_limit: nil
176
+ }
177
+
178
+ def initialize(user_options={}, default_options = {}, env = ENV, &block)
179
+ default_options = self.puma_default_options(env).merge(default_options)
180
+
181
+ @_options = UserFileDefaultOptions.new(user_options, default_options)
182
+ @plugins = PluginLoader.new
183
+ @events = @_options[:events] || Events.new
184
+ @hooks = {}
185
+ @user_dsl = DSL.new(@_options.user_options, self)
186
+ @file_dsl = DSL.new(@_options.file_options, self)
187
+ @default_dsl = DSL.new(@_options.default_options, self)
188
+
189
+ @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
190
+
191
+ if block
192
+ configure(&block)
193
+ end
194
+
195
+ @loaded = false
196
+ @clamped = false
197
+ end
198
+
199
+ attr_reader :plugins, :events, :hooks
200
+
201
+ def options
202
+ raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
203
+
204
+ @_options
205
+ end
206
+
207
+ def configure
208
+ yield @user_dsl, @file_dsl, @default_dsl
209
+ ensure
210
+ @user_dsl._offer_plugins
211
+ @file_dsl._offer_plugins
212
+ @default_dsl._offer_plugins
213
+ end
214
+
215
+ def initialize_copy(other)
216
+ @conf = nil
217
+ @cli_options = nil
218
+ @_options = @_options.dup
219
+ end
220
+
221
+ def flatten
222
+ dup.flatten!
223
+ end
224
+
225
+ def flatten!
226
+ @_options = @_options.flatten
227
+ self
228
+ end
229
+
230
+ def puma_default_options(env = ENV)
231
+ defaults = DEFAULTS.dup
232
+ puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
233
+ defaults
234
+ end
235
+
236
+ def puma_options_from_env(env = ENV)
237
+ min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
238
+ max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
239
+ persistent_timeout = env['PUMA_PERSISTENT_TIMEOUT']
240
+ workers = if env['WEB_CONCURRENCY'] == 'auto'
241
+ require_processor_counter
242
+ ::Concurrent.available_processor_count
243
+ else
244
+ env['WEB_CONCURRENCY']
245
+ end
246
+
247
+ {
248
+ min_threads: min && min != "" && Integer(min),
249
+ max_threads: max && max != "" && Integer(max),
250
+ persistent_timeout: persistent_timeout && persistent_timeout != "" && Integer(persistent_timeout),
251
+ workers: workers && workers != "" && Integer(workers),
252
+ environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
253
+ }
254
+ end
255
+
256
+ def load
257
+ @loaded = true
258
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
259
+ @_options
260
+ end
261
+
262
+ def config_files
263
+ raise NotLoadedError, "ensure load is called before accessing config_files" unless @loaded
264
+
265
+ files = @_options.all_of(:config_files)
266
+
267
+ return [] if files == ['-']
268
+ return files if files.any?
269
+
270
+ first_default_file = %W(config/puma/#{@_options[:environment]}.rb config/puma.rb).find do |f|
271
+ File.exist?(f)
272
+ end
273
+
274
+ [first_default_file]
275
+ end
276
+
277
+ # Call once all configuration (included from rackup files)
278
+ # is loaded to finalize defaults and lock in the configuration.
279
+ #
280
+ # This also calls load if it hasn't been called yet.
281
+ def clamp
282
+ load unless @loaded
283
+ set_conditional_default_options
284
+ @_options.finalize_values
285
+ @clamped = true
286
+ warn_hooks
287
+ options
288
+ end
289
+
290
+ # Injects the Configuration object into the env
291
+ class ConfigMiddleware
292
+ def initialize(config, app)
293
+ @config = config
294
+ @app = app
295
+ end
296
+
297
+ def call(env)
298
+ env[Const::PUMA_CONFIG] = @config
299
+ @app.call(env)
300
+ end
301
+ end
302
+
303
+ # Indicate if there is a properly configured app
304
+ #
305
+ def app_configured?
306
+ options[:app] || File.exist?(rackup)
307
+ end
308
+
309
+ def rackup
310
+ options[:rackup]
311
+ end
312
+
313
+ # Load the specified rackup file, pull options from
314
+ # the rackup file, and set @app.
315
+ #
316
+ def app
317
+ found = options[:app] || load_rackup
318
+
319
+ if options[:log_requests]
320
+ require_relative 'commonlogger'
321
+ logger = options[:custom_logger] ? options[:custom_logger] : options[:logger]
322
+ found = CommonLogger.new(found, logger)
323
+ end
324
+
325
+ ConfigMiddleware.new(self, found)
326
+ end
327
+
328
+ # Return which environment we're running in
329
+ def environment
330
+ options[:environment]
331
+ end
332
+
333
+ def load_plugin(name)
334
+ @plugins.create name
335
+ end
336
+
337
+ # @param key [:Symbol] hook to run
338
+ # @param arg [Launcher, Int] `:before_restart` passes Launcher
339
+ #
340
+ def run_hooks(key, arg, log_writer, hook_data = nil)
341
+ log_writer.debug "Running #{key} hooks"
342
+
343
+ options.all_of(key).each do |hook_options|
344
+ begin
345
+ block = hook_options[:block]
346
+ if id = hook_options[:id]
347
+ hook_data[id] ||= Hash.new
348
+ block.call arg, hook_data[id]
349
+ else
350
+ block.call arg
351
+ end
352
+ rescue => e
353
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
354
+ log_writer.debug e.backtrace.join("\n")
355
+ end
356
+ end
357
+ end
358
+
359
+ def final_options
360
+ options.final_options
361
+ end
362
+
363
+ def self.temp_path
364
+ require 'tmpdir'
365
+
366
+ t = (Time.now.to_f * 1000).to_i
367
+ "#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
368
+ end
369
+
370
+ def self.random_token
371
+ require 'securerandom' unless defined?(SecureRandom)
372
+
373
+ SecureRandom.hex(16)
374
+ end
375
+
376
+ private
377
+
378
+ def require_processor_counter
379
+ require 'concurrent/utility/processor_counter'
380
+ rescue LoadError
381
+ warn <<~MESSAGE
382
+ WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
383
+ Please add "concurrent-ruby" to your Gemfile.
384
+ MESSAGE
385
+ raise
386
+ end
387
+
388
+ # Load and use the normal Rack builder if we can, otherwise
389
+ # fallback to our minimal version.
390
+ def rack_builder
391
+ # Load bundler now if we can so that we can pickup rack from
392
+ # a Gemfile
393
+ if @puma_bundler_pruned
394
+ begin
395
+ require 'bundler/setup'
396
+ rescue LoadError
397
+ end
398
+ end
399
+
400
+ begin
401
+ require 'rack'
402
+ require 'rack/builder'
403
+ ::Rack::Builder
404
+ rescue LoadError
405
+ require_relative 'rack/builder'
406
+ Puma::Rack::Builder
407
+ end
408
+ end
409
+
410
+ def load_rackup
411
+ raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
412
+
413
+ rack_app, rack_options = rack_builder.parse_file(rackup)
414
+ rack_options = rack_options || {}
415
+
416
+ options.file_options.merge!(rack_options)
417
+
418
+ config_ru_binds = []
419
+ rack_options.each do |k, v|
420
+ config_ru_binds << v if k.to_s.start_with?("bind")
421
+ end
422
+
423
+ options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
424
+
425
+ rack_app
426
+ end
427
+
428
+ def set_conditional_default_options
429
+ @_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
430
+ (@_options[:workers] > 1) && Puma.forkable?
431
+ end
432
+
433
+ def warn_hooks
434
+ return if options[:workers] > 0
435
+ return if options[:silence_fork_callback_warning]
436
+
437
+ log_writer = LogWriter.stdio
438
+ @hooks.each_key do |hook|
439
+ options.all_of(hook).each do |hook_options|
440
+ next unless hook_options[:cluster_only]
441
+
442
+ log_writer.log(<<~MSG.tr("\n", " "))
443
+ Warning: The code in the `#{hook}` block will not execute
444
+ in the current Puma configuration. The `#{hook}` block only
445
+ executes in Puma's cluster mode. To fix this, either remove the
446
+ `#{hook}` call or increase Puma's worker count above zero.
447
+ MSG
448
+ end
449
+ end
450
+ end
451
+ end
452
+ end