puma 5.1.1 → 5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +131 -10
  3. data/README.md +24 -2
  4. data/docs/architecture.md +22 -18
  5. data/docs/compile_options.md +6 -6
  6. data/docs/deployment.md +2 -2
  7. data/docs/jungle/rc.d/README.md +1 -1
  8. data/docs/kubernetes.md +66 -0
  9. data/docs/plugins.md +2 -2
  10. data/docs/rails_dev_mode.md +29 -0
  11. data/docs/restart.md +1 -1
  12. data/docs/stats.md +142 -0
  13. data/docs/systemd.md +1 -1
  14. data/ext/puma_http11/extconf.rb +14 -0
  15. data/ext/puma_http11/http11_parser.c +19 -21
  16. data/ext/puma_http11/http11_parser.h +1 -1
  17. data/ext/puma_http11/http11_parser.java.rl +1 -1
  18. data/ext/puma_http11/http11_parser.rl +1 -1
  19. data/ext/puma_http11/mini_ssl.c +162 -84
  20. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
  21. data/ext/puma_http11/puma_http11.c +2 -2
  22. data/lib/puma.rb +34 -8
  23. data/lib/puma/binder.rb +50 -43
  24. data/lib/puma/client.rb +5 -3
  25. data/lib/puma/cluster.rb +40 -8
  26. data/lib/puma/cluster/worker_handle.rb +4 -0
  27. data/lib/puma/configuration.rb +4 -1
  28. data/lib/puma/const.rb +3 -3
  29. data/lib/puma/control_cli.rb +5 -1
  30. data/lib/puma/detect.rb +14 -10
  31. data/lib/puma/dsl.rb +56 -4
  32. data/lib/puma/error_logger.rb +12 -5
  33. data/lib/puma/events.rb +2 -3
  34. data/lib/puma/launcher.rb +4 -3
  35. data/lib/puma/minissl.rb +48 -17
  36. data/lib/puma/minissl/context_builder.rb +6 -0
  37. data/lib/puma/null_io.rb +12 -0
  38. data/lib/puma/queue_close.rb +7 -7
  39. data/lib/puma/reactor.rb +7 -2
  40. data/lib/puma/request.rb +9 -4
  41. data/lib/puma/runner.rb +8 -3
  42. data/lib/puma/server.rb +46 -112
  43. data/lib/puma/thread_pool.rb +4 -3
  44. data/lib/rack/handler/puma.rb +1 -0
  45. metadata +6 -3
@@ -31,6 +31,10 @@ module Puma
31
31
  @stage == :booted
32
32
  end
33
33
 
34
+ def uptime
35
+ Time.now - started_at
36
+ end
37
+
34
38
  def boot!
35
39
  @last_checkin = Time.now
36
40
  @stage = :booted
@@ -193,6 +193,7 @@ module Puma
193
193
  :debug => false,
194
194
  :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
195
195
  :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
196
+ :silence_single_worker_warning => false,
196
197
  :mode => :http,
197
198
  :worker_timeout => DefaultWorkerTimeout,
198
199
  :worker_boot_timeout => DefaultWorkerTimeout,
@@ -205,7 +206,9 @@ module Puma
205
206
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
206
207
  :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
207
208
  :raise_exception_on_sigterm => true,
208
- :max_fast_inline => Const::MAX_FAST_INLINE
209
+ :max_fast_inline => Const::MAX_FAST_INLINE,
210
+ :io_selector_backend => :auto,
211
+ :mutate_stdout_and_stderr_to_sync_on_write => true,
209
212
  }
210
213
  end
211
214
 
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.1.1".freeze
104
- CODE_NAME = "At Your Service".freeze
103
+ PUMA_VERSION = VERSION = "5.3.1".freeze
104
+ CODE_NAME = "Sweetnighter".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -235,7 +235,7 @@ module Puma
235
235
 
236
236
  EARLY_HINTS = "rack.early_hints".freeze
237
237
 
238
- # Mininum interval to checks worker health
238
+ # Minimum interval to checks worker health
239
239
  WORKER_CHECK_INTERVAL = 5
240
240
 
241
241
  # Illegal character in the key or value of response header
@@ -176,7 +176,9 @@ module Puma
176
176
  when 'tcp'
177
177
  TCPSocket.new uri.host, uri.port
178
178
  when 'unix'
179
- UNIXSocket.new "#{uri.host}#{uri.path}"
179
+ # check for abstract UNIXSocket
180
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
181
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
180
182
  else
181
183
  raise "Invalid scheme: #{uri.scheme}"
182
184
  end
@@ -237,6 +239,7 @@ module Puma
237
239
 
238
240
  if sig.nil?
239
241
  @stdout.puts "'#{@command}' not available via pid only"
242
+ @stdout.flush unless @stdout.sync
240
243
  return
