puma 4.3.6 → 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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1486 -518
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -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 +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +46 -9
  28. data/ext/puma_http11/http11_parser.c +68 -57
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +1 -1
  33. data/ext/puma_http11/mini_ssl.c +275 -122
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +47 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +174 -91
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +18 -9
  49. data/lib/puma/control_cli.rb +93 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +364 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +117 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +128 -46
  60. data/lib/puma/null_io.rb +13 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +472 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +287 -743
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +47 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +20 -1
  76. data/lib/puma.rb +46 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  79. data/tools/trickletest.rb +0 -0
  80. metadata +28 -24
  81. data/docs/tcp_mode.md +0 -96
  82. data/ext/puma_http11/io_buffer.c +0 -155
  83. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  84. data/lib/puma/accept_nonblock.rb +0 -29
  85. data/lib/puma/tcp_logger.rb +0 -41
  86. data/tools/jungle/README.md +0 -19
  87. data/tools/jungle/init.d/README.md +0 -61
  88. data/tools/jungle/init.d/puma +0 -421
  89. data/tools/jungle/init.d/run-puma +0 -18
  90. data/tools/jungle/upstart/README.md +0 -61
  91. data/tools/jungle/upstart/puma-manager.conf +0 -31
  92. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/single.rb CHANGED
@@ -13,12 +13,11 @@ module Puma
13
13
  # gets created via the `start_server` method from the `Puma::Runner` class
14
14
  # that this inherits from.
15
15
  class Single < Runner
16
+ # @!attribute [r] stats
16
17
  def stats
17
- b = @server.backlog || 0
18
- r = @server.running || 0
19
- t = @server.pool_capacity || 0
20
- m = @server.max_threads || 0
21
- %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
18
+ {
19
+ started_at: @started_at.utc.iso8601
20
+ }.merge(@server.stats)
22
21
  end
23
22
 
24
23
  def restart
@@ -39,64 +38,10 @@ module Puma
39
38
  @server.stop(true) if @server
40
39
  end
41
40
 
42
- def jruby_daemon?
43
- daemon? and Puma.jruby?
44
- end
45
-
46
- def jruby_daemon_start
47
- require 'puma/jruby_restart'
48
- JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
49
- end
50
-
51
41
  def run
52
- already_daemon = false
53
-
54
- if jruby_daemon?
55
- require 'puma/jruby_restart'
56
-
57
- if JRubyRestart.daemon?
58
- # load and bind before redirecting IO so errors show up on stdout/stderr
59
- load_and_bind
60
- redirect_io
61
- end
62
-
63
- already_daemon = JRubyRestart.daemon_init
64
- end
65
-
66
42
  output_header "single"
67
43
 
68
- if jruby_daemon?
69
- if already_daemon
70
- JRubyRestart.perm_daemonize
71
- else
72
- pid = nil
73
-
74
- Signal.trap "SIGUSR2" do
75
- log "* Started new process #{pid} as daemon..."
76
-
77
- # Must use exit! so we don't unwind and run the ensures
78
- # that will be run by the new child (such as deleting the
79
- # pidfile)
80
- exit!(true)
81
- end
82
-
83
- Signal.trap "SIGCHLD" do
84
- log "! Error starting new process as daemon, exiting"
85
- exit 1
86
- end
87
-
88
- jruby_daemon_start
89
- sleep
90
- end
91
- else
92
- if daemon?
93
- log "* Daemonizing..."
94
- Process.daemon(true)
95
- redirect_io
96
- end
97
-
98
- load_and_bind
99
- end
44
+ load_and_bind
100
45
 
101
46
  Plugins.fire_background
102
47
 
@@ -105,16 +50,15 @@ module Puma
105
50
  start_control
106
51
 
107
52
  @server = server = start_server
53
+ server_thread = server.run
108
54
 
109
- unless daemon?
110
- log "Use Ctrl-C to stop"
111
- redirect_io
112
- end
55
+ log "Use Ctrl-C to stop"
56
+ redirect_io
113
57
 
114
58
  @launcher.events.fire_on_booted!
115
59
 
116
60
  begin
117
- server.run.join
61
+ server_thread.join
118
62
  rescue Interrupt
119
63
  # Swallow it
