puma 3.12.6 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1806 -451
  3. data/LICENSE +23 -20
  4. data/README.md +217 -65
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/docs/kubernetes.md +66 -0
  17. data/docs/nginx.md +2 -2
  18. data/docs/plugins.md +22 -12
  19. data/docs/rails_dev_mode.md +28 -0
  20. data/docs/restart.md +47 -22
  21. data/docs/signals.md +13 -11
  22. data/docs/stats.md +142 -0
  23. data/docs/systemd.md +94 -120
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  27. data/ext/puma_http11/ext_help.h +1 -1
  28. data/ext/puma_http11/extconf.rb +61 -3
  29. data/ext/puma_http11/http11_parser.c +103 -117
  30. data/ext/puma_http11/http11_parser.h +2 -2
  31. data/ext/puma_http11/http11_parser.java.rl +22 -38
  32. data/ext/puma_http11/http11_parser.rl +3 -3
  33. data/ext/puma_http11/http11_parser_common.rl +6 -6
  34. data/ext/puma_http11/mini_ssl.c +389 -99
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +248 -92
  39. data/ext/puma_http11/puma_http11.c +49 -57
  40. data/lib/puma/app/status.rb +71 -49
  41. data/lib/puma/binder.rb +244 -150
  42. data/lib/puma/cli.rb +38 -34
  43. data/lib/puma/client.rb +388 -244
  44. data/lib/puma/cluster/worker.rb +180 -0
  45. data/lib/puma/cluster/worker_handle.rb +97 -0
  46. data/lib/puma/cluster.rb +261 -243
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +116 -88
  49. data/lib/puma/const.rb +154 -104
  50. data/lib/puma/control_cli.rb +115 -70
  51. data/lib/puma/detect.rb +33 -2
  52. data/lib/puma/dsl.rb +764 -134
  53. data/lib/puma/error_logger.rb +113 -0
  54. data/lib/puma/events.rb +16 -112
  55. data/lib/puma/io_buffer.rb +42 -5
  56. data/lib/puma/jruby_restart.rb +2 -59
  57. data/lib/puma/json_serialization.rb +96 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +184 -133
  60. data/lib/puma/log_writer.rb +147 -0
  61. data/lib/puma/minissl/context_builder.rb +93 -0
  62. data/lib/puma/minissl.rb +263 -70
  63. data/lib/puma/null_io.rb +18 -1
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +7 -13
  67. data/lib/puma/rack/builder.rb +9 -11
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +21 -4
  70. data/lib/puma/reactor.rb +93 -315
  71. data/lib/puma/request.rb +671 -0
  72. data/lib/puma/runner.rb +94 -69
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +327 -772
  75. data/lib/puma/single.rb +20 -74
  76. data/lib/puma/state_file.rb +45 -8
  77. data/lib/puma/thread_pool.rb +146 -92
  78. data/lib/puma/util.rb +22 -10
  79. data/lib/puma.rb +60 -5
  80. data/lib/rack/handler/puma.rb +116 -90
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +54 -32
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -25
  88. data/lib/puma/daemon_ext.rb +0 -33
  89. data/lib/puma/delegation.rb +0 -13
  90. data/lib/puma/java_io_buffer.rb +0 -47
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -41
  93. data/tools/jungle/README.md +0 -19
  94. data/tools/jungle/init.d/README.md +0 -61
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
  100. /data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
@@ -3,7 +3,7 @@
3
3
  module Puma
4
4
  # Rack::CommonLogger forwards every request to the given +app+, and
5
5
  # logs a line in the
6
- # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
6
+ # {Apache common log format}[https://httpd.apache.org/docs/2.4/logs.html#common]
7
7
  # to the +logger+.
8
8
  #
9
9
  # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
@@ -16,7 +16,7 @@ module Puma
16
16
  # (which is called without arguments in order to make the error appear for
17
17
  # sure)
18
18
  class CommonLogger
19
- # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
19
+ # Common Log Format: https://httpd.apache.org/docs/2.4/logs.html#common
20
20
  #
21
21
  # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
22
22
  #
@@ -25,10 +25,17 @@ module Puma
25
25
 
26
26
  HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
27
27
 
