puma 6.4.3 → 8.0.2

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +448 -8
  3. data/README.md +110 -51
  4. data/docs/5.0-Upgrade.md +98 -0
  5. data/docs/6.0-Upgrade.md +56 -0
  6. data/docs/7.0-Upgrade.md +52 -0
  7. data/docs/8.0-Upgrade.md +100 -0
  8. data/docs/deployment.md +58 -23
  9. data/docs/fork_worker.md +11 -1
  10. data/docs/grpc.md +62 -0
  11. data/docs/images/favicon.svg +1 -0
  12. data/docs/images/running-puma.svg +1 -0
  13. data/docs/images/standard-logo.svg +1 -0
  14. data/docs/java_options.md +54 -0
  15. data/docs/jungle/README.md +1 -1
  16. data/docs/kubernetes.md +11 -16
  17. data/docs/plugins.md +6 -2
  18. data/docs/restart.md +2 -2
  19. data/docs/signals.md +21 -21
  20. data/docs/stats.md +11 -5
  21. data/docs/systemd.md +14 -5
  22. data/ext/puma_http11/extconf.rb +20 -32
  23. data/ext/puma_http11/http11_parser.java.rl +51 -65
  24. data/ext/puma_http11/mini_ssl.c +29 -9
  25. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  26. data/ext/puma_http11/org/jruby/puma/Http11.java +194 -101
  27. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
  28. data/ext/puma_http11/puma_http11.c +125 -118
  29. data/lib/puma/app/status.rb +11 -3
  30. data/lib/puma/binder.rb +22 -12
  31. data/lib/puma/cli.rb +11 -9
  32. data/lib/puma/client.rb +233 -136
  33. data/lib/puma/client_env.rb +171 -0
  34. data/lib/puma/cluster/worker.rb +24 -21
  35. data/lib/puma/cluster/worker_handle.rb +38 -8
  36. data/lib/puma/cluster.rb +74 -48
  37. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  38. data/lib/puma/commonlogger.rb +3 -3
  39. data/lib/puma/configuration.rb +197 -64
  40. data/lib/puma/const.rb +23 -12
  41. data/lib/puma/control_cli.rb +11 -7
  42. data/lib/puma/detect.rb +13 -0
  43. data/lib/puma/dsl.rb +483 -127
  44. data/lib/puma/error_logger.rb +7 -5
  45. data/lib/puma/events.rb +25 -10
  46. data/lib/puma/io_buffer.rb +8 -4
  47. data/lib/puma/jruby_restart.rb +0 -16
  48. data/lib/puma/launcher/bundle_pruner.rb +3 -5
  49. data/lib/puma/launcher.rb +76 -59
  50. data/lib/puma/log_writer.rb +17 -11
  51. data/lib/puma/minissl/context_builder.rb +1 -0
  52. data/lib/puma/minissl.rb +1 -1
  53. data/lib/puma/null_io.rb +26 -0
  54. data/lib/puma/plugin/systemd.rb +3 -3
  55. data/lib/puma/rack/urlmap.rb +1 -1
  56. data/lib/puma/reactor.rb +19 -13
  57. data/lib/puma/{request.rb → response.rb} +57 -209
  58. data/lib/puma/runner.rb +15 -17
  59. data/lib/puma/sd_notify.rb +1 -4
  60. data/lib/puma/server.rb +200 -104
  61. data/lib/puma/server_plugin_control.rb +32 -0
  62. data/lib/puma/single.rb +7 -4
  63. data/lib/puma/state_file.rb +3 -2
  64. data/lib/puma/thread_pool.rb +179 -96
  65. data/lib/puma/util.rb +0 -7
  66. data/lib/puma.rb +10 -0
  67. data/lib/rack/handler/puma.rb +11 -8
  68. data/tools/Dockerfile +15 -5
  69. metadata +26 -16
  70. data/ext/puma_http11/ext_help.h +0 -15
@@ -3,8 +3,13 @@
3
3
  require 'thread'
4
4
 
5
5
  require_relative 'io_buffer'
6
+ require_relative 'server_plugin_control'
6
7
 
7
8
  module Puma
9
+
10
+ # Add `Thread#puma_server` and `Thread#puma_server=`
11
+ Thread.attr_accessor(:puma_server)
12
+
8
13
  # Internal Docs for A simple thread pool management object.
9
14
  #
10
15
  # Each Puma "worker" has a thread pool to process requests.
@@ -20,37 +25,90 @@ module Puma
20
25
  class ForceShutdown < RuntimeError
