puma 5.0.4 → 5.6.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.

Potentially problematic release.


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

Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +322 -48
  3. data/LICENSE +0 -0
  4. data/README.md +95 -24
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +57 -20
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +2 -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 +0 -0
  14. data/docs/jungle/rc.d/README.md +1 -1
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +66 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +28 -0
  20. data/docs/restart.md +7 -7
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +142 -0
  23. data/docs/systemd.md +85 -66
  24. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  25. data/ext/puma_http11/ext_help.h +0 -0
  26. data/ext/puma_http11/extconf.rb +42 -6
  27. data/ext/puma_http11/http11_parser.c +68 -57
  28. data/ext/puma_http11/http11_parser.h +1 -1
  29. data/ext/puma_http11/http11_parser.java.rl +1 -1
  30. data/ext/puma_http11/http11_parser.rl +1 -1
  31. data/ext/puma_http11/http11_parser_common.rl +1 -1
  32. data/ext/puma_http11/mini_ssl.c +226 -88
  33. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  34. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
  37. data/ext/puma_http11/puma_http11.c +9 -3
  38. data/lib/puma/app/status.rb +4 -7
  39. data/lib/puma/binder.rb +138 -49
  40. data/lib/puma/cli.rb +18 -4
  41. data/lib/puma/client.rb +113 -31
  42. data/lib/puma/cluster/worker.rb +22 -19
  43. data/lib/puma/cluster/worker_handle.rb +13 -2
  44. data/lib/puma/cluster.rb +75 -33
  45. data/lib/puma/commonlogger.rb +0 -0
  46. data/lib/puma/configuration.rb +21 -2
  47. data/lib/puma/const.rb +17 -8
  48. data/lib/puma/control_cli.rb +76 -71
  49. data/lib/puma/detect.rb +19 -9
  50. data/lib/puma/dsl.rb +225 -31
  51. data/lib/puma/error_logger.rb +12 -5
  52. data/lib/puma/events.rb +18 -3
  53. data/lib/puma/io_buffer.rb +0 -0
  54. data/lib/puma/jruby_restart.rb +0 -0
  55. data/lib/puma/json_serialization.rb +96 -0
  56. data/lib/puma/launcher.rb +56 -7
  57. data/lib/puma/minissl/context_builder.rb +14 -6
  58. data/lib/puma/minissl.rb +72 -40
  59. data/lib/puma/null_io.rb +12 -0
  60. data/lib/puma/plugin/tmp_restart.rb +0 -0
  61. data/lib/puma/plugin.rb +2 -2
  62. data/lib/puma/queue_close.rb +7 -7
  63. data/lib/puma/rack/builder.rb +1 -1
  64. data/lib/puma/rack/urlmap.rb +0 -0
  65. data/lib/puma/rack_default.rb +0 -0
  66. data/lib/puma/reactor.rb +19 -12
  67. data/lib/puma/request.rb +55 -21
  68. data/lib/puma/runner.rb +39 -13
  69. data/lib/puma/server.rb +78 -142
  70. data/lib/puma/single.rb +0 -0
  71. data/lib/puma/state_file.rb +45 -9
  72. data/lib/puma/systemd.rb +46 -0
  73. data/lib/puma/thread_pool.rb +11 -8
  74. data/lib/puma/util.rb +8 -1
  75. data/lib/puma.rb +36 -10
  76. data/lib/rack/handler/puma.rb +1 -0
  77. data/tools/Dockerfile +1 -1
  78. data/tools/trickletest.rb +0 -0
  79. metadata +15 -9
@@ -11,6 +11,7 @@ module Puma
11
11
 
12
12
  DefaultTCPHost = "0.0.0.0"
13
13
  DefaultTCPPort = 9292
14
+ DefaultWorkerCheckInterval = 5
14
15
  DefaultWorkerTimeout = 60
15
16
  DefaultWorkerShutdownTimeout = 30
16
17
  end
@@ -92,6 +93,12 @@ module Puma
92
93
  end
93
94
  end
94
95
  end
96
+
97
+ def final_options
98
+ default_options
99
+ .merge(file_options)
100
+ .merge(user_options)
101
+ end
95
102
  end
96
103
 
97
104
  # The main configuration class of Puma.
@@ -187,18 +194,24 @@ module Puma
187
194
  :debug => false,
188
195
  :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
189
196
  :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
197
+ :silence_single_worker_warning => false,
190
198
  :mode => :http,
199
+ :worker_check_interval => DefaultWorkerCheckInterval,
191
200
  :worker_timeout => DefaultWorkerTimeout,
