puma 5.3.2 → 5.6.2

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 +143 -8
  3. data/LICENSE +0 -0
  4. data/README.md +47 -6
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +49 -16
  7. data/docs/compile_options.md +4 -2
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +0 -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 +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +2 -3
  20. data/docs/restart.md +6 -6
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +8 -8
  23. data/docs/systemd.md +64 -67
  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 +28 -5
  27. data/ext/puma_http11/http11_parser.c +23 -10
  28. data/ext/puma_http11/http11_parser.h +0 -0
  29. data/ext/puma_http11/http11_parser.java.rl +0 -0
  30. data/ext/puma_http11/http11_parser.rl +0 -0
  31. data/ext/puma_http11/http11_parser_common.rl +1 -1
  32. data/ext/puma_http11/mini_ssl.c +69 -9
  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 +49 -47
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +28 -43
  37. data/ext/puma_http11/puma_http11.c +1 -1
  38. data/lib/puma/app/status.rb +4 -4
  39. data/lib/puma/binder.rb +50 -5
  40. data/lib/puma/cli.rb +14 -4
  41. data/lib/puma/client.rb +50 -9
  42. data/lib/puma/cluster/worker.rb +8 -18
  43. data/lib/puma/cluster/worker_handle.rb +4 -0
  44. data/lib/puma/cluster.rb +30 -24
  45. data/lib/puma/commonlogger.rb +0 -0
  46. data/lib/puma/configuration.rb +4 -1
  47. data/lib/puma/const.rb +4 -5
  48. data/lib/puma/control_cli.rb +1 -1
  49. data/lib/puma/detect.rb +8 -2
  50. data/lib/puma/dsl.rb +105 -11
  51. data/lib/puma/error_logger.rb +0 -0
  52. data/lib/puma/events.rb +0 -0
  53. data/lib/puma/io_buffer.rb +0 -0
  54. data/lib/puma/jruby_restart.rb +0 -0
  55. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  56. data/lib/puma/launcher.rb +4 -1
  57. data/lib/puma/minissl/context_builder.rb +8 -6
  58. data/lib/puma/minissl.rb +24 -23
  59. data/lib/puma/null_io.rb +0 -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 +0 -0
  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 +0 -0
  67. data/lib/puma/request.rb +14 -9
  68. data/lib/puma/runner.rb +22 -8
  69. data/lib/puma/server.rb +32 -29
  70. data/lib/puma/single.rb +0 -0
  71. data/lib/puma/state_file.rb +41 -7
  72. data/lib/puma/systemd.rb +0 -0
  73. data/lib/puma/thread_pool.rb +7 -5
  74. data/lib/puma/util.rb +8 -1
  75. data/lib/puma.rb +2 -2
  76. data/lib/rack/handler/puma.rb +0 -0
  77. data/tools/Dockerfile +1 -1
  78. data/tools/trickletest.rb +0 -0
  79. metadata +7 -7
@@ -17,7 +17,7 @@ module Puma
17
17
  # be particularly full-featured or fast. It just has to handle the few places
18
18
  # where Puma relies on JSON serialization internally.
19
19
 
20
- module JSON
20
+ module JSONSerialization
21
21
  QUOTE = /"/
22
22
  BACKSLASH = /\\/
23
23
  CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
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,
@@ -73,7 +74,7 @@ module Puma
73
74
 
74
75
  generate_restart_data
75
76
 
76
- if clustered? && !Process.respond_to?(:fork)
77
+ if clustered? && !Puma.forkable?
77
78
  unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
78
79
  end
79
80
 
@@ -319,10 +320,12 @@ module Puma
319
320
  log '* Pruning Bundler environment'
320
321
  home = ENV['GEM_HOME']
321
322
  bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
323
+ bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
322
324
  with_unbundled_env do
323
325
  ENV['GEM_HOME'] = home
324
326
  ENV['BUNDLE_GEMFILE'] = bundle_gemfile
325
327
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
328
+ ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
326
329
  args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
327
330
  # Ruby 2.0+ defaults to true which breaks socket activation
328
331
  args += [{:close_others => false}]
@@ -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']
data/lib/puma/minissl.rb CHANGED
@@ -161,30 +161,15 @@ module Puma
161
161
  @socket.flush
162
162
  end
163
163
 
164
- def read_and_drop(timeout = 1)
165
- return :timeout unless IO.select([@socket], nil, nil, timeout)
166
- case @socket.read_nonblock(1024, exception: false)
167
- when nil
168
- :eof
169
- when :wait_readable
170
- :eagain
171
- else
172
- :drop
173
- end
174
- end
175
-
176
- def should_drop_bytes?
177
- @engine.init? || !@engine.shutdown
178
- end
179
-
180
164
  def close
