puma 4.3.8 → 5.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1543 -521
  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/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/docs/kubernetes.md +66 -0
  14. data/docs/nginx.md +1 -1
  15. data/docs/plugins.md +15 -15
  16. data/docs/rails_dev_mode.md +28 -0
  17. data/docs/restart.md +46 -23
  18. data/docs/signals.md +13 -11
  19. data/docs/stats.md +142 -0
  20. data/docs/systemd.md +85 -128
  21. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  22. data/ext/puma_http11/ext_help.h +1 -1
  23. data/ext/puma_http11/extconf.rb +51 -9
  24. data/ext/puma_http11/http11_parser.c +68 -57
  25. data/ext/puma_http11/http11_parser.h +1 -1
  26. data/ext/puma_http11/http11_parser.java.rl +1 -1
  27. data/ext/puma_http11/http11_parser.rl +1 -1
  28. data/ext/puma_http11/http11_parser_common.rl +1 -1
  29. data/ext/puma_http11/mini_ssl.c +295 -124
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  34. data/ext/puma_http11/puma_http11.c +32 -51
  35. data/lib/puma/app/status.rb +50 -36
  36. data/lib/puma/binder.rb +225 -106
  37. data/lib/puma/cli.rb +24 -18
  38. data/lib/puma/client.rb +197 -92
  39. data/lib/puma/cluster/worker.rb +173 -0
  40. data/lib/puma/cluster/worker_handle.rb +94 -0
  41. data/lib/puma/cluster.rb +212 -220
  42. data/lib/puma/commonlogger.rb +2 -2
  43. data/lib/puma/configuration.rb +58 -49
  44. data/lib/puma/const.rb +26 -9
  45. data/lib/puma/control_cli.rb +99 -76
  46. data/lib/puma/detect.rb +29 -2
  47. data/lib/puma/dsl.rb +368 -96
  48. data/lib/puma/error_logger.rb +104 -0
  49. data/lib/puma/events.rb +55 -34
  50. data/lib/puma/io_buffer.rb +9 -2
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/json_serialization.rb +96 -0
  53. data/lib/puma/launcher.rb +128 -46
  54. data/lib/puma/minissl/context_builder.rb +14 -9
  55. data/lib/puma/minissl.rb +137 -50
  56. data/lib/puma/null_io.rb +18 -1
  57. data/lib/puma/plugin.rb +3 -12
  58. data/lib/puma/queue_close.rb +26 -0
  59. data/lib/puma/rack/builder.rb +1 -5
  60. data/lib/puma/reactor.rb +85 -369
  61. data/lib/puma/request.rb +489 -0
  62. data/lib/puma/runner.rb +46 -61
  63. data/lib/puma/server.rb +292 -751
  64. data/lib/puma/single.rb +9 -65
  65. data/lib/puma/state_file.rb +48 -8
  66. data/lib/puma/systemd.rb +46 -0
  67. data/lib/puma/thread_pool.rb +125 -57
  68. data/lib/puma/util.rb +32 -4
  69. data/lib/puma.rb +48 -0
  70. data/lib/rack/handler/puma.rb +2 -3
  71. data/lib/rack/version_restriction.rb +15 -0
  72. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  73. metadata +29 -24
  74. data/docs/tcp_mode.md +0 -96
  75. data/ext/puma_http11/io_buffer.c +0 -155
  76. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  77. data/lib/puma/accept_nonblock.rb +0 -29
  78. data/lib/puma/tcp_logger.rb +0 -41
  79. data/tools/jungle/README.md +0 -19
  80. data/tools/jungle/init.d/README.md +0 -61
  81. data/tools/jungle/init.d/puma +0 -421
  82. data/tools/jungle/init.d/run-puma +0 -18
  83. data/tools/jungle/upstart/README.md +0 -61
  84. data/tools/jungle/upstart/puma-manager.conf +0 -31
  85. data/tools/jungle/upstart/puma.conf +0 -69
  86. /data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
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,64 @@
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 '' then nil
54
+ when /\A\d+\z/ then v.to_i
55
+ when /\A\d+\.\d+\z/ then v.to_f
56
+ else v.gsub(/\A"|"\z/, '')
57
+ end
58
+ end
17
59
  end
18
60
 
19
- FIELDS = %w!control_url control_auth_token pid!
20
-
21
- FIELDS.each do |f|
61
+ ALLOWED_FIELDS.each do |f|
22
62
  define_method f do