192
201
  :worker_boot_timeout => DefaultWorkerTimeout,
193
202
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
203
+ :worker_culling_strategy => :youngest,
194
204
  :remote_address => :socket,
195
205
  :tag => method(:infer_tag),
196
- :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
206
+ :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
197
207
  :rackup => DefaultRackup,
198
208
  :logger => STDOUT,
199
209
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
200
210
  :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
201
- :raise_exception_on_sigterm => true
211
+ :raise_exception_on_sigterm => true,
212
+ :max_fast_inline => Const::MAX_FAST_INLINE,
213
+ :io_selector_backend => :auto,
214
+ :mutate_stdout_and_stderr_to_sync_on_write => true,
202
215
  }
203
216
  end
204
217
 
@@ -289,6 +302,10 @@ module Puma
289
302
  end
290
303
  end
291
304
 
305
+ def final_options
306
+ @options.final_options
307
+ end
308
+
292
309
  def self.temp_path
293
310
  require 'tmpdir'
294
311
 
@@ -329,6 +346,8 @@ module Puma
329
346
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
330
347
 
331
348
  rack_app, rack_options = rack_builder.parse_file(rackup)
349
+ rack_options = rack_options || {}
350
+
332
351
  @options.file_options.merge!(rack_options)
333
352
 
334
353
  config_ru_binds = []
data/lib/puma/const.rb CHANGED
@@ -76,7 +76,7 @@ module Puma
76
76
  508 => 'Loop Detected',
77
77
  510 => 'Not Extended',
78
78
  511 => 'Network Authentication Required'
79
- }
79
+ }.freeze
80
80
 
81
81
  # For some HTTP status codes the client only expects headers.
82
82
  #
@@ -85,7 +85,7 @@ module Puma
85
85
  204 => true,
86
86
  205 => true,
87
87
  304 => true
88
- }
88
+ }.freeze
89
89
 
90
90
  # Frequently used constants when constructing requests or responses. Many times
91
91
  # the constant just refers to a string with the same contents. Using these constants
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.0.4".freeze
104
- CODE_NAME = "Spoony Bard".freeze
103
+ PUMA_VERSION = VERSION = "5.6.4".freeze
104
+ CODE_NAME = "Birdie's Version".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -145,9 +145,11 @@ module Puma
145
145
  408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
146
146
  # Indicate that there was an internal error, obviously.
147
147
  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
148
+ # Incorrect or invalid header value
149
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
148
150
  # A common header for indicating the server is too busy. Not used yet.
149
151
  503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
150
- }
152
+ }.freeze
151
153
 
152
154
  # The basic max request size we'll try to read.
153
155
  CHUNK_SIZE = 16 * 1024
@@ -228,7 +230,6 @@ module Puma
228
230
  COLON = ": ".freeze
229
231
 
230
232
  NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
232
233
 
233
234
  HIJACK_P = "rack.hijack?".freeze
234
235
  HIJACK = "rack.hijack".freeze
@@ -236,8 +237,16 @@ module Puma
236
237
 
237
238
  EARLY_HINTS = "rack.early_hints".freeze
238
239
 
239
- # Mininum interval to checks worker health
240
- WORKER_CHECK_INTERVAL = 5
240
+ # Illegal character in the key or value of response header
241
+ DQUOTE = "\"".freeze
242
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
243
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
244
+ # header values can contain HTAB?
245
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
241
246
 
247
+ # Banned keys of response header
248
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
249
+
250
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
242
251
  end
243
252
  end
@@ -11,10 +11,32 @@ require 'socket'
11
11
  module Puma
12
12
  class ControlCLI
13
13
 
14
- COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats thread-backtraces refork}
14
+ # values must be string or nil
15
+ # value of `nil` means command cannot be processed via signal
16
+ # @version 5.0.3
17
+ CMD_PATH_SIG_MAP = {
18
+ 'gc' => nil,
19
+ 'gc-stats' => nil,
20
+ 'halt' => 'SIGQUIT',
21
+ 'phased-restart' => 'SIGUSR1',
22
+ 'refork' => 'SIGURG',
23
+ 'reload-worker-directory' => nil,
24
+ 'restart' => 'SIGUSR2',
25
+ 'start' => nil,
26
+ 'stats' => nil,
27
+ 'status' => '',
28
+ 'stop' => 'SIGTERM',
29
+ 'thread-backtraces' => nil
30
+ }.freeze
31
+
32
+ # @deprecated 6.0.0
33
+ COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
+
35
+ # commands that cannot be used in a request
36
+ NO_REQ_COMMANDS = %w{refork}.freeze
15
37
 