241
244
  elsif sig.start_with? 'SIG'
242
245
  Process.kill sig, @pid
@@ -244,6 +247,7 @@ module Puma
244
247
  begin
245
248
  Process.kill 0, @pid
246
249
  @stdout.puts 'Puma is started'
250
+ @stdout.flush unless @stdout.sync
247
251
  rescue Errno::ESRCH
248
252
  raise 'Puma is not running'
249
253
  end
data/lib/puma/detect.rb CHANGED
@@ -1,32 +1,36 @@
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_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/ ||
14
+ IS_JRUBY && RUBY_DESCRIPTION =~ /mswin/)
15
+
16
+ # @version 5.2.0
17
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
12
18
 
13
19
  def self.jruby?
14
20
  IS_JRUBY
15
21
  end
16
22
 
17
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
18
-
19
23
  def self.windows?
20
24
  IS_WINDOWS
21
25
  end
22
26
 
23
27
  # @version 5.0.0
24
28
  def self.mri?
25
- RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?
29
+ IS_MRI
26
30
  end
27
31
 
28
32
  # @version 5.0.0
29
33
  def self.forkable?
30
- ::Process.respond_to?(:fork)
34
+ HAS_FORK
31
35
  end
32
36
  end
data/lib/puma/dsl.rb CHANGED
@@ -51,14 +51,20 @@ module Puma
51
51
  if defined?(JRUBY_VERSION)
52
52
  ssl_cipher_list = opts[:ssl_cipher_list] ?
53
53
  "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
54
+
54
55
  keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
56
+
55
57
  "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
56
58
  "&verify_mode=#{verify}#{tls_str}#{ca_additions}"
57
59
  else
58
60
  ssl_cipher_filter = opts[:ssl_cipher_filter] ?
59
61
  "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
62
+
63
+ v_flags = (ary = opts[:verification_flags]) ?
64
+ "&verification_flags=#{Array(ary).join ','}" : nil
65
+
60
66
  "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
61
- "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}"
67
+ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}"
62
68
  end
63
69
  end
64
70
 
@@ -251,7 +257,7 @@ module Puma
251
257
  # port 9292
252
258
  def port(port, host=nil)
253
259
  host ||= default_host
254
- bind "tcp://#{host}:#{port}"
260
+ bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
255
261
  end
256
262
 
257
263
  # Define how long persistent connections can be idle before Puma closes them.
@@ -399,7 +405,10 @@ module Puma
399
405
  # Configure +min+ to be the minimum number of threads to use to answer
400
406
  # requests and +max+ the maximum.
401
407
  #
402
- # The default is "0, 16".
408
+ # The default is the environment variables +PUMA_MIN_THREADS+ / +PUMA_MAX_THREADS+
409
+ # (or +MIN_THREADS+ / +MAX_THREADS+ if the +PUMA_+ variables aren't set).
410
+ #
411
+ # If these environment variables aren't set, the default is "0, 5" in MRI or "0, 16" for other interpreters.
403
412
  #
404
413
  # @example
405
414
  # threads 0, 16
@@ -429,6 +438,7 @@ module Puma
429
438
  # key: path_to_key,
430
439
  # ssl_cipher_filter: cipher_filter, # optional
431
440
  # verify_mode: verify_mode, # default 'none'
441
+ # verification_flags: flags, # optional, not supported by JRuby
432
442
  # }
433
443
  # @example For JRuby, two keys are required: keystore & keystore_pass.
434
444
  # ssl_bind '127.0.0.1', '9292', {
@@ -463,7 +473,8 @@ module Puma
463
473
  # How many worker processes to run. Typically this is set to
464
474
  # the number of available cores.
465
475
  #
466
- # The default is 0.
476
+ # The default is the value of the environment variable +WEB_CONCURRENCY+ if
477
+ # set, otherwise 0.
467
478
  #
468
479
  # @note Cluster mode only.
469
480
  # @see Puma::Cluster
@@ -471,6 +482,24 @@ module Puma
471
482
  @options[:workers] = count.to_i
472
483
  end
473
484
 
485
+ # Disable warning message when running in cluster mode with a single worker.
486
+ #
487
+ # Cluster mode has some overhead of running an additional 'control' process
488
+ # in order to manage the cluster. If only running a single worker it is
489
+ # likely not worth paying that overhead vs running in single mode with
490
+ # additional threads instead.
491
+ #
492
+ # There are some scenarios where running cluster mode with a single worker
493
+ # may still be warranted and valid under certain deployment scenarios, see
494
+ # https://github.com/puma/puma/issues/2534
495
+ #
496
+ # Moving from workers = 1 to workers = 0 will save 10-30% of memory use.
497
+ #
498
+ # @note Cluster mode only.
499
+ def silence_single_worker_warning
500
+ @options[:silence_single_worker_warning] = true
501
+ end
502
+
474
503
  # Code to run immediately before master process