28
- CONTENT_LENGTH = 'Content-Length'.freeze
29
- PATH_INFO = 'PATH_INFO'.freeze
30
- QUERY_STRING = 'QUERY_STRING'.freeze
31
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
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_VERSION = Const::HTTP_VERSION
33
+ HTTP_X_FORWARDED_FOR = Const::HTTP_X_FORWARDED_FOR
34
+ PATH_INFO = Const::PATH_INFO
35
+ QUERY_STRING = Const::QUERY_STRING
36
+ REMOTE_ADDR = Const::REMOTE_ADDR
37
+ REMOTE_USER = 'REMOTE_USER'
38
+ REQUEST_METHOD = Const::REQUEST_METHOD
32
39
 
33
40
  def initialize(app, logger=nil)
34
41
  @app = app
@@ -57,13 +64,13 @@ module Puma
57
64
  now = Time.now
58
65
 
59
66
  msg = HIJACK_FORMAT % [
60
- env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
61
- env["REMOTE_USER"] || "-",
62
- now.strftime("%d/%b/%Y %H:%M:%S"),
67
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
68
+ env[REMOTE_USER] || "-",
69
+ now.strftime(LOG_TIME_FORMAT),
63
70
  env[REQUEST_METHOD],
64
71
  env[PATH_INFO],
65
72
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
66
- env["HTTP_VERSION"],
73
+ env[HTTP_VERSION],
67
74
  now - began_at ]
68
75
 
69
76
  write(msg)
@@ -74,13 +81,13 @@ module Puma
74
81
  length = extract_content_length(header)
75
82
 
76
83
  msg = FORMAT % [
77
- env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
78
- env["REMOTE_USER"] || "-",
79
- now.strftime("%d/%b/%Y:%H:%M:%S %z"),
84
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
85
+ env[REMOTE_USER] || "-",
86
+ now.strftime(LOG_TIME_FORMAT),
80
87
  env[REQUEST_METHOD],
81
88
  env[PATH_INFO],
82
89
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
83
- env["HTTP_VERSION"],
90
+ env[HTTP_VERSION],
84
91
  status.to_s[0..3],
85
92
  length,
86
93
  now - began_at ]
@@ -1,26 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/rack/builder'
4
- require 'puma/plugin'
5
- require 'puma/const'
3
+ require_relative 'rack/builder'
4
+ require_relative 'plugin'
5
+ require_relative 'const'
6
+ # note that dsl is loaded at end of file, requires ConfigDefault constants
6
7
 
7
8
  module Puma
8
-
9
- module ConfigDefault
10
- DefaultRackup = "config.ru"
11
-
12
- DefaultTCPHost = "0.0.0.0"
13
- DefaultTCPPort = 9292
14
- DefaultWorkerTimeout = 60
15
- DefaultWorkerShutdownTimeout = 30
16
- end
17
-
18
9
  # A class used for storing "leveled" configuration options.
19
10
  #
20
11
  # In this class any "user" specified options take precedence over any
21
12
  # "file" specified options, take precedence over any "default" options.
22
13
  #
23
- # User input is prefered over "defaults":
14
+ # User input is preferred over "defaults":
24
15
  # user_options = { foo: "bar" }
25
16
  # default_options = { foo: "zoo" }
26
17
  # options = UserFileDefaultOptions.new(user_options, default_options)
@@ -32,7 +23,7 @@ module Puma
32
23
  # puts options.all_of(:foo)
33
24
  # # => ["bar", "zoo"]
34
25
  #
35
- # A "file" option can be set. This config will be prefered over "default" options
26
+ # A "file" option can be set. This config will be preferred over "default" options
36
27
  # but will defer to any available "user" specified options.
37
28
  #
38
29
  # user_options = { foo: "bar" }
@@ -54,9 +45,7 @@ module Puma
54
45
  attr_reader :user_options, :file_options, :default_options
55
46
 
56
47
  def [](key)
57
- return user_options[key] if user_options.key?(key)
58
- return file_options[key] if file_options.key?(key)
59
- return default_options[key] if default_options.key?(key)
48
+ fetch(key)
60
49
  end
61
50
 
62
51
  def []=(key, value)
@@ -64,7 +53,11 @@ module Puma
64
53
  end
65
54
 
66
55
  def fetch(key, default_value = nil)
67
- self[key] || default_value
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
68
61
  end
69
62
 
70
63
  def all_of(key)
@@ -90,6 +83,12 @@ module Puma
90
83
  end
91
84
  end
92
85
  end
86
+
87
+ def final_options
88
+ default_options
89
+ .merge(file_options)
90
+ .merge(user_options)
91
+ end
93
92
  end
94
93
 
95
94
  # The main configuration class of Puma.