21
26
  end
22
27
 
28
+ class ProcessorThread
29
+ attr_accessor :thread
30
+ attr_writer :marked_as_io_thread
31
+
32
+ def initialize(pool)
33
+ @pool = pool
34
+ @thread = nil
35
+ @marked_as_io_thread = false
36
+ end
37
+
38
+ def mark_as_io_thread!
39
+ unless @marked_as_io_thread
40
+ @marked_as_io_thread = true
41
+
42
+ # Immediately signal the pool that it can spawn a new thread
43
+ # if there's some work in the queue.
44
+ @pool.spawn_thread_if_needed
45
+ end
46
+ end
47
+
48
+ def marked_as_io_thread?
49
+ @marked_as_io_thread
50
+ end
51
+
52
+ def alive?
53
+ @thread&.alive?
54
+ end
55
+
56
+ def join(...)
57
+ @thread.join(...)
58
+ end
59
+
60
+ def kill(...)
61
+ @thread.kill(...)
62
+ end
63
+
64
+ def [](key)
65
+ @thread[key]
66
+ end
67
+
68
+ def raise(...)
69
+ @thread.raise(...)
70
+ end
71
+ end
72
+
23
73
  # How long, after raising the ForceShutdown of a thread during
24
74
  # forced shutdown mode, to wait for the thread to try and finish
25
75
  # up its work before leaving the thread to die on the vine.
26
76
  SHUTDOWN_GRACE_TIME = 5 # seconds
27
77
 
78
+ attr_reader :out_of_band_running
79
+
28
80
  # Maintain a minimum of +min+ and maximum of +max+ threads
29
81
  # in the pool.
30
82
  #
31
83
  # The block passed is the work that will be performed in each
32
84
  # thread.
33
85
  #
34
- def initialize(name, options = {}, &block)
86
+ def initialize(name, options = {}, server: nil, &block)
87
+ @server = server
88
+
35
89
  @not_empty = ConditionVariable.new
36
90
  @not_full = ConditionVariable.new
37
91
  @mutex = Mutex.new
92
+ @todo = Queue.new
38
93
 
39
- @todo = []
40
-
94
+ @backlog_max = 0
41
95
  @spawned = 0
42
96
  @waiting = 0
43
97
 
44
98
  @name = name
45
99
  @min = Integer(options[:min_threads])
46
100
  @max = Integer(options[:max_threads])
101
+ @max_io_threads = Integer(options[:max_io_threads] || 0)
102
+
47
103
  # Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
48
104
  # to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
49
105
  # makes stubbing constants difficult.
50
106
  @shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
107
+ @shutdown_debug = options[:shutdown_debug]
51
108
  @block = block
52
109
  @out_of_band = options[:out_of_band]
53
- @clean_thread_locals = options[:clean_thread_locals]
110
+ @out_of_band_running = false
111
+ @out_of_band_condvar = ConditionVariable.new
54
112
  @before_thread_start = options[:before_thread_start]
55
113
  @before_thread_exit = options[:before_thread_exit]
56
114
  @reaping_time = options[:reaping_time]
@@ -61,7 +119,7 @@ module Puma
61
119
  @trim_requested = 0
62
120
  @out_of_band_pending = false
63
121
 
64
- @workers = []
122
+ @processors = []
65
123
 
66
124
  @auto_trim = nil
67
125
  @reaper = nil
@@ -78,22 +136,43 @@ module Puma
78
136
  end
79
137
 
80
138
  attr_reader :spawned, :trim_requested, :waiting
139
+ attr_accessor :min, :max
81
140
 
82
- def self.clean_thread_locals
83
- Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
84
- Thread.current[key] = nil unless key == :__recursive_key__
141
+ # generate stats hash so as not to perform multiple locks
142
+ # @return [Hash] hash containing stat info from ThreadPool
143
+ def stats
144
+ with_mutex do
145
+ temp = @backlog_max
146
+ @backlog_max = 0
147
+ { backlog: @todo.size,
148
+ running: @spawned,
149
+ pool_capacity: pool_capacity,
150
+ busy_threads: @spawned - @waiting + @todo.size,
151
+ io_threads: @processors.count(&:marked_as_io_thread?),
152
+ backlog_max: temp
153
+ }
85
154
  end
86
155
  end
87
156
 
157
+ def reset_max
158
+ with_mutex { @backlog_max = 0 }
159
+ end
160
+
88
161
  # How many objects have yet to be processed by the pool?