475
504
  # forks workers (once on boot). These hooks can block if necessary
476
505
  # to wait for background operations unknown to Puma to finish before
@@ -858,5 +887,28 @@ module Puma
858
887
  def max_fast_inline(num_of_requests)
859
888
  @options[:max_fast_inline] = Float(num_of_requests)
860
889
  end
890
+
891
+ # Specify the backend for the IO selector.
892
+ #
893
+ # Provided values will be passed directly to +NIO::Selector.new+, with the
894
+ # exception of +:auto+ which will let nio4r choose the backend.
895
+ #
896
+ # Check the documentation of +NIO::Selector.backends+ for the list of valid
897
+ # options. Note that the available options on your system will depend on the
898
+ # operating system. If you want to use the pure Ruby backend (not
899
+ # recommended due to its comparatively low performance), set environment
900
+ # variable +NIO4R_PURE+ to +true+.
901
+ #
902
+ # The default is +:auto+.
903
+ #
904
+ # @see https://github.com/socketry/nio4r/blob/master/lib/nio/selector.rb
905
+ #
906
+ def io_selector_backend(backend)
907
+ @options[:io_selector_backend] = backend.to_sym
908
+ end
909
+
910
+ def mutate_stdout_and_stderr_to_sync_on_write(enabled=true)
911
+ @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
912
+ end
861
913
  end
862
914
  end
@@ -15,7 +15,6 @@ module Puma
15
15
 
16
16
  def initialize(ioerr)
17
17
  @ioerr = ioerr
18
- @ioerr.sync = true
19
18
 
20
19
  @debug = ENV.key? 'PUMA_DEBUG'
21
20
  end
@@ -24,7 +23,7 @@ module Puma
24
23
  new $stderr
25
24
  end
26
25
 
27
- # Print occured error details.
26
+ # Print occurred error details.
28
27
  # +options+ hash with additional options:
29
28
  # - +error+ is an exception object
30
29
  # - +req+ the http request
@@ -32,10 +31,10 @@ module Puma
32
31
  # and before all remaining info.
33
32
  #
34
33
  def info(options={})
35
- ioerr.puts title(options)
34
+ log title(options)
36
35
  end
37
36
 
38
- # Print occured error details only if
37
+ # Print occurred error details only if
39
38
  # environment variable PUMA_DEBUG is defined.
40
39
  # +options+ hash with additional options:
41
40
  # - +error+ is an exception object
@@ -54,7 +53,7 @@ module Puma
54
53
  string_block << request_dump(req) if request_parsed?(req)
55
54
  string_block << error.backtrace if error
56
55
 
57
- ioerr.puts string_block.join("\n")
56
+ log string_block.join("\n")
58
57
  end
59
58
 
60
59
  def title(options={})
@@ -93,5 +92,13 @@ module Puma
93
92
  def request_parsed?(req)
94
93
  req && req.env[REQUEST_METHOD]
95
94
  end
95
+
96
+ private
97
+
98
+ def log(str)
99
+ ioerr.puts str
100
+
101
+ ioerr.flush unless ioerr.sync
102
+ end
96
103
  end
97
104
  end
data/lib/puma/events.rb CHANGED
@@ -30,9 +30,6 @@ module Puma
30
30
  @stdout = stdout
31
31
  @stderr = stderr
32
32
 
33
- @stdout.sync = true
34
- @stderr.sync = true
35
-
36
33
  @debug = ENV.key? 'PUMA_DEBUG'
37
34
  @error_logger = ErrorLogger.new(@stderr)
38
35
 
@@ -66,6 +63,8 @@ module Puma
66
63
  #
67
64
  def log(str)
68
65
  @stdout.puts format(str) if @stdout.respond_to? :puts
66
+
67
+ @stdout.flush unless @stdout.sync
69
68
  rescue Errno::EPIPE
70
69
  end
71
70
 
data/lib/puma/launcher.rb CHANGED
@@ -139,7 +139,6 @@ module Puma
139
139
 
140
140
  # Begin async shutdown of the server gracefully
141
141
  def stop
142
- @events.fire_on_stopped!
143
142
  @status = :stop
144
143
  @runner.stop
145
144
  end
@@ -218,6 +217,10 @@ module Puma
218
217
  def close_binder_listeners
219
218
  @runner.close_control_listeners
220
219
  @binder.close_listeners
220
+ unless @status == :restart
221
+ log "=== puma shutdown: #{Time.now} ==="
222
+ log "- Goodbye!"
223
+ end
221
224
  end
222
225
 
223
226
  # @!attribute [r] thread_status
@@ -375,8 +378,6 @@ module Puma
375
378
  def graceful_stop
376
379
  @events.fire_on_stopped!
