puma 5.0.3 → 5.2.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +110 -40
  3. data/README.md +48 -18
  4. data/docs/compile_options.md +19 -0
  5. data/docs/deployment.md +1 -1
  6. data/docs/fork_worker.md +2 -0
  7. data/docs/kubernetes.md +66 -0
  8. data/docs/plugins.md +1 -1
  9. data/docs/rails_dev_mode.md +29 -0
  10. data/docs/stats.md +142 -0
  11. data/docs/systemd.md +24 -2
  12. data/ext/puma_http11/extconf.rb +18 -5
  13. data/ext/puma_http11/http11_parser.c +45 -47
  14. data/ext/puma_http11/http11_parser.java.rl +1 -1
  15. data/ext/puma_http11/http11_parser.rl +1 -1
  16. data/ext/puma_http11/mini_ssl.c +162 -84
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
  18. data/ext/puma_http11/puma_http11.c +8 -2
  19. data/lib/puma.rb +20 -10
  20. data/lib/puma/app/status.rb +4 -7
  21. data/lib/puma/binder.rb +60 -24
  22. data/lib/puma/cli.rb +4 -0
  23. data/lib/puma/client.rb +4 -9
  24. data/lib/puma/cluster.rb +13 -7
  25. data/lib/puma/cluster/worker.rb +8 -2
  26. data/lib/puma/cluster/worker_handle.rb +5 -2
  27. data/lib/puma/configuration.rb +13 -1
  28. data/lib/puma/const.rb +11 -3
  29. data/lib/puma/control_cli.rb +73 -70
  30. data/lib/puma/detect.rb +14 -10
  31. data/lib/puma/dsl.rb +100 -22
  32. data/lib/puma/error_logger.rb +10 -3
  33. data/lib/puma/events.rb +18 -3
  34. data/lib/puma/json.rb +96 -0
  35. data/lib/puma/launcher.rb +52 -6
  36. data/lib/puma/minissl.rb +48 -17
  37. data/lib/puma/minissl/context_builder.rb +6 -0
  38. data/lib/puma/null_io.rb +4 -0
  39. data/lib/puma/reactor.rb +19 -12
  40. data/lib/puma/request.rb +20 -7
  41. data/lib/puma/runner.rb +14 -7
  42. data/lib/puma/server.rb +20 -75
  43. data/lib/puma/state_file.rb +5 -3
  44. data/lib/puma/systemd.rb +46 -0
  45. data/lib/rack/handler/puma.rb +1 -0
  46. metadata +12 -6
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
@@ -34,6 +34,40 @@ module Puma
34
34
  class DSL
35
35
  include ConfigDefault
36
36
 
37
+ # convenience method so logic can be used in CI
38
+ # @see ssl_bind
39
+ #
40
+ def self.ssl_bind_str(host, port, opts)
41
+ verify = opts.fetch(:verify_mode, 'none').to_s
42
+
43
+ tls_str =
44
+ if opts[:no_tlsv1_1] then '&no_tlsv1_1=true'
45
+ elsif opts[:no_tlsv1] then '&no_tlsv1=true'
46
+ else ''
47
+ end
48
+
49
+ ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
50
+
51
+ if defined?(JRUBY_VERSION)
52
+ ssl_cipher_list = opts[:ssl_cipher_list] ?
53
+ "&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
54
+
55
+ keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
56
+
57
+ "ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
58
+ "&verify_mode=#{verify}#{tls_str}#{ca_additions}"
59
+ else
60
+ ssl_cipher_filter = opts[:ssl_cipher_filter] ?
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
+
66
+ "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
67
+ "#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}"
68
+ end
69
+ end
70
+
37
71
  def initialize(options, config)
38
72
  @config = config
39
73
  @options = options
@@ -191,13 +225,39 @@ module Puma
191
225
  @options[:binds] = []
192
226
  end
193
227
 