181
165
  begin
182
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
183
- # Don't let this socket hold this loop forever.
184
- # If it can't send more packets within 1s, then give up.
185
- 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
186
171
  rescue IOError, SystemCallError
187
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
+ Puma::Util.purge_interrupt_queue
188
173
  # nothing
189
174
  ensure
190
175
  @socket.close
@@ -223,6 +208,10 @@ module Puma
223
208
  def initialize
224
209
  @no_tlsv1 = false
225
210
  @no_tlsv1_1 = false
211
+ @key = nil
212
+ @cert = nil
213
+ @key_pem = nil
214
+ @cert_pem = nil
226
215
  end
227
216
 
228
217
  if IS_JRUBY
@@ -245,6 +234,8 @@ module Puma
245
234
  attr_reader :key
246
235
  attr_reader :cert
247
236
  attr_reader :ca
237
+ attr_reader :cert_pem
238
+ attr_reader :key_pem
248
239
  attr_accessor :ssl_cipher_filter
249
240
  attr_accessor :verification_flags
250
241
 
@@ -263,9 +254,19 @@ module Puma
263
254
  @ca = ca
264
255
  end
265
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
+
266
267
  def check
267
- raise "Key not configured" unless @key
268
- 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?
269
270
  end
270
271
  end
271
272
 
data/lib/puma/null_io.rb CHANGED
File without changes
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)
File without changes
@@ -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
File without changes
data/lib/puma/request.rb CHANGED
@@ -51,7 +51,7 @@ module Puma
51
51
  head = env[REQUEST_METHOD] == HEAD
52
52
 
53
53
  env[RACK_INPUT] = body
54
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
54
+ env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
55
55
 
56
56
  if @early_hints