23
63
  @options[f]
24
64
  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,18 +10,45 @@ module Puma
10
10
  IO.pipe
11
11
  end
12
12
 
13
- # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
14
- # target encoding of the string returned, and it defaults to UTF-8
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
+ # Escapes and unescapes a URI escaped string with
21
+ # +encoding+. +encoding+ will be the target encoding of the string
22
+ # returned, and it defaults to UTF-8
15
23
  if defined?(::Encoding)
24
+ def escape(s, encoding = Encoding::UTF_8)
25
+ URI.encode_www_form_component(s, encoding)
26
+ end
27
+
16
28
  def unescape(s, encoding = Encoding::UTF_8)
17
29
  URI.decode_www_form_component(s, encoding)
18
30
  end
19
31
  else
32
+ def escape(s, encoding = nil)
33
+ URI.encode_www_form_component(s, encoding)
34
+ end
35
+
20
36
  def unescape(s, encoding = nil)
21
37
  URI.decode_www_form_component(s, encoding)
22
38
  end
23
39
  end
24
- module_function :unescape
40
+ module_function :unescape, :escape
41
+
42
+ # @version 5.0.0
43
+ def nakayoshi_gc(events)
44
+ events.log "! Promoting existing objects to old generation..."
45
+ 4.times { GC.start(full_mark: false) }
46
+ if GC.respond_to?(:compact)
47
+ events.log "! Compacting..."
48
+ GC.compact
49
+ end
50
+ events.log "! Friendly fork preparation complete."
51
+ end
25
52
 
26
53
  DEFAULT_SEP = /[&;] */n
27
54
 
@@ -50,7 +77,7 @@ module Puma
50
77
  end
51
78
  end
52
79
 
53
- return params
80
+ params
54
81
  end
55
82
 
56
83
  # A case-insensitive Hash that preserves the original case of a
@@ -72,6 +99,7 @@ module Puma
72
99
  end
73
100
  end
74
101
 
102
+ # @!attribute [r] to_hash
75
103
  def to_hash
76
104
  hash = {}
77
105
  each { |k,v| hash[k] = v }
data/lib/puma.rb CHANGED
@@ -10,16 +10,64 @@ require 'stringio'
10
10
 
11
11
  require 'thread'
12
12
 
13
+ # extension files should not be loaded with `require_relative`
14
+ require 'puma/puma_http11'
15
+ require_relative 'puma/detect'
16
+ require_relative 'puma/json_serialization'
17
+ require_relative 'rack/version_restriction'
18
+
13
19
  module Puma
14
20
  autoload :Const, 'puma/const'
15
21
  autoload :Server, 'puma/server'
16
22
  autoload :Launcher, 'puma/launcher'
17
23
 
24
+ # at present, MiniSSL::Engine is only defined in extension code (puma_http11),
25
+ # not in minissl.rb
26
+ HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
27
+
28
+ HAS_UNIX_SOCKET = Object.const_defined?(:UNIXSocket) && !IS_WINDOWS
29
+
30
+ if HAS_SSL
31
+ require 'puma/minissl'
32
+ else
33
+ module MiniSSL
34
+ # this class is defined so that it exists when Puma is compiled
35
+ # without ssl support, as Server and Reactor use it in rescue statements.
36
+ class SSLError < StandardError ; end
37
+ end
38
+ end
39
+
40
+ def self.ssl?
41
+ HAS_SSL
42
+ end
43
+
44
+ def self.abstract_unix_socket?
45
+ @abstract_unix ||=
46
+ if HAS_UNIX_SOCKET
47
+ begin
48
+ ::UNIXServer.new("\0puma.temp.unix").close
49
+ true
50
+ rescue ArgumentError # darwin
51
+ false
52
+ end
53
+ else
54
+ false
55
+ end
56
+ end
57
+
58
+ # @!attribute [rw] stats_object=
18
59
  def self.stats_object=(val)
19
60
  @get_stats = val
20
61
  end
21
62
 
63
+ # @!attribute [rw] stats_object
22
64
  def self.stats
65
+ Puma::JSONSerialization.generate @get_stats.stats
66
+ end
67
+
68
+ # @!attribute [r] stats_hash
69
+ # @version 5.0.0
70
+ def self.stats_hash
23
71
  @get_stats.stats
24
72
  end
25
73