89
162
  #
90
163
  def backlog
91
164
  with_mutex { @todo.size }
92
165
  end
93
166
 
167
+ # The maximum size of the backlog
168
+ #
169
+ def backlog_max
170
+ with_mutex { @backlog_max }
171
+ end
172
+
94
173
  # @!attribute [r] pool_capacity
95
174
  def pool_capacity
96
- waiting + (@max - spawned)
175
+ (waiting + (@max - spawned)).clamp(0, Float::INFINITY)
97
176
  end
98
177
 
99
178
  # @!attribute [r] busy_threads
@@ -110,8 +189,12 @@ module Puma
110
189
  @spawned += 1
111
190
 
112
191
  trigger_before_thread_start_hooks
113
- th = Thread.new(@spawned) do |spawned|
192
+ processor = ProcessorThread.new(self)
193
+ processor.thread = Thread.new(processor, @spawned) do |processor, spawned|
114
194
  Puma.set_thread_name '%s tp %03i' % [@name, spawned]
195
+ # Advertise server into the thread
196
+ Thread.current.puma_server = @server
197
+
115
198
  todo = @todo
116
199
  block = @block
117
200
  mutex = @mutex
@@ -122,11 +205,23 @@ module Puma
122
205
  work = nil
123
206
 
124
207
  mutex.synchronize do
208
+ if processor.marked_as_io_thread?
209
+ if @processors.count { |t| !t.marked_as_io_thread? } < @max
210
+ # We're not at max processor threads, so the io thread can rejoin the normal population.
211
+ processor.marked_as_io_thread = false
212
+ else
213
+ # We're already at max threads, so we exit the extra io thread.
214
+ @processors.delete(processor)
215
+ trigger_before_thread_exit_hooks
216
+ Thread.exit
217
+ end
218
+ end
219
+
125
220
  while todo.empty?
126
221
  if @trim_requested > 0
127
222
  @trim_requested -= 1
128
223
  @spawned -= 1
129
- @workers.delete th
224
+ @processors.delete(processor)
130
225
  not_full.signal
131
226
  trigger_before_thread_exit_hooks
132
227
  Thread.exit
@@ -147,21 +242,17 @@ module Puma
147
242
  work = todo.shift
148
243
  end
149
244
 
150
- if @clean_thread_locals
151
- ThreadPool.clean_thread_locals
152
- end
153
-
154
245
  begin
155
- @out_of_band_pending = true if block.call(work)
246
+ @out_of_band_pending = true if block.call(processor, work)
156
247
  rescue Exception => e
157
248
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
158
249
  end
159
250
  end
160
251
  end
161
252
 
162
- @workers << th
253
+ @processors << processor
163
254
 
164
- th
255
+ processor
165
256
  end
166
257
 
167
258
  private :spawn_thread
@@ -171,7 +262,7 @@ module Puma
171
262
 
172
263
  @before_thread_start.each do |b|
173
264
  begin
174
- b.call
265
+ b[:block].call(ServerPluginControl.new(@server))
175
266
  rescue Exception => e
176
267
  STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
177
268
  end
@@ -186,7 +277,7 @@ module Puma
186
277
 
187
278
  @before_thread_exit.each do |b|
188
279
  begin
189
- b.call
280
+ b[:block].call
190
281
  rescue Exception => e
191
282
  STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
192
283
  end
@@ -202,16 +293,27 @@ module Puma
202
293
 
203
294
  # we execute on idle hook when all threads are free
204
295
  return false unless @spawned == @waiting
205
-
206
- @out_of_band.each(&:call)
296
+ @out_of_band_running = true
297
+ @out_of_band.each { |b| b[:block].call }
207
298
  true
208
299
  rescue Exception => e
209
300
  STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
210
301
  true
302
+ ensure
303
+ @out_of_band_running = false
304
+ @out_of_band_condvar.broadcast
211
305
  end
212
306
 
213
307
  private :trigger_out_of_band_hook
214
308
 
309
+ def wait_while_out_of_band_running
310
+ return unless @out_of_band_running
311
+
312
+ with_mutex do
313
+ @out_of_band_condvar.wait(@mutex) while @out_of_band_running
314
+ end
315
+ end
316
+
215
317
  # @version 5.0.0
216
318
  def with_mutex(&block)
217
319
  @mutex.owned? ?
@@ -219,6 +321,16 @@ module Puma
219
321
  @mutex.synchronize(&block)
220
322
  end
221
323
 
