puma 5.0.4 → 5.6.4

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 (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
data/lib/puma/launcher.rb CHANGED
@@ -15,6 +15,7 @@ module Puma
15
15
  # It is responsible for either launching a cluster of Puma workers or a single
16
16
  # puma server.
17
17
  class Launcher
18
+ # @deprecated 6.0.0
18
19
  KEYS_NOT_TO_PERSIST_IN_STATE = [
19
20
  :logger, :lowlevel_error_handler,
20
21
  :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
@@ -58,6 +59,13 @@ module Puma
58
59
 
59
60
  @config.load
60
61
 
62
+ if @config.options[:bind_to_activated_sockets]
63
+ @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
64
+ @config.options[:binds],
65
+ @config.options[:bind_to_activated_sockets] == 'only'
66
+ )
67
+ end
68
+
61
69
  @options = @config.options
62
70
  @config.clamp
63
71
 
@@ -66,7 +74,7 @@ module Puma
66
74
 
67
75
  generate_restart_data
68
76
 
69
- if clustered? && !Process.respond_to?(:fork)
77
+ if clustered? && !Puma.forkable?
70
78
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
71
79
  end
72
80
 
@@ -87,6 +95,8 @@ module Puma
87
95
  Puma.stats_object = @runner
88
96
 
89
97
  @status = :run
98
+
99
+ log_config if ENV['PUMA_LOG_CONFIG']
90
100
  end
91
101
 
92
102
  attr_reader :binder, :events, :config, :options, :restart_dir
@@ -168,6 +178,7 @@ module Puma
168
178
 
169
179
  setup_signals
170
180
  set_process_title
181
+ integrate_with_systemd
171
182
  @runner.run
172
183
 
173
184
  case @status
@@ -207,6 +218,10 @@ module Puma
207
218
  def close_binder_listeners
208
219
  @runner.close_control_listeners
209
220
  @binder.close_listeners
221
+ unless @status == :restart
222
+ log "=== puma shutdown: #{Time.now} ==="
223
+ log "- Goodbye!"
224
+ end
210
225
  end
211
226
 
212
227
  # @!attribute [r] thread_status
@@ -229,11 +244,10 @@ module Puma
229
244
  def write_pid
230
245
  path = @options[:pidfile]
231
246
  return unless path
232
-
233
- File.open(path, 'w') { |f| f.puts Process.pid }
234
- cur = Process.pid
247
+ cur_pid = Process.pid
248
+ File.write path, cur_pid, mode: 'wb:UTF-8'
235
249
  at_exit do
236
- delete_pidfile if cur == Process.pid
250
+ delete_pidfile if cur_pid == Process.pid
237
251
  end
238
252
  end
239
253
 
@@ -242,6 +256,7 @@ module Puma
242
256
  end
243
257
 
244
258
  def restart!
259
+ @events.fire_on_restart!
245
260
  @config.run_hooks :on_restart, self, @events
246
261
 
247
262
  if Puma.jruby?
@@ -305,10 +320,12 @@ module Puma
305
320
  log '* Pruning Bundler environment'
306
321
  home = ENV['GEM_HOME']
307
322
  bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
323
+ bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
308
324
  with_unbundled_env do
309
325
  ENV['GEM_HOME'] = home
310
326
  ENV['BUNDLE_GEMFILE'] = bundle_gemfile
311
327
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
328
+ ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
312
329
  args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
313
330
  # Ruby 2.0+ defaults to true which breaks socket activation
314
331
  args += [{:close_others => false}]
@@ -316,6 +333,30 @@ module Puma
316
333
  end
317
334
  end
318
335
 
336
+ #
337
+ # Puma's systemd integration allows Puma to inform systemd:
338
+ # 1. when it has successfully started
339
+ # 2. when it is starting shutdown
340
+ # 3. periodically for a liveness check with a watchdog thread
341
+ #
342
+
343
+ def integrate_with_systemd
344
+ return unless ENV["NOTIFY_SOCKET"]
345
+
346
+ begin
347
+ require 'puma/systemd'
348
+ rescue LoadError
349
+ log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
350
+ return
351
+ end
352
+
353
+ log "* Enabling systemd notification integration"
354
+
355
+ systemd = Systemd.new(@events)
356
+ systemd.hook_events
357
+ systemd.start_watchdog
358
+ end
359
+
319
360
  def spec_for_gem(gem_name)
320
361
  Bundler.rubygems.loaded_specs(gem_name)
321
362
  end
@@ -338,9 +379,8 @@ module Puma
338
379
  end
339
380
 
340
381
  def graceful_stop
382
+ @events.fire_on_stopped!
341
383
  @runner.stop_blocked
342
- log "=== puma shutdown: #{Time.now} ==="
343
- log "- Goodbye!"
344
384
  end
345
385
 
346
386
  def set_process_title
@@ -493,5 +533,14 @@ module Puma
493
533
  Bundler.with_unbundled_env { yield }
494
534
  end
495
535
  end
536
+
537
+ def log_config
538
+ log "Configuration:"
539
+
540
+ @config.final_options
541
+ .each { |config_key, value| log "- #{config_key}: #{value}" }
542
+
543
+ log "\n"
544
+ end
496
545
  end
497
546
  end
@@ -23,17 +23,19 @@ module Puma
23
23
  ctx.keystore_pass = params['keystore-pass']
24
24
  ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
25
25
  else
26
- unless params['key']
27
- events.error "Please specify the SSL key via 'key='"
26
+ if params['key'].nil? && params['key_pem'].nil?
27
+ events.error "Please specify the SSL key via 'key=' or 'key_pem='"
28
28
  end
29
29
 
30
- ctx.key = params['key']
30
+ ctx.key = params['key'] if params['key']
31
+ ctx.key_pem = params['key_pem'] if params['key_pem']
31
32
 
32
- unless params['cert']
33
- events.error "Please specify the SSL cert via 'cert='"
33
+ if params['cert'].nil? && params['cert_pem'].nil?
34
+ events.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
34
35
  end
35
36
 
36
- ctx.cert = params['cert']
37
+ ctx.cert = params['cert'] if params['cert']
38
+ ctx.cert_pem = params['cert_pem'] if params['cert_pem']
37
39
 
38
40
  if ['peer', 'force_peer'].include?(params['verify_mode'])
39
41
  unless params['ca']
@@ -62,6 +64,12 @@ module Puma
62
64
  end
63
65
  end
64
66
 
67
+ if params['verification_flags']
68
+ ctx.verification_flags = params['verification_flags'].split(',').
69
+ map { |flag| MiniSSL::VERIFICATION_FLAGS.fetch(flag) }.
70
+ inject { |sum, flag| sum ? sum | flag : flag }
71
+ end
72
+
65
73
  ctx
66
74
  end
67
75
 
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
 
@@ -160,30 +161,15 @@ module Puma
160
161
  @socket.flush
161
162
  end
162
163
 
163
- def read_and_drop(timeout = 1)
164
- return :timeout unless IO.select([@socket], nil, nil, timeout)
165
- case @socket.read_nonblock(1024, exception: false)
166
- when nil
167
- :eof
168
- when :wait_readable
169
- :eagain
170
- else
171
- :drop
172
- end
173
- end
174
-
175
- def should_drop_bytes?
176
- @engine.init? || !@engine.shutdown
177
- end
178
-
179
164
  def close
180
165
  begin
181
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
182
- # Don't let this socket hold this loop forever.
183
- # If it can't send more packets within 1s, then give up.
184
- return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
166
+ unless @engine.shutdown
167
+ while alert_data = @engine.extract
168
+ @socket.write alert_data
169
+ end
170
+ end
185
171
  rescue IOError, SystemCallError
186
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
+ Puma::Util.purge_interrupt_queue
187
173
  # nothing
188
174
  ensure
189
175
  @socket.close
@@ -222,6 +208,10 @@ module Puma
222
208
  def initialize
223
209
  @no_tlsv1 = false
224
210
  @no_tlsv1_1 = false
211
+ @key = nil
212
+ @cert = nil
213
+ @key_pem = nil
214
+ @cert_pem = nil
225
215
  end
226
216
 
227
217
  if IS_JRUBY
@@ -244,7 +234,10 @@ module Puma
244
234
  attr_reader :key
245
235
  attr_reader :cert
246
236
  attr_reader :ca
237
+ attr_reader :cert_pem
238
+ attr_reader :key_pem
247
239
  attr_accessor :ssl_cipher_filter
240
+ attr_accessor :verification_flags
248
241
 
249
242
  def key=(key)
250
243
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -261,9 +254,19 @@ module Puma
261
254
  @ca = ca
262
255
  end
263
256
 
257
+ def cert_pem=(cert_pem)
258
+ raise ArgumentError, "'cert_pem' is not a String" unless cert_pem.is_a? String
259
+ @cert_pem = cert_pem
260
+ end
261
+
262
+ def key_pem=(key_pem)
263
+ raise ArgumentError, "'key_pem' is not a String" unless key_pem.is_a? String
264
+ @key_pem = key_pem
265
+ end
266
+
264
267
  def check
265
- raise "Key not configured" unless @key
266
- raise "Cert not configured" unless @cert
268
+ raise "Key not configured" if @key.nil? && @key_pem.nil?
269
+ raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
267
270
  end
268
271
  end
269
272
 
@@ -287,33 +290,58 @@ module Puma
287
290
  VERIFY_PEER = 1
288
291
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
289
292
 
293
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
294
+ # /* Certificate verify flags */
295
+ VERIFICATION_FLAGS = {
296
+ "USE_CHECK_TIME" => 0x2,
297
+ "CRL_CHECK" => 0x4,
298
+ "CRL_CHECK_ALL" => 0x8,
299
+ "IGNORE_CRITICAL" => 0x10,
300
+ "X509_STRICT" => 0x20,
301
+ "ALLOW_PROXY_CERTS" => 0x40,
302
+ "POLICY_CHECK" => 0x80,
303
+ "EXPLICIT_POLICY" => 0x100,
304
+ "INHIBIT_ANY" => 0x200,
305
+ "INHIBIT_MAP" => 0x400,
306
+ "NOTIFY_POLICY" => 0x800,
307
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
308
+ "USE_DELTAS" => 0x2000,
309
+ "CHECK_SS_SIGNATURE" => 0x4000,
310
+ "TRUSTED_FIRST" => 0x8000,
311
+ "SUITEB_128_LOS_ONLY" => 0x10000,
312
+ "SUITEB_192_LOS" => 0x20000,
313
+ "SUITEB_128_LOS" => 0x30000,
314
+ "PARTIAL_CHAIN" => 0x80000,
315
+ "NO_ALT_CHAINS" => 0x100000,
316
+ "NO_CHECK_TIME" => 0x200000
317
+ }.freeze
318
+
290
319
  class Server
291
320
  def initialize(socket, ctx)
292
321
  @socket = socket
293
322
  @ctx = ctx
294
- end
295
-
296
- # @!attribute [r] to_io
297
- def to_io
298
- @socket
323
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
299
324
  end
300
325
 
301
326
  def accept
302
327
  @ctx.check
303
328
  io = @socket.accept
304
- engine = Engine.server @ctx
305
-
329
+ engine = Engine.server @eng_ctx
306
330
  Socket.new io, engine
307
331
  end
308
332
 
309
333
  def accept_nonblock
310
334
  @ctx.check
311
335
  io = @socket.accept_nonblock
312
- engine = Engine.server @ctx
313
-
336
+ engine = Engine.server @eng_ctx
314
337
  Socket.new io, engine
315
338
  end
316
339
 
340
+ # @!attribute [r] to_io
341
+ def to_io
342
+ @socket
343
+ end
344
+
317
345
  # @!attribute [r] addr
318
346
  # @version 5.0.0
319
347
  def addr
@@ -323,6 +351,10 @@ module Puma
323
351
  def close
324
352
  @socket.close unless @socket.closed? # closed? call is for Windows
325
353
  end
354
+
355
+ def closed?
356
+ @socket.closed?
357
+ end
326
358
  end
327
359
  end
328
360
  end
data/lib/puma/null_io.rb CHANGED
@@ -9,6 +9,10 @@ module Puma
9
9
  nil
10
10
  end
11
11
 
12
+ def string
13
+ ""
14
+ end
15
+
12
16
  def each
13
17
  end
14
18
 
@@ -32,6 +36,10 @@ module Puma
32
36
  true
33
37
  end
34
38
 
39
+ def sync
40
+ true
41
+ end
42
+
35
43
  def sync=(v)
36
44
  end
37
45
 
@@ -40,5 +48,9 @@ module Puma
40
48
 
41
49
  def write(*ary)
42
50
  end
51
+
52
+ def flush
53
+ self
54
+ end
43
55
  end
44
56
  end
File without changes
data/lib/puma/plugin.rb CHANGED
@@ -64,7 +64,7 @@ module Puma
64
64
  def fire_background
65
65
  @background.each_with_index do |b, i|
66
66
  Thread.new do
67
- Puma.set_thread_name "plugin background #{i}"
67
+ Puma.set_thread_name "plgn bg #{i}"
68
68
  b.call
69
69
  end
70
70
  end
@@ -91,7 +91,7 @@ module Puma
91
91
  path = ary.first[CALLER_FILE]
92
92
 
93
93
  m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
94
- return m[1]
94
+ m[1]
95
95
  end
96
96
 
97
97
  def self.create(&blk)
@@ -5,22 +5,22 @@ module Puma
5
5
  # Add a simple implementation for earlier Ruby versions.
6
6
  #
7
7
  module QueueClose
8
- def initialize
9
- @closed = false
10
- super
11
- end
12
8
  def close
9
+ num_waiting.times {push nil}
13
10
  @closed = true
14
11
  end
15
12
  def closed?
16
- @closed
13
+ @closed ||= false
17
14
  end
18
15
  def push(object)
19
- @closed ||= false
20
- raise ClosedQueueError if @closed
16
+ raise ClosedQueueError if closed?
21
17
  super
22
18
  end
23
19
  alias << push
20
+ def pop(non_block=false)
21
+ return nil if !non_block && closed? && empty?
22
+ super
23
+ end
24
24
  end
25
25
  ::Queue.prepend QueueClose
26
26
  end
@@ -165,7 +165,7 @@ module Puma::Rack
165
165
  require config
166
166
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
167
167
  end
168
- return app, options
168
+ [app, options]
169
169
  end
170
170
 
171
171
  def self.new_from_string(builder_script, file="(rackup)")
File without changes
File without changes
data/lib/puma/reactor.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'puma/queue_close' unless ::Queue.instance_methods.include? :close
4
4
 
5
5
  module Puma
6
+ class UnsupportedBackend < StandardError; end
7
+
6
8
  # Monitors a collection of IO objects, calling a block whenever
7
9
  # any monitored object either receives data or times out, or when the Reactor shuts down.
8
10
  #
@@ -18,9 +20,12 @@ module Puma
18
20
  # Create a new Reactor to monitor IO objects added by #add.
19
21
  # The provided block will be invoked when an IO has data available to read,
20
22
  # its timeout elapses, or when the Reactor shuts down.
21
- def initialize(&block)
23
+ def initialize(backend, &block)
22
24
  require 'nio'
23
- @selector = NIO::Selector.new
25
+ unless backend == :auto || NIO::Selector.backends.include?(backend)
26
+ raise "unsupported IO selector backend: #{backend} (available backends: #{NIO::Selector.backends.join(', ')})"
27
+ end
28
+ @selector = backend == :auto ? NIO::Selector.new : NIO::Selector.new(backend)
24
29
  @input = Queue.new
25
30
  @timeouts = []
26
31
  @block = block
@@ -38,11 +43,11 @@ module Puma
38
43
  end
39
44
  end
40
45
 
41
- # Add a new IO object to monitor.
46
+ # Add a new client to monitor.
42
47
  # The object must respond to #timeout and #timeout_at.
43
48
  # Returns false if the reactor is already shut down.
44
- def add(io)
45
- @input << io
49
+ def add(client)
50
+ @input << client
46
51
  @selector.wakeup
47
52
  true
48
53
  rescue ClosedQueueError
@@ -92,17 +97,19 @@ module Puma
92
97
  end
93
98
 
94
99
  # Start monitoring the object.
95
- def register(io)
96
- @selector.register(io, :r).value = io
97
- @timeouts << io
100
+ def register(client)
101
+ @selector.register(client.to_io, :r).value = client
102
+ @timeouts << client
103
+ rescue ArgumentError
104
+ # unreadable clients raise error when processed by NIO
98
105
  end
99
106
 
100
107
  # 'Wake up' a monitored object by calling the provided block.
101
108
  # Stop monitoring the object if the block returns `true`.
102
- def wakeup!(io)
103
- if @block.call(io)
104
- @selector.deregister(io)
105
- @timeouts.delete(io)
109
+ def wakeup!(client)
110
+ if @block.call client
111
+ @selector.deregister client.to_io
112
+ @timeouts.delete client
106
113
  end
107
114
  end
108
115
  end