57
57
  env[EARLY_HINTS] = lambda { |headers|
@@ -167,16 +167,21 @@ module Puma
167
167
  end
168
168
 
169
169
  ensure
170
- uncork_socket io
171
-
172
- body.close
173
- client.tempfile.unlink if client.tempfile
174
- res_body.close if res_body.respond_to? :close
170
+ begin
171
+ uncork_socket io
172
+
173
+ body.close
174
+ client.tempfile.unlink if client.tempfile
175
+ ensure
176
+ # Whatever happens, we MUST call `close` on the response body.
177
+ # Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
178
+ res_body.close if res_body.respond_to? :close
179
+ end
175
180
 
176
181
  after_reply.each { |o| o.call }
177
182
  end
178
183
 
179
- return res_info[:keep_alive]
184
+ res_info[:keep_alive]
180
185
  end
181
186
 
182
187
  # @param env [Hash] see Puma::Client#env, from request
@@ -201,7 +206,7 @@ module Puma
201
206
  begin
202
207
  n = io.syswrite str
203
208
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
204
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
209
+ unless io.wait_writable WRITE_TIMEOUT
205
210
  raise ConnectionError, "Socket timeout writing data"
206
211
  end
207
212
 
@@ -419,7 +424,7 @@ module Puma
419
424
  # of concurrent connections exceeds the size of the threadpool.
420
425
  res_info[:keep_alive] &&= requests < @max_fast_inline ||
421
426
  @thread_pool.busy_threads < @max_threads ||
422
- !IO.select([client.listener], nil, nil, 0)
427
+ !client.listener.to_io.wait_readable(0)
423
428
 
424
429
  res_info[:response_hijack] = nil
425
430
 
data/lib/puma/runner.rb CHANGED
@@ -15,6 +15,16 @@ module Puma
15
15
  @app = nil
16
16
  @control = nil
17
17
  @started_at = Time.now
18
+ @wakeup = nil
19
+ end
20
+
21
+ def wakeup!
22
+ return unless @wakeup
23
+
24
+ @wakeup.write "!" unless @wakeup.closed?
25
+
26
+ rescue SystemCallError, IOError
27
+ Puma::Util.purge_interrupt_queue
18
28
  end
19
29
 
20
30
  def development?
@@ -59,7 +69,7 @@ module Puma
59
69
 
60
70
  control.binder.parse [str], self, 'Starting control server'
61
71
 
62
- control.run thread_name: 'control'
72
+ control.run thread_name: 'ctl'
63
73
  @control = control
64
74
  end
65
75
 
@@ -84,12 +94,13 @@ module Puma
84
94
  def output_header(mode)
85
95
  min_t = @options[:min_threads]
86
96
  max_t = @options[:max_threads]
97
+ environment = @options[:environment]
87
98
 
88
99
  log "Puma starting in #{mode} mode..."
89
100
  log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
90
101
  log "* Min threads: #{min_t}"
91
102
  log "* Max threads: #{max_t}"
92
- log "* Environment: #{ENV['RACK_ENV']}"
103
+ log "* Environment: #{environment}"
93
104
 
94
105
  if mode == "cluster"
95
106
  log "* Master PID: #{Process.pid}"
@@ -108,9 +119,7 @@ module Puma
108
119
  append = @options[:redirect_append]
109
120
 
110
121
  if stdout
111
- unless Dir.exist?(File.dirname(stdout))
112
- raise "Cannot redirect STDOUT to #{stdout}"
113
- end
122
+ ensure_output_directory_exists(stdout, 'STDOUT')
114
123
 
115
124
  STDOUT.reopen stdout, (append ? "a" : "w")
116
125
  STDOUT.puts "=== puma startup: #{Time.now} ==="
@@ -118,9 +127,7 @@ module Puma
118
127
  end
119
128
 
120
129
  if stderr
121
- unless Dir.exist?(File.dirname(stderr))
122
- raise "Cannot redirect STDERR to #{stderr}"
123
- end
130
+ ensure_output_directory_exists(stderr, 'STDERR')
124
131
 
125
132
  STDERR.reopen stderr, (append ? "a" : "w")
126
133
  STDERR.puts "=== puma startup: #{Time.now} ==="
@@ -159,5 +166,12 @@ module Puma
159
166
  server.inherit_binder @launcher.binder
160
167
  server
161
168
  end
169
+
170
+ private
171
+ def ensure_output_directory_exists(path, io_name)
172
+ unless Dir.exist?(File.dirname(path))
173
+ raise "Cannot redirect #{io_name} to #{path}"
174
+ end
175
+ end
162
176
  end
163
177
  end
data/lib/puma/server.rb CHANGED
@@ -14,6 +14,7 @@ require 'puma/io_buffer'
14
14
  require 'puma/request'
15
15
 
16
16
  require 'socket'
17
+ require 'io/wait'
17
18
  require 'forwardable'
18
19
 
19
20
  module Puma
@@ -145,7 +146,7 @@ module Puma
145
146
  begin
146
147
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
147
148
  rescue IOError, SystemCallError
148
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
+ Puma::Util.purge_interrupt_queue
149
150
  end
150
151
  end
151
152
 
@@ -154,7 +155,7 @@ module Puma
154
155
  begin
155
156
  skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
156
157
  rescue IOError, SystemCallError
157
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
158
+ Puma::Util.purge_interrupt_queue
158
159
  end
159
160
  end
160
161
  else
@@ -175,7 +176,7 @@ module Puma
175
176
  begin
176
177
  tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
177
178
  rescue IOError, SystemCallError
178
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
179
+ Puma::Util.purge_interrupt_queue
179
180
  @precheck_closing = false
180
181
  false
181
182
  else
@@ -219,7 +220,7 @@ module Puma
219
220
  # up in the background to handle requests. Otherwise requests
220
221
  # are handled synchronously.
221
222
  #
222
- def run(background=true, thread_name: 'server')
223
+ def run(background=true, thread_name: 'srv')
223
224
  BasicSocket.do_not_reverse_lookup = true
224
225
 
225
226
  @events.fire :state, :booting
@@ -227,6 +228,7 @@ module Puma
227
228
  @status = :run
228
229
 
229
230
  @thread_pool = ThreadPool.new(
231
+ thread_name,
230
232
  @min_threads,
231
233
  @max_threads,
232
234
  ::Puma::IOBuffer,
@@ -313,14 +315,15 @@ module Puma
313
315
  queue_requests = @queue_requests
314
316
  drain = @options[:drain_on_shutdown] ? 0 : nil
315
317
 
316
- remote_addr_value = nil
317
- remote_addr_header = nil
318
-
319
- case @options[:remote_address]
318
+ addr_send_name, addr_value = case @options[:remote_address]
320
319
  when :value
321
- remote_addr_value = @options[:remote_address_value]
320
+ [:peerip=, @options[:remote_address_value]]
322
321
  when :header
323
- remote_addr_header = @options[:remote_address_header]
322
+ [:remote_addr_header=, @options[:remote_address_header]]
323
+ when :proxy_protocol
324
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
325
+ else
326
+ [nil, nil]
324
327
  end
325
328
 
326
329
  while @status == :run || (drain && shutting_down?)
@@ -340,17 +343,16 @@ module Puma
340
343
  next
341
344
  end
342
345
  drain += 1 if shutting_down?
343
- client = Client.new io, @binder.env(sock)
344
- client.listener = sock
345
- if remote_addr_value
346
- client.peerip = remote_addr_value
347
- elsif remote_addr_header
348
- client.remote_addr_header = remote_addr_header
349
- end
350
- pool << client
346
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
347
+ c.listener = sock
348
+ c.send(addr_send_name, addr_value) if addr_value
349
+ }
351
350
  end
352
351
  end
353
- rescue Object => e
352
+ rescue IOError, Errno::EBADF
353
+ # In the case that any of the sockets are unexpectedly close.
354
+ raise
355
+ rescue StandardError => e
354
356
  @events.unknown_error e, nil, "Listen loop"
355
357
  end
356
358
  end
@@ -366,13 +368,14 @@ module Puma
366
368
  rescue Exception => e
367
369
  @events.unknown_error e, nil, "Exception handling servers"
368
370
  ensure
369
- begin
370
- @check.close unless @check.closed?
371
- rescue Errno::EBADF, RuntimeError
372
- # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
373
- # Errno::EBADF is infrequently raised
371
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
372
+ # Errno::EBADF is infrequently raised
373
+ [@check, @notify].each do |io|
374
+ begin
375
+ io.close unless io.closed?
376
+ rescue Errno::EBADF, RuntimeError
377
+ end
374
378
  end
375
- @notify.close
376
379
  @notify = nil
377
380
  @check = nil
378
381
  end
@@ -396,7 +399,7 @@ module Puma
396
399
  return true
397
400
  end
398
401
 
399
- return false
402
+ false
400
403
  end
401
404
 
402
405
  # Given a connection on +client+, handle the incoming requests,
@@ -482,7 +485,7 @@ module Puma
482
485
  begin
483
486
  client.close if close_socket
484
487
  rescue IOError, SystemCallError
485
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
488
+ Puma::Util.purge_interrupt_queue
486
489
  # Already closed
487
490
  rescue StandardError => e
488
491
  @events.unknown_error e, nil, "Client"
@@ -574,11 +577,11 @@ module Puma
574
577
  @notify << message
575
578
  rescue IOError, NoMethodError, Errno::EPIPE
576
579
  # The server, in another thread, is shutting down
577
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
580
+ Puma::Util.purge_interrupt_queue
578
581
  rescue RuntimeError => e
579
582
  # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
580
583
  if e.message.include?('IOError')
581
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
584
+ Puma::Util.purge_interrupt_queue
582
585
  else
583
586
  raise e
584
587
  end
data/lib/puma/single.rb CHANGED
File without changes
@@ -1,15 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
-
5
3
  module Puma
4
+
5
+ # Puma::Launcher uses StateFile to write a yaml file for use with Puma::ControlCLI.
6
+ #
7
+ # In previous versions of Puma, YAML was used to read/write the state file.
8
+ # Since Puma is similar to Bundler/RubyGems in that it may load before one's app
9
+ # does, minimizing the dependencies that may be shared with the app is desired.
10
+ #
11
+ # At present, it only works with numeric and string values. It is still a valid
12
+ # yaml file, and the CI tests parse it with Psych.
13
+ #
6
14
  class StateFile
15
+
16
+ ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
17
+
18
+ # @deprecated 6.0.0
19
+ FIELDS = ALLOWED_FIELDS
20
+
7
21
  def initialize
8
22
  @options = {}
9
23
  end
10
24
 
11
25
  def save(path, permission = nil)
12
- contents =YAML.dump @options
26
+ contents = "---\n".dup
27
+ @options.each do |k,v|
28
+ next unless ALLOWED_FIELDS.include? k
29
+ case v
30
+ when Numeric
31
+ contents << "#{k}: #{v}\n"
32
+ when String
33
+ next if v.strip.empty?
34
+ contents << (k == 'running_from' || v.to_s.include?(' ') ?
35
+ "#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
36
+ end
37
+ end
13
38
  if permission
14
39
  File.write path, contents, mode: 'wb:UTF-8'
15
40
  else
@@ -18,12 +43,21 @@ module Puma
18
43
  end
19
44
 
20
45
  def load(path)
21
- @options = YAML.load File.read(path)
46
+ File.read(path).lines.each do |line|
47
+ next if line.start_with? '#'
48
+ k,v = line.split ':', 2
49
+ next unless v && ALLOWED_FIELDS.include?(k)
50
+ v = v.strip
51
+ @options[k] =
52
+ case v
53
+ when /\A\d+\z/ then v.to_i
54
+ when /\A\d+\.\d+\z/ then v.to_f
55
+ else v.gsub(/\A"|"\z/, '')
56
+ end
57
+ end
22
58
  end
23
59
 
24
- FIELDS = %w!control_url control_auth_token pid running_from!
25
-
26
- FIELDS.each do |f|
60
+ ALLOWED_FIELDS.each do |f|
27
61
  define_method f do
28
62
  @options[f]
29
63
  end
data/lib/puma/systemd.rb CHANGED
File without changes
@@ -29,7 +29,7 @@ module Puma
29
29
  # The block passed is the work that will be performed in each
30
30
  # thread.
31
31
  #
32
- def initialize(min, max, *extra, &block)
32
+ def initialize(name, min, max, *extra, &block)
33
33
  @not_empty = ConditionVariable.new
34
34
  @not_full = ConditionVariable.new
35
35
  @mutex = Mutex.new
@@ -39,6 +39,7 @@ module Puma
39
39
  @spawned = 0
40
40
  @waiting = 0
41
41
 
42
+ @name = name
42
43
  @min = Integer(min)
43
44
  @max = Integer(max)
44
45
  @block = block
@@ -71,7 +72,7 @@ module Puma
71
72
  attr_accessor :out_of_band_hook # @version 5.0.0
72
73
 
73
74
  def self.clean_thread_locals
74
- Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
75
+ Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
75
76
  Thread.current[key] = nil unless key == :__recursive_key__
76
77
  end
77
78
  end
@@ -101,7 +102,7 @@ module Puma
101
102
  @spawned += 1
102
103
 
103
104
  th = Thread.new(@spawned) do |spawned|
104
- Puma.set_thread_name 'threadpool %03i' % spawned
105
+ Puma.set_thread_name '%s tp %03i' % [@name, spawned]
105
106
  todo = @todo
106
107
  block = @block
107
108
  mutex = @mutex
@@ -119,6 +120,7 @@ module Puma
119
120
  @trim_requested -= 1
120
121
  @spawned -= 1
121
122
  @workers.delete th
123
+ not_full.signal
122
124
  Thread.exit
123
125
  end
124
126
 
@@ -318,12 +320,12 @@ module Puma
318
320
  end
319
321
 
320
322
  def auto_trim!(timeout=30)
321
- @auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
323
+ @auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
322
324
  @auto_trim.start!
323
325
  end
324
326
 
325
327
  def auto_reap!(timeout=5)
326
- @reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
328
+ @reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
327
329
  @reaper.start!
328
330
  end
329
331
 
data/lib/puma/util.rb CHANGED
@@ -10,6 +10,13 @@ module Puma
10
10
  IO.pipe
11
11
  end
12
12
 
13
+ # An instance method on Thread has been provided to address https://bugs.ruby-lang.org/issues/13632,
14
+ # which currently effects some older versions of Ruby: 2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1
15
+ # Additional context: https://github.com/puma/puma/pull/1345
16
+ def purge_interrupt_queue
17
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
18
+ end
19
+
13
20
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
14
21
  # target encoding of the string returned, and it defaults to UTF-8
15
22
  if defined?(::Encoding)
@@ -61,7 +68,7 @@ module Puma
61
68
  end
62
69
  end
63
70
 
64
- return params
71
+ params
65
72
  end
66
73
 
67
74
  # A case-insensitive Hash that preserves the original case of a
data/lib/puma.rb CHANGED
@@ -12,7 +12,7 @@ require 'thread'
12
12
 
13
13
  require 'puma/puma_http11'
14
14
  require 'puma/detect'
15
- require 'puma/json'
15
+ require 'puma/json_serialization'
16
16
 
17
17
  module Puma
18
18
  autoload :Const, 'puma/const'
@@ -60,7 +60,7 @@ module Puma
60
60
 
61
61
  # @!attribute [rw] stats_object
62
62
  def self.stats
63
- Puma::JSON.generate @get_stats.stats
63
+ Puma::JSONSerialization.generate @get_stats.stats
64
64
  end
65
65
 
66
66
  # @!attribute [r] stats_hash
File without changes
data/tools/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use this Dockerfile to create minimal reproductions of issues
2
2
 
3
- FROM ruby:2.6
3
+ FROM ruby:3.1
4
4
 
5
5
  # throw errors if Gemfile has been modified since Gemfile.lock
6
6
  RUN bundle config --global frozen 1
data/tools/trickletest.rb CHANGED
File without changes