324
+ # :nodoc:
325
+ #
326
+ # Must be called with @mutex held!
327
+ #
328
+ def can_spawn_processor?
329
+ io_processors_count = @processors.count(&:marked_as_io_thread?)
330
+ extra_io_processors_count = io_processors_count > @max_io_threads ? io_processors_count - @max_io_threads : 0
331
+ (@spawned - io_processors_count) < (@max - extra_io_processors_count)
332
+ end
333
+
222
334
  # Add +work+ to the todo list for a Thread to pickup and process.
223
335
  def <<(work)
224
336
  with_mutex do
@@ -227,78 +339,26 @@ module Puma
227
339
  end
228
340
 
229
341
  @todo << work
342
+ t = @todo.size
343
+ @backlog_max = t if t > @backlog_max
230
344
 
231
- if @waiting < @todo.size and @spawned < @max
345
+ if @waiting < @todo.size and can_spawn_processor?
232
346
  spawn_thread
233
347
  end
234
348
 
235
349
  @not_empty.signal
236
350
  end
351
+ self
237
352
  end
238
353
 
239
- # This method is used by `Puma::Server` to let the server know when
240
- # the thread pool can pull more requests from the socket and
241
- # pass to the reactor.
242
- #
243
- # The general idea is that the thread pool can only work on a fixed
244
- # number of requests at the same time. If it is already processing that
245
- # number of requests then it is at capacity. If another Puma process has
246
- # spare capacity, then the request can be left on the socket so the other
247
- # worker can pick it up and process it.
248
- #
249
- # For example: if there are 5 threads, but only 4 working on
250
- # requests, this method will not wait and the `Puma::Server`
251
- # can pull a request right away.
252
- #
253
- # If there are 5 threads and all 5 of them are busy, then it will
254
- # pause here, and wait until the `not_full` condition variable is
255
- # signaled, usually this indicates that a request has been processed.
256
- #
257
- # It's important to note that even though the server might accept another
258
- # request, it might not be added to the `@todo` array right away.
259
- # For example if a slow client has only sent a header, but not a body
260
- # then the `@todo` array would stay the same size as the reactor works
261
- # to try to buffer the request. In that scenario the next call to this
262
- # method would not block and another request would be added into the reactor
263
- # by the server. This would continue until a fully buffered request
264
- # makes it through the reactor and can then be processed by the thread pool.
265
- def wait_until_not_full
354
+ def spawn_thread_if_needed # :nodoc:
266
355
  with_mutex do
267
- while true
268
- return if @shutdown
269
-
270
- # If we can still spin up new threads and there
271
- # is work queued that cannot be handled by waiting
272
- # threads, then accept more work until we would
273
- # spin up the max number of threads.
274
- return if busy_threads < @max
275
-
276
- @not_full.wait @mutex
356
+ if @waiting < @todo.size and can_spawn_processor?
357
+ spawn_thread
277
358
  end
278
359
  end
279
360
  end
280
361
 
281
- # @version 5.0.0
282
- def wait_for_less_busy_worker(delay_s)
283
- return unless delay_s && delay_s > 0
284
-
285
- # Ruby MRI does GVL, this can result
286
- # in processing contention when multiple threads
287
- # (requests) are running concurrently
288
- return unless Puma.mri?
289
-
290
- with_mutex do
291
- return if @shutdown
292
-
293
- # do not delay, if we are not busy
294
- return unless busy_threads > 0
295
-
296
- # this will be signaled once a request finishes,
297
- # which can happen earlier than delay
298
- @not_full.wait @mutex, delay_s
299
- end
300
- end
301
-
302
362
  # If there are any free threads in the pool, tell one to go ahead
303
363
  # and exit. If +force+ is true, then a trim request is requested
304
364
  # even if all threads are being utilized.
@@ -317,16 +377,12 @@ module Puma
317
377
  # spawned counter so that new healthy threads could be created again.
318
378
  def reap
319
379
  with_mutex do
320
- dead_workers = @workers.reject(&:alive?)
380
+ @processors, dead_processors = @processors.partition(&:alive?)
321
381
 
322
- dead_workers.each do |worker|
323
- worker.kill
382
+ dead_processors.each do |processor|
383
+ processor.kill
324
384
  @spawned -= 1
325
385
  end
326
-
327
- @workers.delete_if do |w|
328
- dead_workers.include?(w)
329
- end
330
386
  end
331
387
  end
332
388
 
@@ -358,12 +414,12 @@ module Puma
358
414
  end