16
38
  # @version 5.0.0
17
- PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}
39
+ PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
18
40
 
19
41
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
20
42
  @state = nil
@@ -25,7 +47,7 @@ module Puma
25
47
  @control_auth_token = nil
26
48
  @config_file = nil
27
49
  @command = nil
28
- @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
50
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
29
51
 
30
52
  @argv = argv.dup
31
53
  @stdout = stdout
@@ -33,7 +55,7 @@ module Puma
33
55
  @cli_options = {}
34
56
 
35
57
  opts = OptionParser.new do |o|
36
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
58
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
37
59
 
38
60
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
39
61
  @state = arg
@@ -74,7 +96,7 @@ module Puma
74
96
  end
75
97
 
76
98
  o.on_tail("-V", "--version", "Show version") do
77
- puts Const::PUMA_VERSION
99
+ @stdout.puts Const::PUMA_VERSION
78
100
  exit
79
101
  end
80
102
  end
@@ -86,10 +108,10 @@ module Puma
86
108
 
87
109
  # check presence of command
88
110
  unless @command
89
- raise "Available commands: #{COMMANDS.join(", ")}"
111
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
90
112
  end
91
113
 
92
- unless COMMANDS.include? @command
114
+ unless CMD_PATH_SIG_MAP.key? @command
93
115
  raise "Invalid command: #{@command}"
94
116
  end
95
117
 
@@ -134,7 +156,7 @@ module Puma
134
156
  @pid = sf.pid
135
157
  elsif @pidfile
136
158
  # get pid from pid_file
137
- File.open(@pidfile) { |f| @pid = f.read.to_i }
159
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
138
160
  end
139
161
  end
140
162
 
@@ -142,24 +164,29 @@ module Puma
142
164
  uri = URI.parse @control_url
143
165
 
144
166
  # create server object by scheme
145
- server = case uri.scheme
146
- when "ssl"
147
- require 'openssl'
148
- OpenSSL::SSL::SSLSocket.new(
149
- TCPSocket.new(uri.host, uri.port),
150
- OpenSSL::SSL::SSLContext.new)
151
- .tap { |ssl| ssl.sync_close = true } # default is false
152
- .tap(&:connect)
153
- when "tcp"
154
- TCPSocket.new uri.host, uri.port
155
- when "unix"
156
- UNIXSocket.new "#{uri.host}#{uri.path}"
157
- else
158
- raise "Invalid scheme: #{uri.scheme}"
159
- end
160
-
161
- if @command == "status"
162
- message "Puma is started"
167
+ server =
168
+ case uri.scheme
169
+ when 'ssl'
170
+ require 'openssl'
171
+ OpenSSL::SSL::SSLSocket.new(
172
+ TCPSocket.new(uri.host, uri.port),
173
+ OpenSSL::SSL::SSLContext.new)
174
+ .tap { |ssl| ssl.sync_close = true } # default is false
175
+ .tap(&:connect)
176
+ when 'tcp'
177
+ TCPSocket.new uri.host, uri.port
178
+ when 'unix'
179
+ # check for abstract UNIXSocket
180
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
181
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
182
+ else
183
+ raise "Invalid scheme: #{uri.scheme}"
184
+ end
185
+
186
+ if @command == 'status'
187
+ message 'Puma is started'
188
+ elsif NO_REQ_COMMANDS.include? @command
189
+ raise "Invalid request command: #{@command}"
163
190
  else
164
191
  url = "/#{@command}"
165
192
 
@@ -167,10 +194,10 @@ module Puma
167
194
  url = url + "?token=#{@control_auth_token}"
168
195
  end
169
196
 
170
- server << "GET #{url} HTTP/1.0\r\n\r\n"
197
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
171
198
 
172
199
  unless data = server.read
173
- raise "Server closed connection before responding"
200
+ raise 'Server closed connection before responding'
174
201
  end
175
202
 
176
203
  response = data.split("\r\n")
@@ -179,13 +206,13 @@ module Puma
179
206
  raise "Server sent empty response"
180
207
  end
181
208
 
182
- (@http,@code,@message) = response.first.split(" ",3)
209
+ @http, @code, @message = response.first.split(' ',3)
183
210
 