120
64
  end
@@ -1,24 +1,63 @@
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
- def save(path)
12
- File.write path, YAML.dump(@options)
25
+ def save(path, permission = nil)
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
38
+ if permission
39
+ File.write path, contents, mode: 'wb:UTF-8'
40
+ else
41
+ File.write path, contents, mode: 'wb:UTF-8', perm: permission
42
+ end
13
43
  end
14
44
 
15
45
  def load(path)
16
- @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
17
58
  end
18
59
 
19
- FIELDS = %w!control_url control_auth_token pid!
20
-
21
- FIELDS.each do |f|
60
+ ALLOWED_FIELDS.each do |f|
22
61
  define_method f do
23
62
  @options[f]
24
63
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sd_notify'
4
+
5
+ module Puma
6
+ class Systemd
7
+ def initialize(events)
8
+ @events = events
9
+ end
10
+
11
+ def hook_events
12
+ @events.on_booted { SdNotify.ready }
13
+ @events.on_stopped { SdNotify.stopping }
14
+ @events.on_restart { SdNotify.reloading }
15
+ end
16
+
17
+ def start_watchdog
18
+ return unless SdNotify.watchdog?
19
+
20
+ ping_f = watchdog_sleep_time
21
+
22
+ log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
23
+ Thread.new do
24
+ loop do
25
+ sleep ping_f
26
+ SdNotify.watchdog
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def watchdog_sleep_time
34
+ usec = Integer(ENV["WATCHDOG_USEC"])
35
+
36
+ sec_f = usec / 1_000_000.0
37
+ # "It is recommended that a daemon sends a keep-alive notification message
38
+ # to the service manager every half of the time returned here."
39
+ sec_f / 2
40
+ end
41
+
42
+ def log(str)
43
+ @events.log str
44
+ end
45
+ end
46
+ end
@@ -13,7 +13,7 @@ module Puma
13
13
  # a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array.
14
14
  #
15
15
  # Each thread in the pool has an internal loop where it pulls a request from the `@todo` array
16
- # and proceses it.
16
+ # and processes it.
17
17
  class ThreadPool
18
18
  class ForceShutdown < RuntimeError
19
19
  end
@@ -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
@@ -47,6 +48,7 @@ module Puma
47
48
  @shutdown = false
48
49
 
49
50
  @trim_requested = 0
51
+ @out_of_band_pending = false
50
52
 
51
53
  @workers = []
52
54
 
@@ -54,17 +56,23 @@ module Puma
54
56
  @reaper = nil
55
57
 
56
58
  @mutex.synchronize do
57
- @min.times { spawn_thread }
59
+ @min.times do
60
+ spawn_thread
61
+ @not_full.wait(@mutex)
62
+ end
58
63
  end
59
64
 
60
65
  @clean_thread_locals = false
66
+ @force_shutdown = false
67
+ @shutdown_mutex = Mutex.new
61
68
  end
62
69
 
63
70
  attr_reader :spawned, :trim_requested, :waiting
64
71
  attr_accessor :clean_thread_locals
72
+ attr_accessor :out_of_band_hook # @version 5.0.0
65
73
 
66
74
  def self.clean_thread_locals
67
- Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods
75
+ Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
68
76
  Thread.current[key] = nil unless key == :__recursive_key__
69
77
  end
70
78
  end
@@ -72,13 +80,20 @@ module Puma
72
80
  # How many objects have yet to be processed by the pool?
73
81
  #
74
82
  def backlog
75
- @mutex.synchronize { @todo.size }
83
+ with_mutex { @todo.size }
76
84
  end
77
85
 
86
+ # @!attribute [r] pool_capacity
78
87
  def pool_capacity
79
88
  waiting + (@max - spawned)
80
89
  end
81
90
 
91
+ # @!attribute [r] busy_threads
92
+ # @version 5.0.0
93
+ def busy_threads
94
+ with_mutex { @spawned - @waiting + @todo.size }
95
+ end
96
+
82
97
  # :nodoc:
83
98
  #
84
99
  # Must be called with @mutex held!
@@ -87,7 +102,7 @@ module Puma
87
102
  @spawned += 1
88
103
 
89
104
  th = Thread.new(@spawned) do |spawned|