359
415
 
360
416
  def auto_trim!(timeout=@auto_trim_time)
361
- @auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
417
+ @auto_trim = Automaton.new(self, timeout, "#{@name} tp trim", :trim)
362
418
  @auto_trim.start!
363
419
  end
364
420
 
365
421
  def auto_reap!(timeout=@reaping_time)
366
- @reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
422
+ @reaper = Automaton.new(self, timeout, "#{@name} tp reap", :reap)
367
423
  @reaper.start!
368
424
  end
369
425
 
@@ -385,7 +441,7 @@ module Puma
385
441
  # Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
386
442
  # threads. Finally, wait 1 second for remaining threads to exit.
387
443
  #
388
- def shutdown(timeout=-1)
444
+ def shutdown(timeout)
389
445
  threads = with_mutex do
390
446
  @shutdown = true
391
447
  @trim_requested = @spawned
@@ -394,8 +450,12 @@ module Puma
394
450
 
395
451
  @auto_trim&.stop
396
452
  @reaper&.stop
397
- # dup workers so that we join them all safely
398
- @workers.dup
453
+ # dup processors so that we join them all safely
454
+ @processors.dup
455
+ end
456
+
457
+ if @shutdown_debug == true
458
+ shutdown_debug("Shutdown initiated")
399
459
  end
400
460
 
401
461
  if timeout == -1
@@ -405,13 +465,16 @@ module Puma
405
465
  join = ->(inner_timeout) do
406
466
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
407
467
  threads.reject! do |t|
408
- elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
409
- t.join inner_timeout - elapsed
468
+ remaining = inner_timeout - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
469
+ remaining > 0 && t.join(remaining)
410
470
  end
411
471
  end
412
472
 
413
473
  # Wait +timeout+ seconds for threads to finish.
414
474
  join.call(timeout)
475
+ if @shutdown_debug == :on_force && !threads.empty?
476
+ shutdown_debug("Shutdown timeout exceeded")
477
+ end
415
478
 
416
479
  # If threads are still running, raise ForceShutdown and wait to finish.
417
480
  @shutdown_mutex.synchronize do
@@ -421,6 +484,9 @@ module Puma
421
484
  end
422
485
  end
423
486
  join.call(@shutdown_grace_time)
487
+ if @shutdown_debug == :on_force && !threads.empty?
488
+ shutdown_debug("Shutdown grace timeout exceeded")
489
+ end
424
490
 
425
491
  # If threads are _still_ running, forcefully kill them and wait to finish.
426
492
  threads.each(&:kill)
@@ -428,7 +494,24 @@ module Puma
428
494
  end
429
495
 
430
496
  @spawned = 0
431
- @workers = []
497
+ @processors = []
498
+ end
499
+
500
+ private
501
+
502
+ def shutdown_debug(message)
503
+ pid = Process.pid
504
+ threads = Thread.list
505
+
506
+ $stdout.syswrite "#{pid}: #{message}\n"
507
+ $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
508
+
509
+ threads.each_with_index do |thread, index|
510
+ $stdout.syswrite "#{pid}: Thread #{index + 1}/#{threads.size}: #{thread.inspect}\n"
511
+ $stdout.syswrite "#{pid}: #{(thread.backtrace || []).join("\n#{pid}: ")}\n\n"
512
+ end
513
+
514
+ $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
432
515
  end
433
516
  end
434
517
  end
data/lib/puma/util.rb CHANGED
@@ -10,13 +10,6 @@ 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
-
20
13
  # Escapes and unescapes a URI escaped string with
21
14
  # +encoding+. +encoding+ will be the target encoding of the string
22
15
  # returned, and it defaults to UTF-8
data/lib/puma.rb CHANGED
@@ -75,4 +75,14 @@ module Puma
75
75
  def self.set_thread_name(name)
76
76
  Thread.current.name = "puma #{name}"
77
77
  end
78
+
79
+ # Shows deprecated warning for renamed methods.
80
+ # @example
81
+ # Puma.deprecate_method_change :on_booted, __callee__, __method__
82
+ #
83
+ def self.deprecate_method_change(method_old, method_caller, method_new)
84
+ if method_old == method_caller
85
+ warn "Use '#{method_new}', '#{method_caller}' is deprecated and will be removed in v8"
86
+ end
87
+ end
78
88
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This module is used as an 'include' file in code at bottom of file
4
3
  module Puma
