puma 4.3.12 → 6.3.1

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1729 -521
  3. data/LICENSE +23 -20
  4. data/README.md +169 -45
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  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/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +84 -128
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +49 -12
  30. data/ext/puma_http11/http11_parser.c +46 -48
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +3 -3
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +2 -2
  35. data/ext/puma_http11/mini_ssl.c +278 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  40. data/ext/puma_http11/puma_http11.c +46 -57
  41. data/lib/puma/app/status.rb +53 -39
  42. data/lib/puma/binder.rb +237 -121
  43. data/lib/puma/cli.rb +34 -34
  44. data/lib/puma/client.rb +172 -98
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +226 -231
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +114 -87
  50. data/lib/puma/const.rb +139 -95
  51. data/lib/puma/control_cli.rb +99 -79
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +516 -110
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -115
  56. data/lib/puma/io_buffer.rb +44 -2
  57. data/lib/puma/jruby_restart.rb +2 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +164 -155
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +36 -19
  63. data/lib/puma/minissl.rb +230 -55
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +1 -1
  67. data/lib/puma/plugin.rb +3 -12
  68. data/lib/puma/rack/builder.rb +7 -11
  69. data/lib/puma/rack/urlmap.rb +0 -0
  70. data/lib/puma/rack_default.rb +19 -4
  71. data/lib/puma/reactor.rb +93 -368
  72. data/lib/puma/request.rb +671 -0
  73. data/lib/puma/runner.rb +92 -75
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +321 -794
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +140 -68
  79. data/lib/puma/util.rb +21 -4
  80. data/lib/puma.rb +54 -7
  81. data/lib/rack/handler/puma.rb +113 -87
  82. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  83. data/tools/trickletest.rb +0 -0
  84. metadata +33 -24
  85. data/docs/tcp_mode.md +0 -96
  86. data/ext/puma_http11/io_buffer.c +0 -155
  87. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  88. data/lib/puma/accept_nonblock.rb +0 -29
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
@@ -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,20 +1,11 @@
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
@@ -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,26 +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,
190
- :raise_exception_on_sigterm => true
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'],
191
233
  }
192
234
  end
193
235
 
@@ -203,7 +245,7 @@ module Puma
203
245
  return [] if files == ['-']
204
246
  return files if files.any?
205
247
 
206
- 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|
207
249
  File.exist?(f)
208
250
  end
209
251
 
@@ -245,16 +287,8 @@ module Puma
245
287
  def app
246
288
  found = options[:app] || load_rackup
247
289
 
248
- if @options[:mode] == :tcp
249
- require 'puma/tcp_logger'
250
-
251
- logger = @options[:logger]
252
- quiet = !@options[:log_requests]
253
- return TCPLogger.new(logger, found, quiet)
254
- end
255
-
256
290
  if @options[:log_requests]
257
- require 'puma/commonlogger'
291
+ require_relative 'commonlogger'
258
292
  logger = @options[:logger]
259
293
  found = CommonLogger.new(found, logger)
260
294
  end
@@ -267,16 +301,31 @@ module Puma
267
301
  @options[:environment]
268
302
  end
269
303
 
270
- def environment_str
271
- environment.respond_to?(:call) ? environment.call : environment
272
- end
273
-
274
304
  def load_plugin(name)
275
305
  @plugins.create name
276
306
  end
277
307
 
278
- def run_hooks(key, arg)
279
- @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
280
329
  end
281
330
 
282
331
  def self.temp_path
@@ -288,10 +337,6 @@ module Puma
288
337
 
289
338
  private
290
339
 
291
- def infer_tag
292
- File.basename(Dir.getwd)
293
- end
294
-
295
340
  # Load and use the normal Rack builder if we can, otherwise
296
341
  # fallback to our minimal version.
297
342
  def rack_builder
@@ -319,6 +364,8 @@ module Puma
319
364
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
320
365
 
321
366
  rack_app, rack_options = rack_builder.parse_file(rackup)
367
+ rack_options = rack_options || {}
368
+
322
369
  @options.file_options.merge!(rack_options)
323
370
 
324
371
  config_ru_binds = []
@@ -332,31 +379,11 @@ module Puma
332
379
  end
333
380
 
334
381
  def self.random_token
335
- begin
336
- require 'openssl'
337
- rescue LoadError
338
- end
339
-
340
- count = 16
341
-
342
- bytes = nil
343
-
344
- if defined? OpenSSL::Random
345
- bytes = OpenSSL::Random.random_bytes(count)
346
- elsif File.exist?("/dev/urandom")
347
- File.open('/dev/urandom') { |f| bytes = f.read(count) }
348
- end
349
-
350
- if bytes
351
- token = "".dup
352
- bytes.each_byte { |b| token << b.to_s(16) }
353
- else
354
- token = (0..count).to_a.map { rand(255).to_s(16) }.join
355
- end
382
+ require 'securerandom' unless defined?(SecureRandom)
356
383
 
357
- return token
384
+ SecureRandom.hex(16)
358
385
  end
359
386
  end
360
387
  end
361
388
 
362
- require 'puma/dsl'
389
+ require_relative 'dsl'