228
+ # Bind to (systemd) activated sockets, regardless of configured binds.
229
+ #
230
+ # Systemd can present sockets as file descriptors that are already opened.
231
+ # By default Puma will use these but only if it was explicitly told to bind
232
+ # to the socket. If not, it will close the activated sockets. This means
233
+ # all configuration is duplicated.
234
+ #
235
+ # Binds can contain additional configuration, but only SSL config is really
236
+ # relevant since the unix and TCP socket options are ignored.
237
+ #
238
+ # This means there is a lot of duplicated configuration for no additional
239
+ # value in most setups. This method tells the launcher to bind to all
240
+ # activated sockets, regardless of existing bind.
241
+ #
242
+ # To clear configured binds, the value only can be passed. This will clear
243
+ # out any binds that may have been configured.
244
+ #
245
+ # @example Use any systemd activated sockets as well as configured binds
246
+ # bind_to_activated_sockets
247
+ #
248
+ # @example Only bind to systemd activated sockets, ignoring other binds
249
+ # bind_to_activated_sockets 'only'
250
+ def bind_to_activated_sockets(bind=true)
251
+ @options[:bind_to_activated_sockets] = bind
252
+ end
253
+
194
254
  # Define the TCP port to bind to. Use +bind+ for more advanced options.
195
255
  #
196
256
  # @example
197
257
  # port 9292
198
258
  def port(port, host=nil)
199
259
  host ||= default_host
200
- bind "tcp://#{host}:#{port}"
260
+ bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
201
261
  end
202
262
 
203
263
  # Define how long persistent connections can be idle before Puma closes them.
@@ -345,7 +405,10 @@ module Puma
345
405
  # Configure +min+ to be the minimum number of threads to use to answer
346
406
  # requests and +max+ the maximum.
347
407
  #
348
- # 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.
349
412
  #
350
413
  # @example
351
414
  # threads 0, 16
@@ -375,29 +438,17 @@ module Puma
375
438
  # key: path_to_key,
376
439
  # ssl_cipher_filter: cipher_filter, # optional
377
440
  # verify_mode: verify_mode, # default 'none'
441
+ # verification_flags: flags, # optional, not supported by JRuby
378
442
  # }
379
- # @example For JRuby additional keys are required: keystore & keystore_pass.
443
+ # @example For JRuby, two keys are required: keystore & keystore_pass.
380
444
  # ssl_bind '127.0.0.1', '9292', {
381
- # cert: path_to_cert,
382
- # key: path_to_key,
383
- # ssl_cipher_filter: cipher_filter, # optional
384
- # verify_mode: verify_mode, # default 'none'
385
445
  # keystore: path_to_keystore,
386
- # keystore_pass: password
446
+ # keystore_pass: password,
447
+ # ssl_cipher_list: cipher_list, # optional
448
+ # verify_mode: verify_mode # default 'none'
387
449
  # }
388
450
  def ssl_bind(host, port, opts)
389
- verify = opts.fetch(:verify_mode, 'none').to_s
390
- no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
391
- no_tlsv1_1 = opts.fetch(:no_tlsv1_1, 'false')
392
- ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
393
-
394
- if defined?(JRUBY_VERSION)
395
- keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
396
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
397
- else
398
- ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
399
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
400
- end
451
+ bind self.class.ssl_bind_str(host, port, opts)
401
452
  end
402
453
 
403
454
  # Use +path+ as the file to store the server info state. This is
@@ -422,7 +473,8 @@ module Puma
422
473
  # How many worker processes to run. Typically this is set to
423
474
  # the number of available cores.
424
475
  #
425
- # The default is 0.
476
+ # The default is the value of the environment variable +WEB_CONCURRENCY+ if
477
+ # set, otherwise 0.
426
478
  #
427
479
  # @note Cluster mode only.
428
480
  # @see Puma::Cluster
@@ -561,7 +613,7 @@ module Puma
561
613
  end
562
614
 
563
615
  # Preload the application before starting the workers; this conflicts with
564
- # phased restart feature. This is off by default.
616
+ # phased restart feature. On by default if your app uses more than 1 worker.
565
617
  #
566
618
  # @note Cluster mode only.
567
619
  # @example
@@ -810,5 +862,31 @@ module Puma
810
862
  def nakayoshi_fork(enabled=true)
811
863
  @options[:nakayoshi_fork] = enabled
812
864
  end