@@ -106,16 +105,17 @@ module Puma
106
105
  #
107
106
  # It also handles loading plugins.
108
107
  #
109
- # > Note: `:port` and `:host` are not valid keys. By they time they make it to the
108
+ # [Note:]
109
+ # `:port` and `:host` are not valid keys. By the time they make it to the
110
110
  # configuration options they are expected to be incorporated into a `:binds` key.
111
111
  # Under the hood the DSL maps `port` and `host` calls to `:binds`
112
112
  #
113
- # config = Configuration.new({}) do |user_config, file_config, default_config|
114
- # user_config.port 3003
115
- # end
116
- # config.load
117
- # puts config.options[:port]
118
- # # => 3003
113
+ # config = Configuration.new({}) do |user_config, file_config, default_config|
114
+ # user_config.port 3003
115
+ # end
116
+ # config.load
117
+ # puts config.options[:port]
118
+ # # => 3003
119
119
  #
120
120
  # It is expected that `load` is called on the configuration instance after setting
121
121
  # config. This method expands any values in `config_file` and puts them into the
@@ -126,7 +126,50 @@ module Puma
126
126
  # is done because an environment variable may have been modified while loading
127
127
  # configuration files.
128
128
  class Configuration
129
- include ConfigDefault
129
+ DEFAULTS = {
130
+ auto_trim_time: 30,
131
+ binds: ['tcp://0.0.0.0:9292'.freeze],
132
+ clean_thread_locals: false,
133
+ debug: false,
134
+ early_hints: nil,
135
+ environment: 'development'.freeze,
136
+ # Number of seconds to wait until we get the first data for the request
137
+ first_data_timeout: 30,
138
+ io_selector_backend: :auto,
139
+ log_requests: false,
140
+ logger: STDOUT,
141
+ # How many requests to attempt inline before sending a client back to
142
+ # the reactor to be subject to normal ordering. The idea here is that
143
+ # we amortize the cost of going back to the reactor for a well behaved
144
+ # but very "greedy" client across 10 requests. This prevents a not
145
+ # well behaved client from monopolizing the thread forever.
146
+ max_fast_inline: 10,
147
+ max_threads: Puma.mri? ? 5 : 16,
148
+ min_threads: 0,
149
+ mode: :http,
150
+ mutate_stdout_and_stderr_to_sync_on_write: true,
151
+ out_of_band: [],
152
+ # Number of seconds for another request within a persistent session.
153
+ persistent_timeout: 20,
154
+ queue_requests: true,
155
+ rackup: 'config.ru'.freeze,
156
+ raise_exception_on_sigterm: true,
157
+ reaping_time: 1,
158
+ remote_address: :socket,
159
+ silence_single_worker_warning: false,
160
+ silence_fork_callback_warning: false,
161
+ tag: File.basename(Dir.getwd),
162
+ tcp_host: '0.0.0.0'.freeze,
163
+ tcp_port: 9292,
164
+ wait_for_less_busy_worker: 0.005,
165
+ worker_boot_timeout: 60,
166
+ worker_check_interval: 5,
167
+ worker_culling_strategy: :youngest,
168
+ worker_shutdown_timeout: 30,
169
+ worker_timeout: 60,
170
+ workers: 0,
171
+ http_content_length_limit: nil
172
+ }
130
173
 
131
174
  def initialize(user_options={}, default_options = {}, &block)
132
175
  default_options = self.puma_default_options.merge(default_options)
@@ -137,6 +180,10 @@ module Puma
137
180
  @file_dsl = DSL.new(@options.file_options, self)
138
181
  @default_dsl = DSL.new(@options.default_options, self)
139
182
 
183
+ if !@options[:prune_bundler]
184
+ default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
185
+ end
186
+
140
187
  if block
141
188
  configure(&block)
142
189
  end
@@ -168,25 +215,21 @@ module Puma
168
215
  end
169
216
 
170
217
  def puma_default_options