90
- Puma.set_thread_name 'threadpool %03i' % spawned
105
+ Puma.set_thread_name '%s tp %03i' % [@name, spawned]
91
106
  todo = @todo
92
107
  block = @block
93
108
  mutex = @mutex
@@ -99,48 +114,41 @@ module Puma
99
114
  while true
100
115
  work = nil
101
116
 
102
- continue = true
103
-
104
117
  mutex.synchronize do
105
118
  while todo.empty?
106
119
  if @trim_requested > 0
107
120
  @trim_requested -= 1
108
- continue = false
121
+ @spawned -= 1
122
+ @workers.delete th
109
123
  not_full.signal
110
- break
111
- end
112
-
113
- if @shutdown
114
- continue = false
115
- break
124
+ Thread.exit
116
125
  end
117
126
 
118
127
  @waiting += 1
128
+ if @out_of_band_pending && trigger_out_of_band_hook
129
+ @out_of_band_pending = false
130
+ end
119
131
  not_full.signal
120
- not_empty.wait mutex
121
- @waiting -= 1
132
+ begin
133
+ not_empty.wait mutex
134
+ ensure
135
+ @waiting -= 1
136
+ end
122
137
  end
123
138
 
124
- work = todo.shift if continue
139
+ work = todo.shift
125
140
  end
126
141
 
127
- break unless continue
128
-
129
142
  if @clean_thread_locals
130
143
  ThreadPool.clean_thread_locals
131
144
  end
132
145
 
133
146
  begin
134
- block.call(work, *extra)
147
+ @out_of_band_pending = true if block.call(work, *extra)
135
148
  rescue Exception => e
136
149
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
137
150
  end
138
151
  end
139
-
140
- mutex.synchronize do
141
- @spawned -= 1
142
- @workers.delete th
143
- end
144
152
  end
145
153
 
146
154
  @workers << th
@@ -150,9 +158,32 @@ module Puma
150
158
 
151
159
  private :spawn_thread
152
160
 
161
+ # @version 5.0.0
162
+ def trigger_out_of_band_hook
163
+ return false unless out_of_band_hook && out_of_band_hook.any?
164
+
165
+ # we execute on idle hook when all threads are free
166
+ return false unless @spawned == @waiting
167
+
168
+ out_of_band_hook.each(&:call)
169
+ true
170
+ rescue Exception => e
171
+ STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
172
+ true
173
+ end
174
+
175
+ private :trigger_out_of_band_hook
176
+
177
+ # @version 5.0.0
178
+ def with_mutex(&block)
179
+ @mutex.owned? ?
180
+ yield :
181
+ @mutex.synchronize(&block)
182
+ end
183
+
153
184
  # Add +work+ to the todo list for a Thread to pickup and process.
154
185
  def <<(work)
155
- @mutex.synchronize do
186
+ with_mutex do
156
187
  if @shutdown
157
188
  raise "Unable to add work while shutting down"
158
189
  end
@@ -191,13 +222,10 @@ module Puma
191
222
  # then the `@todo` array would stay the same size as the reactor works
192
223
  # to try to buffer the request. In that scenario the next call to this
193
224
  # method would not block and another request would be added into the reactor
194
- # by the server. This would continue until a fully bufferend request
225
+ # by the server. This would continue until a fully buffered request
195
226
  # makes it through the reactor and can then be processed by the thread pool.
196
- #
197
- # Returns the current number of busy threads, or +nil+ if shutting down.
198
- #
199
227
  def wait_until_not_full
200
- @mutex.synchronize do
228
+ with_mutex do
201
229
  while true
202
230
  return if @shutdown
203
231
 
@@ -205,21 +233,42 @@ module Puma
205
233
  # is work queued that cannot be handled by waiting
206
234
  # threads, then accept more work until we would
207
235
  # spin up the max number of threads.
208
- busy_threads = @spawned - @waiting + @todo.size
209
- return busy_threads if @max > busy_threads
236
+ return if busy_threads < @max
210
237
 
211
238
  @not_full.wait @mutex
212
239
  end
213
240
  end
214
241
  end
215
242
 