865
+
866
+ # The number of requests to attempt inline before sending a client back to
867
+ # the reactor to be subject to normal ordering.
868
+ #
869
+ def max_fast_inline(num_of_requests)
870
+ @options[:max_fast_inline] = Float(num_of_requests)
871
+ end
872
+
873
+ # Specify the backend for the IO selector.
874
+ #
875
+ # Provided values will be passed directly to +NIO::Selector.new+, with the
876
+ # exception of +:auto+ which will let nio4r choose the backend.
877
+ #
878
+ # Check the documentation of +NIO::Selector.backends+ for the list of valid
879
+ # options. Note that the available options on your system will depend on the
880
+ # operating system. If you want to use the pure Ruby backend (not
881
+ # recommended due to its comparatively low performance), set environment
882
+ # variable +NIO4R_PURE+ to +true+.
883
+ #
884
+ # The default is +:auto+.
885
+ #
886
+ # @see https://github.com/socketry/nio4r/blob/master/lib/nio/selector.rb
887
+ #
888
+ def io_selector_backend(backend)
889
+ @options[:io_selector_backend] = backend.to_sym
890
+ end
813
891
  end
814
892
  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
@@ -32,7 +31,7 @@ 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
37
  # Print occured error details only if
@@ -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
 
@@ -137,10 +136,26 @@ module Puma
137
136
  register(:on_booted, &block)
138
137
  end
139
138
 
139
+ def on_restart(&block)
140
+ register(:on_restart, &block)
141
+ end
142
+
143
+ def on_stopped(&block)
144
+ register(:on_stopped, &block)
145
+ end
146
+
140
147
  def fire_on_booted!
141
148
  fire(:on_booted)
142
149
  end
143
150
 
151
+ def fire_on_restart!
152
+ fire(:on_restart)
153
+ end
154
+
155
+ def fire_on_stopped!
156
+ fire(:on_stopped)
157
+ end
158
+
144
159
  DEFAULT = new(STDOUT, STDERR)
145
160
 
146
161
  # Returns an Events object which writes its status to 2 StringIO
data/lib/puma/json.rb ADDED
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ require 'stringio'
3
+
4
+ module Puma
5
+
6
+ # Puma deliberately avoids the use of the json gem and instead performs JSON
7
+ # serialization without any external dependencies. In a puma cluster, loading
8
+ # any gem into the puma master process means that operators cannot use a
9
+ # phased restart to upgrade their application if the new version of that
10
+ # application uses a different version of that gem. The json gem in
11
+ # particular is additionally problematic because it leverages native
12
+ # extensions. If the puma master process relies on a gem with native
13
+ # extensions and operators remove gems from disk related to old releases,
14
+ # subsequent phased restarts can fail.
15
+ #
16
+ # The implementation of JSON serialization in this module is not designed to
17
+ # be particularly full-featured or fast. It just has to handle the few places
18
+ # where Puma relies on JSON serialization internally.
19
+
20
+ module JSON
21
+ QUOTE = /"/
22
+ BACKSLASH = /\\/
23
+ CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
24
+ CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
25
+
26
+ class SerializationError < StandardError; end
27
+
28
+ class << self
29
+ def generate(value)
30
+ StringIO.open do |io|
31
+ serialize_value io, value
32
+ io.string
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def serialize_value(output, value)
39
+ case value
40
+ when Hash
41
+ output << '{'
42
+ value.each_with_index do |(k, v), index|
43
+ output << ',' if index != 0
44
+ serialize_object_key output, k
45
+ output << ':'
46
+ serialize_value output, v
47
+ end
48
+ output << '}'
49
+ when Array
50
+ output << '['
51
+ value.each_with_index do |member, index|
52
+ output << ',' if index != 0
53
+ serialize_value output, member
54
+ end
55
+ output << ']'
56
+ when Integer, Float
57
+ output << value.to_s
58
+ when String
59
+ serialize_string output, value
60
+ when true
61
+ output << 'true'
62
+ when false
63
+ output << 'false'
64
+ when nil
65
+ output << 'null'
66
+ else
67
+ raise SerializationError, "Unexpected value of type #{value.class}"
68
+ end
69
+ end
70
+
71
+ def serialize_string(output, value)
72
+ output << '"'
73
+ output << value.gsub(CHAR_TO_ESCAPE) do |character|
74
+ case character
75
+ when BACKSLASH
76
+ '\\\\'
77
+ when QUOTE
78
+ '\\"'
79
+ when CONTROL_CHAR_TO_ESCAPE
80
+ '\u%.4X' % character.ord
81
+ end
82
+ end
83
+ output << '"'
84
+ end
85
+
86
+ def serialize_object_key(output, value)
87
+ case value
88
+ when Symbol, String
89
+ serialize_string output, value.to_s
90
+ else
91
+ raise SerializationError, "Could not serialize object of type #{value.class} as object key"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
data/lib/puma/launcher.rb CHANGED
@@ -58,6 +58,13 @@ module Puma
58
58
 