184
- if @code == "403"
185
- raise "Unauthorized access to server (wrong auth token)"
186
- elsif @code == "404"
211
+ if @code == '403'
212
+ raise 'Unauthorized access to server (wrong auth token)'
213
+ elsif @code == '404'
187
214
  raise "Command error: #{response.last}"
188
- elsif @code != "200"
215
+ elsif @code != '200'
189
216
  raise "Bad response from server: #{@code}"
190
217
  end
191
218
 
@@ -194,7 +221,7 @@ module Puma
194
221
  end
195
222
  ensure
196
223
  if server
197
- if uri.scheme == "ssl"
224
+ if uri.scheme == 'ssl'
198
225
  server.sysclose
199
226
  else
200
227
  server.close unless server.closed?
@@ -204,51 +231,30 @@ module Puma
204
231
 
205
232
  def send_signal
206
233
  unless @pid
207
- raise "Neither pid nor control url available"
234
+ raise 'Neither pid nor control url available'
208
235
  end
209
236
 
210
237
  begin
238
+ sig = CMD_PATH_SIG_MAP[@command]
211
239
 
212
- case @command
213
- when "restart"
214
- Process.kill "SIGUSR2", @pid
215
-
216
- when "halt"
217
- Process.kill "QUIT", @pid
218
-
219
- when "stop"
220
- Process.kill "SIGTERM", @pid
221
-
222
- when "stats"
223
- puts "Stats not available via pid only"
224
- return
225
-
226
- when "reload-worker-directory"
227
- puts "reload-worker-directory not available via pid only"
240
+ if sig.nil?
241
+ @stdout.puts "'#{@command}' not available via pid only"
242
+ @stdout.flush unless @stdout.sync
228
243
  return
229
-
230
- when "phased-restart"
231
- Process.kill "SIGUSR1", @pid
232
-
233
- when "status"
244
+ elsif sig.start_with? 'SIG'
245
+ Process.kill sig, @pid
246
+ elsif @command == 'status'
234
247
  begin
235
248
  Process.kill 0, @pid
236
- puts "Puma is started"
249
+ @stdout.puts 'Puma is started'
250
+ @stdout.flush unless @stdout.sync
237
251
  rescue Errno::ESRCH
238
- raise "Puma is not running"
252
+ raise 'Puma is not running'
239
253
  end
240
-
241
- return
242
-
243
- when "refork"
244
- Process.kill "SIGURG", @pid
245
-
246
- else
247
254
  return
248
255
  end
249
-
250
256
  rescue SystemCallError
251
- if @command == "restart"
257
+ if @command == 'restart'
252
258
  start
253
259
  else
254
260
  raise "No pid '#{@pid}' found"
@@ -259,14 +265,13 @@ module Puma
259
265
  end
260
266
 
261
267
  def run
262
- return start if @command == "start"
263
-
268
+ return start if @command == 'start'
264
269
  prepare_configuration
265
270
 
266
- if Puma.windows?
271
+ if Puma.windows? || @control_url
267
272
  send_request
268
273
  else
269
- @control_url ? send_request : send_signal
274
+ send_signal
270
275
  end
271
276
 
272
277
  rescue => e
data/lib/puma/detect.rb CHANGED
@@ -1,20 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This file can be loaded independently of puma.rb, so it cannot have any code
4
+ # that assumes puma.rb is loaded.
5
+
6
+
3
7
  module Puma
4
- # at present, MiniSSL::Engine is only defined in extension code, not in minissl.rb
5
- HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
8
+ # @version 5.2.1
9
+ HAS_FORK = ::Process.respond_to? :fork
6
10
 
7
- def self.ssl?
8
- HAS_SSL
9
- end
11
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
10
12
 
11
- IS_JRUBY = defined?(JRUBY_VERSION)
13
+ IS_OSX = RUBY_PLATFORM.include? 'darwin'
14
+
15
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
16
+ IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
17
+
18
+ # @version 5.2.0
19
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
12
20
 
13
21
  def self.jruby?
14
22
  IS_JRUBY
15
23
  end
16
24
 
17
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
25
+ def self.osx?
26
+ IS_OSX
27
+ end
18
28
 
19
29
  def self.windows?
20
30
  IS_WINDOWS
@@ -22,11 +32,11 @@ module Puma
22
32
 
23
33
  # @version 5.0.0
24
34
  def self.mri?
25
- RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?
35
+ IS_MRI
26
36
  end
27
37
 
28
38
  # @version 5.0.0
29
39
  def self.forkable?
30
- ::Process.respond_to?(:fork)
40
+ HAS_FORK
31
41
  end
32
42
  end