377
380
  @runner.stop_blocked
378
- log "=== puma shutdown: #{Time.now} ==="
379
- log "- Goodbye!"
380
381
  end
381
382
 
382
383
  def set_process_title
data/lib/puma/minissl.rb CHANGED
@@ -73,7 +73,6 @@ module Puma
73
73
 
74
74
  def engine_read_all
75
75
  output = @engine.read
76
- raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
77
76
  while output and additional_output = @engine.read
78
77
  output << additional_output
79
78
  end
@@ -100,6 +99,7 @@ module Puma
100
99
  # ourselves.
101
100
  raise IO::EAGAINWaitReadable
102
101
  elsif data.nil?
102
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
103
103
  return nil
104
104
  end
105
105
 
@@ -117,22 +117,23 @@ module Puma
117
117
  def write(data)
118
118
  return 0 if data.empty?
119
119
 
120
- need = data.bytesize
120
+ data_size = data.bytesize
121
+ need = data_size
121
122
 
122
123
  while true
123
124
  wrote = @engine.write data
124
- enc = @engine.extract
125
125
 
126
- while enc
127
- @socket.write enc
128
- enc = @engine.extract
126
+ enc_wr = ''.dup
127
+ while (enc = @engine.extract)
128
+ enc_wr << enc
129
129
  end
130
+ @socket.write enc_wr unless enc_wr.empty?
130
131
 
131
132
  need -= wrote
132
133
 
133
- return data.bytesize if need == 0
134
+ return data_size if need == 0
134
135
 
135
- data = data[wrote..-1]
136
+ data = data.byteslice(wrote..-1)
136
137
  end
137
138
  end
138
139
 
@@ -245,6 +246,7 @@ module Puma
245
246
  attr_reader :cert
246
247
  attr_reader :ca
247
248
  attr_accessor :ssl_cipher_filter
249
+ attr_accessor :verification_flags
248
250
 
249
251
  def key=(key)
250
252
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -287,33 +289,58 @@ module Puma
287
289
  VERIFY_PEER = 1
288
290
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
289
291
 
292
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
293
+ # /* Certificate verify flags */
294
+ VERIFICATION_FLAGS = {
295
+ "USE_CHECK_TIME" => 0x2,
296
+ "CRL_CHECK" => 0x4,
297
+ "CRL_CHECK_ALL" => 0x8,
298
+ "IGNORE_CRITICAL" => 0x10,
299
+ "X509_STRICT" => 0x20,
300
+ "ALLOW_PROXY_CERTS" => 0x40,
301
+ "POLICY_CHECK" => 0x80,
302
+ "EXPLICIT_POLICY" => 0x100,
303
+ "INHIBIT_ANY" => 0x200,
304
+ "INHIBIT_MAP" => 0x400,
305
+ "NOTIFY_POLICY" => 0x800,
306
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
307
+ "USE_DELTAS" => 0x2000,
308
+ "CHECK_SS_SIGNATURE" => 0x4000,
309
+ "TRUSTED_FIRST" => 0x8000,
310
+ "SUITEB_128_LOS_ONLY" => 0x10000,
311
+ "SUITEB_192_LOS" => 0x20000,
312
+ "SUITEB_128_LOS" => 0x30000,
313
+ "PARTIAL_CHAIN" => 0x80000,
314
+ "NO_ALT_CHAINS" => 0x100000,
315
+ "NO_CHECK_TIME" => 0x200000
316
+ }.freeze
317
+
290
318
  class Server
291
319
  def initialize(socket, ctx)
292
320
  @socket = socket
293
321
  @ctx = ctx
294
- end
295
-
296
- # @!attribute [r] to_io
297
- def to_io
298
- @socket
322
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
299
323
  end
300
324
 
301
325
  def accept
302
326
  @ctx.check
303
327
  io = @socket.accept
304
- engine = Engine.server @ctx
305
-
328
+ engine = Engine.server @eng_ctx
306
329
  Socket.new io, engine
307
330
  end
308
331
 
309
332
  def accept_nonblock
310
333
  @ctx.check
311
334
  io = @socket.accept_nonblock
312
- engine = Engine.server @ctx
313
-
335
+ engine = Engine.server @eng_ctx
314
336
  Socket.new io, engine
315
337
  end
316
338
 
339
+ # @!attribute [r] to_io
340
+ def to_io
341
+ @socket
342
+ end
343
+
317
344
  # @!attribute [r] addr
318
345
  # @version 5.0.0
319
346
  def addr
@@ -323,6 +350,10 @@ module Puma
323
350
  def close
324
351
  @socket.close unless @socket.closed? # closed? call is for Windows
325
352
  end
353
+
354
+ def closed?
355
+ @socket.closed?
356
+ end
326
357
  end
327
358
  end
328
359
  end