216
- # If too many threads are in the pool, tell one to finish go ahead
243
+ # @version 5.0.0
244
+ def wait_for_less_busy_worker(delay_s)
245
+ return unless delay_s && delay_s > 0
246
+
247
+ # Ruby MRI does GVL, this can result
248
+ # in processing contention when multiple threads
249
+ # (requests) are running concurrently
250
+ return unless Puma.mri?
251
+
252
+ with_mutex do
253
+ return if @shutdown
254
+
255
+ # do not delay, if we are not busy
256
+ return unless busy_threads > 0
257
+
258
+ # this will be signaled once a request finishes,
259
+ # which can happen earlier than delay
260
+ @not_full.wait @mutex, delay_s
261
+ end
262
+ end
263
+
264
+ # If there are any free threads in the pool, tell one to go ahead
217
265
  # and exit. If +force+ is true, then a trim request is requested
218
266
  # even if all threads are being utilized.
219
267
  #
220
268
  def trim(force=false)
221
- @mutex.synchronize do
222
- if (force or @waiting > 0) and @spawned - @trim_requested > @min
269
+ with_mutex do
270
+ free = @waiting - @todo.size
271
+ if (force or free > 0) and @spawned - @trim_requested > @min
223
272
  @trim_requested += 1
224
273
  @not_empty.signal
225
274
  end
@@ -229,7 +278,7 @@ module Puma
229
278
  # If there are dead threads in the pool make them go away while decreasing
230
279
  # spawned counter so that new healthy threads could be created again.
231
280
  def reap
232
- @mutex.synchronize do
281
+ with_mutex do
233
282
  dead_workers = @workers.reject(&:alive?)
234
283
 
235
284
  dead_workers.each do |worker|
@@ -271,20 +320,37 @@ module Puma
271
320
  end
272
321
 
273
322
  def auto_trim!(timeout=30)
274
- @auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
323
+ @auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
275
324
  @auto_trim.start!
276
325
  end
277
326
 
278
327
  def auto_reap!(timeout=5)
279
- @reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
328
+ @reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
280
329
  @reaper.start!
281
330
  end
282
331
 
332
+ # Allows ThreadPool::ForceShutdown to be raised within the
333
+ # provided block if the thread is forced to shutdown during execution.
334
+ def with_force_shutdown
335
+ t = Thread.current
336
+ @shutdown_mutex.synchronize do
337
+ raise ForceShutdown if @force_shutdown
338
+ t[:with_force_shutdown] = true
339
+ end
340
+ yield
341
+ ensure
342
+ t[:with_force_shutdown] = false
343
+ end
344
+
283
345
  # Tell all threads in the pool to exit and wait for them to finish.
346
+ # Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
347
+ # Next, wait an extra +grace+ seconds then force-kill remaining threads.
348
+ # Finally, wait +kill_grace+ seconds for remaining threads to exit.
284
349
  #
285
350
  def shutdown(timeout=-1)
286
- threads = @mutex.synchronize do
351
+ threads = with_mutex do
287
352
  @shutdown = true
353
+ @trim_requested = @spawned
288
354
  @not_empty.broadcast
289
355
  @not_full.broadcast
290
356
 
@@ -298,27 +364,29 @@ module Puma
298
364
  # Wait for threads to finish without force shutdown.
299
365
  threads.each(&:join)
300
366
  else
301
- # Wait for threads to finish after n attempts (+timeout+).
302
- # If threads are still running, it will forcefully kill them.
303
- timeout.times do
304
- threads.delete_if do |t|
305
- t.join 1
306
- end
307
-
308
- if threads.empty?
309
- break
310
- else
311
- sleep 1
367
+ join = ->(inner_timeout) do
368
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
369
+ threads.reject! do |t|
370
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
371
+ t.join inner_timeout - elapsed
312
372
  end
313
373
  end
314
374
 
315
- threads.each do |t|
316
- t.raise ForceShutdown
317
- end
375
+ # Wait +timeout+ seconds for threads to finish.
376
+ join.call(timeout)
318
377
 
319
- threads.each do |t|
320
- t.join SHUTDOWN_GRACE_TIME
378
+ # If threads are still running, raise ForceShutdown and wait to finish.
379
+ @shutdown_mutex.synchronize do
380
+ @force_shutdown = true
381
+ threads.each do |t|
382
+ t.raise ForceShutdown if t[:with_force_shutdown]
383
+ end
321
384
  end