59
59
  @config.load
60
60
 
61
+ if @config.options[:bind_to_activated_sockets]
62
+ @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
63
+ @config.options[:binds],
64
+ @config.options[:bind_to_activated_sockets] == 'only'
65
+ )
66
+ end
67
+
61
68
  @options = @config.options
62
69
  @config.clamp
63
70
 
@@ -87,6 +94,8 @@ module Puma
87
94
  Puma.stats_object = @runner
88
95
 
89
96
  @status = :run
97
+
98
+ log_config if ENV['PUMA_LOG_CONFIG']
90
99
  end
91
100
 
92
101
  attr_reader :binder, :events, :config, :options, :restart_dir
@@ -168,6 +177,7 @@ module Puma
168
177
 
169
178
  setup_signals
170
179
  set_process_title
180
+ integrate_with_systemd
171
181
  @runner.run
172
182
 
173
183
  case @status
@@ -207,6 +217,10 @@ module Puma
207
217
  def close_binder_listeners
208
218
  @runner.close_control_listeners
209
219
  @binder.close_listeners
220
+ unless @status == :restart
221
+ log "=== puma shutdown: #{Time.now} ==="
222
+ log "- Goodbye!"
223
+ end
210
224
  end
211
225
 
212
226
  # @!attribute [r] thread_status
@@ -229,11 +243,10 @@ module Puma
229
243
  def write_pid
230
244
  path = @options[:pidfile]
231
245
  return unless path
232
-
233
- File.open(path, 'w') { |f| f.puts Process.pid }
234
- cur = Process.pid
246
+ cur_pid = Process.pid
247
+ File.write path, cur_pid, mode: 'wb:UTF-8'
235
248
  at_exit do
236
- delete_pidfile if cur == Process.pid
249
+ delete_pidfile if cur_pid == Process.pid
237
250
  end
238
251
  end
239
252
 
@@ -242,6 +255,7 @@ module Puma
242
255
  end
243
256
 
244
257
  def restart!
258
+ @events.fire_on_restart!
245
259
  @config.run_hooks :on_restart, self, @events
246
260
 
247
261
  if Puma.jruby?
@@ -316,6 +330,30 @@ module Puma
316
330
  end
317
331
  end
318
332
 
333
+ #
334
+ # Puma's systemd integration allows Puma to inform systemd:
335
+ # 1. when it has successfully started
336
+ # 2. when it is starting shutdown
337
+ # 3. periodically for a liveness check with a watchdog thread
338
+ #
339
+
340
+ def integrate_with_systemd
341
+ return unless ENV["NOTIFY_SOCKET"]
342
+
343
+ begin
344
+ require 'puma/systemd'
345
+ rescue LoadError
346
+ log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
347
+ return
348
+ end
349
+
350
+ log "* Enabling systemd notification integration"
351
+
352
+ systemd = Systemd.new(@events)
353
+ systemd.hook_events
354
+ systemd.start_watchdog
355
+ end
356
+
319
357
  def spec_for_gem(gem_name)
320
358
  Bundler.rubygems.loaded_specs(gem_name)
321
359
  end
@@ -338,9 +376,8 @@ module Puma
338
376
  end
339
377
 
340
378
  def graceful_stop
379
+ @events.fire_on_stopped!
341
380
  @runner.stop_blocked
342
- log "=== puma shutdown: #{Time.now} ==="
343
- log "- Goodbye!"
344
381
  end
345
382
 
346
383
  def set_process_title
@@ -493,5 +530,14 @@ module Puma
493
530
  Bundler.with_unbundled_env { yield }
494
531
  end
495
532
  end
533
+
534
+ def log_config
535
+ log "Configuration:"
536
+
537
+ @config.final_options
538
+ .each { |config_key, value| log "- #{config_key}: #{value}" }
539
+
540
+ log "\n"
541
+ end
496
542
  end
497
543
  end