4
+
5
+ # This module is used as an 'include' file in code at bottom of file. It loads
6
+ # into either `Rackup::Handler::Puma` or `Rack::Handler::Puma`.
7
+
5
8
  module RackHandler
6
9
  DEFAULT_OPTIONS = {
7
10
  :Verbose => false,
@@ -29,7 +32,7 @@ module Puma
29
32
 
30
33
  @events = options[:events] || ::Puma::Events.new
31
34
 
32
- conf = ::Puma::Configuration.new(options, default_options.merge({events: @events})) do |user_config, file_config, default_config|
35
+ conf = ::Puma::Configuration.new(options, default_options.merge({ events: @events })) do |user_config, file_config, default_config|
33
36
  if options.delete(:Verbose)
34
37
  begin
35
38
  require 'rack/commonlogger' # Rack 1.x
@@ -69,7 +72,7 @@ module Puma
69
72
 
70
73
  log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
71
74
 
72
- launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer, events: @events)
75
+ launcher = ::Puma::Launcher.new(conf, log_writer: log_writer, events: @events)
73
76
 
74
77
  yield launcher if block_given?
75
78
  begin
@@ -93,9 +96,9 @@ module Puma
93
96
  def set_host_port_to_config(host, port, config)
94
97
  config.clear_binds! if host || port
95
98
 
96
- if host && (host[0,1] == '.' || host[0,1] == '/')
99
+ if host&.start_with? '.', '/', '@'
97
100
  config.bind "unix://#{host}"
98
- elsif host && host =~ /^ssl:\/\//
101
+ elsif host&.start_with? 'ssl://'
99
102
  uri = URI.parse(host)
100
103
  uri.port ||= port || ::Puma::Configuration::DEFAULTS[:tcp_port]
101
104
  config.bind uri.to_s
@@ -106,7 +109,7 @@ module Puma
106
109
  end
107
110
 
108
111
  if port
109
- host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
112
+ host ||= ::Puma::Configuration.default_tcp_host
110
113
  config.port port, host
111
114
  end
112
115
  end
@@ -115,7 +118,7 @@ module Puma
115
118
  end
116
119
 
117
120
  # rackup was removed in Rack 3, it is now a separate gem
118
- if Object.const_defined? :Rackup
121
+ if Object.const_defined?(:Rackup) && ::Rackup.const_defined?(:Handler)
119
122
  module Rackup
120
123
  module Handler
121
124
  module Puma
@@ -127,7 +130,7 @@ if Object.const_defined? :Rackup
127
130
  end
128
131
  end
129
132
  else
130
- do_register = Object.const_defined?(:Rack) && Rack.release < '3'
133
+ do_register = Object.const_defined?(:Rack) && ::Rack.release < '3'
131
134
  module Rack
132
135
  module Handler
133
136
  module Puma
data/tools/Dockerfile CHANGED
@@ -1,16 +1,26 @@
1
1
  # Use this Dockerfile to create minimal reproductions of issues
2
+ # Build (MRI): docker build -f tools/Dockerfile .
3
+ # Build (JRuby): docker build -f tools/Dockerfile --build-arg RUBY_IMAGE=jruby:9.4 .
2
4
 
3
- FROM ruby:3.2
5
+ ARG RUBY_IMAGE=ruby:latest
6
+ FROM ${RUBY_IMAGE}
4
7
 
5
- # throw errors if Gemfile has been modified since Gemfile.lock
6
- RUN bundle config --global frozen 1
8
+ # Set BUNDLE_FROZEN=false if you need to update Gemfile.lock during a build.
9
+ ARG BUNDLE_FROZEN=true
10
+
11
+ RUN apt-get update \
12
+ && apt-get install -y --no-install-recommends ragel procps git \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Only freeze Bundler and compile native extensions when using MRI.
16
+ RUN if [ "$(ruby -e 'print RUBY_ENGINE')" = "ruby" ] && [ "${BUNDLE_FROZEN}" = "true" ]; then bundle config --global frozen 1; fi
7
17
 
8
18
  WORKDIR /usr/src/app
9
19
 
10
20
  COPY . .
11
21
 
12
22
  RUN bundle install
13
- RUN bundle exec rake compile
23
+ RUN if [ "$(ruby -e 'print RUBY_ENGINE')" = "ruby" ]; then bundle exec rake compile; fi
14
24
 
15
25
  EXPOSE 9292
16
- CMD bundle exec bin/puma test/rackup/hello.ru
26
+ CMD ["bundle", "exec", "bin/puma", "test/rackup/hello.ru"]