385
+ join.call(SHUTDOWN_GRACE_TIME)
386
+
387
+ # If threads are _still_ running, forcefully kill them and wait to finish.
388
+ threads.each(&:kill)
389
+ join.call(1)
322
390
  end
323
391
 
324
392
  @spawned = 0
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)
@@ -23,6 +30,17 @@ module Puma
23
30
  end
24
31
  module_function :unescape
25
32
 
33
+ # @version 5.0.0
34
+ def nakayoshi_gc(events)
35
+ events.log "! Promoting existing objects to old generation..."
36
+ 4.times { GC.start(full_mark: false) }
37
+ if GC.respond_to?(:compact)
38
+ events.log "! Compacting..."
39
+ GC.compact
40
+ end
41
+ events.log "! Friendly fork preparation complete."
42
+ end
43
+
26
44
  DEFAULT_SEP = /[&;] */n
27
45
 
28
46
  # Stolen from Mongrel, with some small modifications:
@@ -50,7 +68,7 @@ module Puma
50
68
  end
51
69
  end
52
70
 
53
- return params
71
+ params
54
72
  end
55
73
 
56
74
  # A case-insensitive Hash that preserves the original case of a
@@ -72,6 +90,7 @@ module Puma
72
90
  end
73
91
  end
74
92
 
93
+ # @!attribute [r] to_hash
75
94
  def to_hash
76
95
  hash = {}
77
96
  each { |k,v| hash[k] = v }
data/lib/puma.rb CHANGED
@@ -10,16 +10,62 @@ require 'stringio'
10
10
 
11
11
  require 'thread'
12
12
 
13
+ require 'puma/puma_http11'
14
+ require 'puma/detect'
15
+ require 'puma/json_serialization'
16
+
13
17
  module Puma
14
18
  autoload :Const, 'puma/const'
15
19
  autoload :Server, 'puma/server'
16
20
  autoload :Launcher, 'puma/launcher'
17
21
 
22
+ # at present, MiniSSL::Engine is only defined in extension code (puma_http11),
23
+ # not in minissl.rb
24
+ HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
25
+
26
+ HAS_UNIX_SOCKET = Object.const_defined? :UNIXSocket
27
+
28
+ if HAS_SSL
29
+ require 'puma/minissl'
30
+ else
31
+ module MiniSSL
32
+ # this class is defined so that it exists when Puma is compiled
33
+ # without ssl support, as Server and Reactor use it in rescue statements.
34
+ class SSLError < StandardError ; end
35
+ end
36
+ end
37
+
38
+ def self.ssl?
39
+ HAS_SSL
40
+ end
41
+
42
+ def self.abstract_unix_socket?
43
+ @abstract_unix ||=
44
+ if HAS_UNIX_SOCKET
45
+ begin
46
+ ::UNIXServer.new("\0puma.temp.unix").close
47
+ true
48
+ rescue ArgumentError # darwin
49
+ false
50
+ end
51
+ else
52
+ false
53
+ end
54
+ end
55
+
56
+ # @!attribute [rw] stats_object=
18
57
  def self.stats_object=(val)
19
58
  @get_stats = val
20
59
  end
21
60
 
61
+ # @!attribute [rw] stats_object
22
62
  def self.stats
63
+ Puma::JSONSerialization.generate @get_stats.stats
64
+ end
65
+
66
+ # @!attribute [r] stats_hash
67
+ # @version 5.0.0
68
+ def self.stats_hash
23
69
  @get_stats.stats
24
70
  end
25
71
 
@@ -30,9 +30,8 @@ module Rack
30
30
  end
31
31
 
32
32
  conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
33
- user_config.quiet
34
-
35
33
  if options.delete(:Verbose)
34
+ require 'rack/common_logger'
36
35
  app = Rack::CommonLogger.new(app, STDOUT)
37
36
  end
38
37
 
@@ -61,7 +60,7 @@ module Rack
61
60
  conf
62
61
  end
63
62
 
64
- def self.run(app, options = {})
63
+ def self.run(app, **options)
65
64
  conf = self.config(app, options)
66
65
 
67
66
  events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio
@@ -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