218
+ defaults = DEFAULTS.dup
219
+ puma_options_from_env.each { |k,v| defaults[k] = v if v }
220
+ defaults
221
+ end
222
+
223
+ def puma_options_from_env
224
+ min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
225
+ max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
226
+ workers = ENV['WEB_CONCURRENCY']
227
+
171
228
  {
172
- :min_threads => 0,
173
- :max_threads => 16,
174
- :log_requests => false,
175
- :debug => false,
176
- :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
177
- :workers => 0,
178
- :daemon => false,
179
- :mode => :http,
180
- :worker_timeout => DefaultWorkerTimeout,
181
- :worker_boot_timeout => DefaultWorkerTimeout,
182
- :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
183
- :remote_address => :socket,
184
- :tag => method(:infer_tag),
185
- :environment => -> { ENV['RACK_ENV'] || "development" },
186
- :rackup => DefaultRackup,
187
- :logger => STDOUT,
188
- :persistent_timeout => Const::PERSISTENT_TIMEOUT,
189
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT
229
+ min_threads: min && Integer(min),
230
+ max_threads: max && Integer(max),
231
+ workers: workers && Integer(workers),
232
+ environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
190
233
  }
191
234
  end
192
235
 
@@ -202,7 +245,7 @@ module Puma
202
245
  return [] if files == ['-']
203
246
  return files if files.any?
204
247
 
205
- first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
248
+ first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
206
249
  File.exist?(f)
207
250
  end
208
251
 
@@ -244,16 +287,8 @@ module Puma
244
287
  def app
245
288
  found = options[:app] || load_rackup
246
289
 
247
- if @options[:mode] == :tcp
248
- require 'puma/tcp_logger'
249
-
250
- logger = @options[:logger]
251
- quiet = !@options[:log_requests]
252
- return TCPLogger.new(logger, found, quiet)
253
- end
254
-
255
290
  if @options[:log_requests]
256
- require 'puma/commonlogger'
291
+ require_relative 'commonlogger'
257
292
  logger = @options[:logger]
258
293
  found = CommonLogger.new(found, logger)
259
294
  end
@@ -266,16 +301,31 @@ module Puma
266
301
  @options[:environment]
267
302
  end
268
303
 
269
- def environment_str
270
- environment.respond_to?(:call) ? environment.call : environment
271
- end
272
-
273
304
  def load_plugin(name)
274
305
  @plugins.create name
275
306
  end
276
307
 
277
- def run_hooks(key, arg)
278
- @options.all_of(key).each { |b| b.call arg }
308
+ # @param key [:Symbol] hook to run
309
+ # @param arg [Launcher, Int] `:on_restart` passes Launcher
310
+ #
311
+ def run_hooks(key, arg, log_writer, hook_data = nil)
312
+ @options.all_of(key).each do |b|
313
+ begin
314
+ if Array === b
315
+ hook_data[b[1]] ||= Hash.new
316
+ b[0].call arg, hook_data[b[1]]
317
+ else
318
+ b.call arg
319
+ end
320
+ rescue => e
321
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
322
+ log_writer.debug e.backtrace.join("\n")
323
+ end
324
+ end
325
+ end
326
+
327
+ def final_options
328
+ @options.final_options
279
329
  end
280
330
 
281
331
  def self.temp_path
@@ -287,10 +337,6 @@ module Puma
287
337
 
288
338
  private
289
339
 
290
- def infer_tag
291
- File.basename(Dir.getwd)
292
- end
293
-
294
340
  # Load and use the normal Rack builder if we can, otherwise
295
341
  # fallback to our minimal version.
296
342
  def rack_builder
@@ -318,6 +364,8 @@ module Puma
318
364
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
319
365
 
320
366
  rack_app, rack_options = rack_builder.parse_file(rackup)
367
+ rack_options = rack_options || {}
368
+
321
369
  @options.file_options.merge!(rack_options)
322
370
 
323
371
  config_ru_binds = []
@@ -331,31 +379,11 @@ module Puma
331
379
  end
332
380
 
333
381
  def self.random_token
334
- begin
335
- require 'openssl'
336
- rescue LoadError
337
- end
338
-
339
- count = 16
340
-
341
- bytes = nil
342
-
343
- if defined? OpenSSL::Random
344
- bytes = OpenSSL::Random.random_bytes(count)
345
- elsif File.exist?("/dev/urandom")
346
- File.open('/dev/urandom') { |f| bytes = f.read(count) }
347
- end
348
-
349
- if bytes
350
- token = "".dup
351
- bytes.each_byte { |b| token << b.to_s(16) }
352
- else
353
- token = (0..count).to_a.map { rand(255).to_s(16) }.join
354
- end
382
+ require 'securerandom' unless defined?(SecureRandom)
355
383
 
356
- return token
384
+ SecureRandom.hex(16)
357
385
  end
358
386
  end
359
387
  end
360
388
 
361
- require 'puma/dsl'
389
+ require_relative 'dsl'