puma 5.2.2 → 6.3.0

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +483 -4
  3. data/README.md +101 -20
  4. data/bin/puma-wild +1 -1
  5. data/docs/architecture.md +50 -16
  6. data/docs/compile_options.md +38 -2
  7. data/docs/deployment.md +53 -67
  8. data/docs/fork_worker.md +1 -3
  9. data/docs/jungle/rc.d/README.md +1 -1
  10. data/docs/kubernetes.md +1 -1
  11. data/docs/nginx.md +1 -1
  12. data/docs/plugins.md +15 -15
  13. data/docs/rails_dev_mode.md +2 -3
  14. data/docs/restart.md +7 -7
  15. data/docs/signals.md +11 -10
  16. data/docs/stats.md +8 -8
  17. data/docs/systemd.md +65 -69
  18. data/docs/testing_benchmarks_local_files.md +150 -0
  19. data/docs/testing_test_rackup_ci_files.md +36 -0
  20. data/ext/puma_http11/extconf.rb +44 -13
  21. data/ext/puma_http11/http11_parser.c +24 -11
  22. data/ext/puma_http11/http11_parser.h +2 -2
  23. data/ext/puma_http11/http11_parser.java.rl +2 -2
  24. data/ext/puma_http11/http11_parser.rl +2 -2
  25. data/ext/puma_http11/http11_parser_common.rl +3 -3
  26. data/ext/puma_http11/mini_ssl.c +150 -23
  27. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  28. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
  29. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  30. data/ext/puma_http11/puma_http11.c +18 -10
  31. data/lib/puma/app/status.rb +10 -7
  32. data/lib/puma/binder.rb +112 -62
  33. data/lib/puma/cli.rb +24 -20
  34. data/lib/puma/client.rb +162 -36
  35. data/lib/puma/cluster/worker.rb +31 -27
  36. data/lib/puma/cluster/worker_handle.rb +12 -1
  37. data/lib/puma/cluster.rb +102 -61
  38. data/lib/puma/commonlogger.rb +21 -14
  39. data/lib/puma/configuration.rb +78 -54
  40. data/lib/puma/const.rb +135 -97
  41. data/lib/puma/control_cli.rb +25 -20
  42. data/lib/puma/detect.rb +12 -2
  43. data/lib/puma/dsl.rb +308 -58
  44. data/lib/puma/error_logger.rb +20 -11
  45. data/lib/puma/events.rb +6 -126
  46. data/lib/puma/io_buffer.rb +39 -4
  47. data/lib/puma/jruby_restart.rb +2 -1
  48. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  49. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  50. data/lib/puma/launcher.rb +114 -173
  51. data/lib/puma/log_writer.rb +147 -0
  52. data/lib/puma/minissl/context_builder.rb +30 -16
  53. data/lib/puma/minissl.rb +132 -38
  54. data/lib/puma/null_io.rb +5 -0
  55. data/lib/puma/plugin/systemd.rb +90 -0
  56. data/lib/puma/plugin/tmp_restart.rb +1 -1
  57. data/lib/puma/plugin.rb +2 -2
  58. data/lib/puma/rack/builder.rb +7 -7
  59. data/lib/puma/rack_default.rb +19 -4
  60. data/lib/puma/reactor.rb +19 -10
  61. data/lib/puma/request.rb +373 -153
  62. data/lib/puma/runner.rb +74 -28
  63. data/lib/puma/sd_notify.rb +149 -0
  64. data/lib/puma/server.rb +127 -136
  65. data/lib/puma/single.rb +13 -11
  66. data/lib/puma/state_file.rb +39 -7
  67. data/lib/puma/thread_pool.rb +33 -26
  68. data/lib/puma/util.rb +20 -15
  69. data/lib/puma.rb +28 -11
  70. data/lib/rack/handler/puma.rb +113 -86
  71. data/tools/Dockerfile +1 -1
  72. metadata +15 -10
  73. data/lib/puma/queue_close.rb +0 -26
  74. data/lib/puma/systemd.rb +0 -46
data/lib/puma/client.rb CHANGED
@@ -8,7 +8,8 @@ class IO
8
8
  end
9
9
  end
10
10
 
11
- require 'puma/detect'
11
+ require_relative 'detect'
12
+ require_relative 'io_buffer'
12
13
  require 'tempfile'
13
14
  require 'forwardable'
14
15
 
@@ -23,6 +24,11 @@ module Puma
23
24
 
24
25
  class ConnectionError < RuntimeError; end
25
26
 
27
+ class HttpParserError501 < IOError; end
28
+
29
+ #———————————————————————— DO NOT USE — this class is for internal use only ———
30
+
31
+
26
32
  # An instance of this class represents a unique request from a client.
27
33
  # For example, this could be a web request from a browser or from CURL.
28
34
  #
@@ -35,7 +41,21 @@ module Puma
35
41
  # Instances of this class are responsible for knowing if
36
42
  # the header and body are fully buffered via the `try_to_finish` method.
37
43
  # They can be used to "time out" a response via the `timeout_at` reader.
38
- class Client
44
+ #
45
+ class Client # :nodoc:
46
+
47
+ # this tests all values but the last, which must be chunked
48
+ ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
49
+
50
+ # chunked body validation
51
+ CHUNK_SIZE_INVALID = /[^\h]/.freeze
52
+ CHUNK_VALID_ENDING = "\r\n".freeze
53
+
54
+ # Content-Length header value validation
55
+ CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
56
+
57
+ TE_ERR_MSG = 'Invalid Transfer-Encoding'
58
+
39
59
  # The object used for a request with no body. All requests with
40
60
  # no body share this one object since it has no state.
41
61
  EmptyBody = NullIO.new
@@ -46,16 +66,14 @@ module Puma
46
66
  def initialize(io, env=nil)
47
67
  @io = io
48
68
  @to_io = io.to_io
69
+ @io_buffer = IOBuffer.new
49
70
  @proto_env = env
50
- if !env
51
- @env = nil
52
- else
53
- @env = env.dup
54
- end
71
+ @env = env&.dup
55
72
 
56
73
  @parser = HttpParser.new
57
74
  @parsed_bytes = 0
58
75
  @read_header = true
76
+ @read_proxy = false
59
77
  @ready = false
60
78
 
61
79
  @body = nil
@@ -68,20 +86,29 @@ module Puma
68
86
  @requests_served = 0
69
87
  @hijacked = false
70
88
 
89
+ @http_content_length_limit = nil
90
+ @http_content_length_limit_exceeded = false
91
+
71
92
  @peerip = nil
93
+ @peer_family = nil
94
+ @listener = nil
72
95
  @remote_addr_header = nil
96
+ @expect_proxy_proto = false
73
97
 
74
98
  @body_remain = 0
75
99
 
76
100
  @in_last_chunk = false
101
+
102
+ # need unfrozen ASCII-8BIT, +'' is UTF-8
103
+ @read_buffer = String.new # rubocop: disable Performance/UnfreezeString
77
104
  end
78
105
 
79
106
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
80
- :tempfile
107
+ :tempfile, :io_buffer, :http_content_length_limit_exceeded
81
108
 
82
- attr_writer :peerip
109
+ attr_writer :peerip, :http_content_length_limit
83
110
 
84
- attr_accessor :remote_addr_header
111
+ attr_accessor :remote_addr_header, :listener
85
112
 
86
113
  def_delegators :@io, :closed?
87
114
 
@@ -105,7 +132,7 @@ module Puma
105
132
 
106
133
  # @!attribute [r] in_data_phase
107
134
  def in_data_phase
108
- !@read_header
135
+ !(@read_header || @read_proxy)
109
136
  end
110
137
 
111
138
  def set_timeout(val)
@@ -119,17 +146,22 @@ module Puma
119
146
 
120
147
  def reset(fast_check=true)
121
148
  @parser.reset
149
+ @io_buffer.reset
122
150
  @read_header = true
151
+ @read_proxy = !!@expect_proxy_proto
123
152
  @env = @proto_env.dup
124
153
  @body = nil
125
154
  @tempfile = nil
126
155
  @parsed_bytes = 0
127
156
  @ready = false
128
157
  @body_remain = 0
129
- @peerip = nil
158
+ @peerip = nil if @remote_addr_header
130
159
  @in_last_chunk = false
160
+ @http_content_length_limit_exceeded = false
131
161
 
132
162
  if @buffer
163
+ return false unless try_to_parse_proxy_protocol
164
+
133
165
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
134
166
 
135
167
  if @parser.finished?
@@ -142,8 +174,7 @@ module Puma
142
174
  return false
143
175
  else
144
176
  begin
145
- if fast_check &&
146
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
177
+ if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
147
178
  return try_to_finish
148
179
  end
149
180
  rescue IOError
@@ -156,13 +187,48 @@ module Puma
156
187
  def close
157
188
  begin
158
189
  @io.close
159
- rescue IOError
160
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
190
+ rescue IOError, Errno::EBADF
191
+ Puma::Util.purge_interrupt_queue
161
192
  end
162
193
  end
163
194
 
195
+ # If necessary, read the PROXY protocol from the buffer. Returns
196
+ # false if more data is needed.
197
+ def try_to_parse_proxy_protocol
198
+ if @read_proxy
199
+ if @expect_proxy_proto == :v1
200
+ if @buffer.include? "\r\n"
201
+ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
202
+ if md[1]
203
+ @peerip = md[1].split(" ")[0]
204
+ end
205
+ @buffer = md.post_match
206
+ end
207
+ # if the buffer has a \r\n but doesn't have a PROXY protocol
208
+ # request, this is just HTTP from a non-PROXY client; move on
209
+ @read_proxy = false
210
+ return @buffer.size > 0
211
+ else
212
+ return false
213
+ end
214
+ end
215
+ end
216
+ true
217
+ end
218
+
164
219
  def try_to_finish
165
- return read_body unless @read_header
220
+ if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
221
+ @http_content_length_limit_exceeded = true
222
+ end
223
+
224
+ if @http_content_length_limit_exceeded
225
+ @buffer = nil
226
+ @body = EmptyBody
227
+ set_ready
228
+ return true
229
+ end
230
+
231
+ return read_body if in_data_phase
166
232
 
167
233
  begin
168
234
  data = @io.read_nonblock(CHUNK_SIZE)
@@ -187,8 +253,14 @@ module Puma
187
253
  @buffer = data
188
254
  end
189
255
 
256
+ return false unless try_to_parse_proxy_protocol
257
+
190
258
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
191
259
 
260
+ if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
261
+ @http_content_length_limit_exceeded = true
262
+ end
263
+
192
264
  if @parser.finished?
193
265
  return setup_body
194
266
  elsif @parsed_bytes >= MAX_HEADER
@@ -201,13 +273,13 @@ module Puma
201
273
 
202
274
  def eagerly_finish
203
275
  return true if @ready
204
- return false unless IO.select([@to_io], nil, nil, 0)
276
+ return false unless @to_io.wait_readable(0)
205
277
  try_to_finish
206
278
  end
207
279
 
208
280
  def finish(timeout)
209
281
  return if @ready
210
- IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
282
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
211
283
  end
212
284
 
213
285
  def timeout!
@@ -226,7 +298,7 @@ module Puma
226
298
  return @peerip if @peerip
227
299
 
228
300
  if @remote_addr_header
229
- hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
301
+ hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
230
302
  @peerip = hdr
231
303
  return hdr
232
304
  end
@@ -234,6 +306,16 @@ module Puma
234
306
  @peerip ||= @io.peeraddr.last
235
307
  end
236
308
 
309
+ def peer_family
310
+ return @peer_family if @peer_family
311
+
312
+ @peer_family ||= begin
313
+ @io.local_address.afamily
314
+ rescue
315
+ Socket::AF_INET
316
+ end
317
+ end
318
+
237
319
  # Returns true if the persistent connection can be closed immediately
238
320
  # without waiting for the configured idle/shutdown timeout.
239
321
  # @version 5.0.0
@@ -243,10 +325,21 @@ module Puma
243
325
  @parsed_bytes == 0
244
326
  end
245
327
 
328
+ def expect_proxy_proto=(val)
329
+ if val
330
+ if @read_header
331
+ @read_proxy = true
332
+ end
333
+ else
334
+ @read_proxy = false
335
+ end
336
+ @expect_proxy_proto = val
337
+ end
338
+
246
339
  private
247
340
 
248
341
  def setup_body
249
- @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
342
+ @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
250
343
 
251
344
  if @env[HTTP_EXPECT] == CONTINUE
252
345
  # TODO allow a hook here to check the headers before
@@ -260,16 +353,27 @@ module Puma
260
353
  body = @parser.body
261
354
 
262
355
  te = @env[TRANSFER_ENCODING2]
263
-
264
356
  if te
265
- if te.include?(",")
266
- te.split(",").each do |part|
267
- if CHUNKED.casecmp(part.strip) == 0
268
- return setup_chunked_body(body)
269
- end
357
+ te_lwr = te.downcase
358
+ if te.include? ','
359
+ te_ary = te_lwr.split ','
360
+ te_count = te_ary.count CHUNKED
361
+ te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
362
+ if te_ary.last == CHUNKED && te_count == 1 && te_valid
363
+ @env.delete TRANSFER_ENCODING2
364
+ return setup_chunked_body body
365
+ elsif te_count >= 1
366
+ raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
367
+ elsif !te_valid
368
+ raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
270
369
  end
271
- elsif CHUNKED.casecmp(te) == 0
272
- return setup_chunked_body(body)
370
+ elsif te_lwr == CHUNKED
371
+ @env.delete TRANSFER_ENCODING2
372
+ return setup_chunked_body body
373
+ elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
374
+ raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
375
+ else
376
+ raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
273
377
  end
274
378
  end
275
379
 
@@ -277,7 +381,12 @@ module Puma
277
381
 
278
382
  cl = @env[CONTENT_LENGTH]
279
383
 
280
- unless cl
384
+ if cl
385
+ # cannot contain characters that are not \d
386
+ if CONTENT_LENGTH_VALUE_INVALID.match? cl
387
+ raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
388
+ end
389
+ else
281
390
  @buffer = body.empty? ? nil : body
282
391
  @body = EmptyBody
283
392
  set_ready
@@ -295,6 +404,7 @@ module Puma
295
404
 
296
405
  if remain > MAX_BODY
297
406
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
407
+ @body.unlink
298
408
  @body.binmode
299
409
  @tempfile = @body
300
410
  else
@@ -307,7 +417,7 @@ module Puma
307
417
 
308
418
  @body_remain = remain
309
419
 
310
- return false
420
+ false
311
421
  end
312
422
 
313
423
  def read_body
@@ -326,7 +436,7 @@ module Puma
326
436
  end
327
437
 
328
438
  begin
329
- chunk = @io.read_nonblock(want)
439
+ chunk = @io.read_nonblock(want, @read_buffer)
330
440
  rescue IO::WaitReadable
331
441
  return false
332
442
  rescue SystemCallError, IOError
@@ -358,7 +468,7 @@ module Puma
358
468
  def read_chunked_body
359
469
  while true
360
470
  begin
361
- chunk = @io.read_nonblock(4096)
471
+ chunk = @io.read_nonblock(4096, @read_buffer)
362
472
  rescue IO::WaitReadable
363
473
  return false
364
474
  rescue SystemCallError, IOError
@@ -386,6 +496,7 @@ module Puma
386
496
  @prev_chunk = ""
387
497
 
388
498
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
499
+ @body.unlink
389
500
  @body.binmode
390
501
  @tempfile = @body
391
502
  @chunked_content_length = 0
@@ -434,7 +545,13 @@ module Puma
434
545
  while !io.eof?
435
546
  line = io.gets
436
547
  if line.end_with?("\r\n")
437
- len = line.strip.to_i(16)
548
+ # Puma doesn't process chunk extensions, but should parse if they're
549
+ # present, which is the reason for the semicolon regex
550
+ chunk_hex = line.strip[/\A[^;]+/]
551
+ if CHUNK_SIZE_INVALID.match? chunk_hex
552
+ raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
553
+ end
554
+ len = chunk_hex.to_i(16)
438
555
  if len == 0
439
556
  @in_last_chunk = true
440
557
  @body.rewind
@@ -465,7 +582,12 @@ module Puma
465
582
 
466
583
  case
467
584
  when got == len
468
- write_chunk(part[0..-3]) # to skip the ending \r\n
585
+ # proper chunked segment must end with "\r\n"
586
+ if part.end_with? CHUNK_VALID_ENDING
587
+ write_chunk(part[0..-3]) # to skip the ending \r\n
588
+ else
589
+ raise HttpParserError, "Chunk size mismatch"
590
+ end
469
591
  when got <= len - 2
470
592
  write_chunk(part)
471
593
  @partial_part_left = len - part.size
@@ -489,10 +611,14 @@ module Puma
489
611
 
490
612
  def set_ready
491
613
  if @body_read_start
492
- @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
614
+ @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
493
615
  end
494
616
  @requests_served += 1
495
617
  @ready = true
496
618
  end
619
+
620
+ def above_http_content_limit(value)
621
+ @http_content_length_limit&.< value
622
+ end
497
623
  end
498
624
  end
@@ -2,27 +2,29 @@
2
2
 
3
3
  module Puma
4
4
  class Cluster < Puma::Runner
5
+ #—————————————————————— DO NOT USE — this class is for internal use only ———
6
+
7
+
5
8
  # This class is instantiated by the `Puma::Cluster` and represents a single
6
9
  # worker process.
7
10
  #
8
11
  # At the core of this class is running an instance of `Puma::Server` which
9
12
  # gets created via the `start_server` method from the `Puma::Runner` class
10
13
  # that this inherits from.
11
- class Worker < Puma::Runner
14
+ class Worker < Puma::Runner # :nodoc:
12
15
  attr_reader :index, :master
13
16
 
14
17
  def initialize(index:, master:, launcher:, pipes:, server: nil)
15
- super launcher, launcher.events
18
+ super(launcher)
16
19
 
17
20
  @index = index
18
21
  @master = master
19
- @launcher = launcher
20
- @options = launcher.options
21
22
  @check_pipe = pipes[:check_pipe]
22
23
  @worker_write = pipes[:worker_write]
23
24
  @fork_pipe = pipes[:fork_pipe]
24
25
  @wakeup = pipes[:wakeup]
25
26
  @server = server
27
+ @hook_data = {}
26
28
  end
27
29
 
28
30
  def run
@@ -34,8 +36,8 @@ module Puma
34
36
  Signal.trap "SIGCHLD", "DEFAULT"
35
37
 
36
38
  Thread.new do
37
- Puma.set_thread_name "worker check pipe"
38
- IO.select [@check_pipe]
39
+ Puma.set_thread_name "wrkr check"
40
+ @check_pipe.wait_readable
39
41
  log "! Detected parent died, dying"
40
42
  exit! 1
41
43
  end
@@ -52,9 +54,17 @@ module Puma
52
54
 
53
55
  # Invoke any worker boot hooks so they can get
54
56
  # things in shape before booting the app.
55
- @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
57
+ @config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
56
58
 
59
+ begin
57
60
  server = @server ||= start_server
61
+ rescue Exception => e
62
+ log "! Unable to start worker"
63
+ log e
64
+ log e.backtrace.join("\n ")
65
+ exit 1
66
+ end
67
+
58
68
  restart_server = Queue.new << true << false
59
69
 
60
70
  fork_worker = @options[:fork_worker] && index == 0
@@ -69,15 +79,14 @@ module Puma
69
79
  end
70
80
 
71
81
  Thread.new do
72
- Puma.set_thread_name "worker fork pipe"
82
+ Puma.set_thread_name "wrkr fork"
73
83
  while (idx = @fork_pipe.gets)
74
84
  idx = idx.to_i
75
85
  if idx == -1 # stop server
76
86
  if restart_server.length > 0
77
87
  restart_server.clear
78
88
  server.begin_restart(true)
79
- @launcher.config.run_hooks :before_refork, nil, @launcher.events
80
- Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
89
+ @config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
81
90
  end
82
91
  elsif idx == 0 # restart server
83
92
  restart_server << true << false
@@ -99,15 +108,20 @@ module Puma
99
108
  begin
100
109
  @worker_write << "b#{Process.pid}:#{index}\n"
101
110
  rescue SystemCallError, IOError
102
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
111
+ Puma::Util.purge_interrupt_queue
103
112
  STDERR.puts "Master seems to have exited, exiting."
104
113
  return
105
114
  end
106
115
 
107
116
  while restart_server.pop
108
117
  server_thread = server.run
118
+
119
+ if @log_writer.debug? && index == 0
120
+ debug_loaded_extensions "Loaded Extensions - worker 0:"
121
+ end
122
+
109
123
  stat_thread ||= Thread.new(@worker_write) do |io|
110
- Puma.set_thread_name "stat payload"
124
+ Puma.set_thread_name "stat pld"
111
125
  base_payload = "p#{Process.pid}"
112
126
 
113
127
  while true
@@ -120,10 +134,10 @@ module Puma
120
134
  payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
121
135
  io << payload
122
136
  rescue IOError
123
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
137
+ Puma::Util.purge_interrupt_queue
124
138
  break
125
139
  end
126
- sleep Const::WORKER_CHECK_INTERVAL
140
+ sleep @options[:worker_check_interval]
127
141
  end
128
142
  end
129
143
  server_thread.join
@@ -131,7 +145,7 @@ module Puma
131
145
 
132
146
  # Invoke any worker shutdown hooks so they can prevent the worker
133
147
  # exiting until any background operations are completed
134
- @launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
148
+ @config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
135
149
  ensure
136
150
  @worker_write << "t#{Process.pid}\n" rescue nil
137
151
  @worker_write.close
@@ -140,7 +154,7 @@ module Puma
140
154
  private
141
155
 
142
156
  def spawn_worker(idx)
143
- @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
157
+ @config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
144
158
 
145
159
  pid = fork do
146
160
  new_worker = Worker.new index: idx,
@@ -158,19 +172,9 @@ module Puma
158
172
  exit! 1
159
173
  end
160
174
 
161
- @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
175
+ @config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
162
176
  pid
163
177
  end
164
-
165
- def wakeup!
166
- return unless @wakeup
167
-
168
- begin
169
- @wakeup.write "!" unless @wakeup.closed?
170
- rescue SystemCallError, IOError
171
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
- end
173
- end
174
178
  end
175
179
  end
176
180
  end
@@ -2,12 +2,15 @@
2
2
 
3
3
  module Puma
4
4
  class Cluster < Runner
5
+ #—————————————————————— DO NOT USE — this class is for internal use only ———
6
+
7
+
5
8
  # This class represents a worker process from the perspective of the puma
6
9
  # master process. It contains information about the process and its health
7
10
  # and it exposes methods to control the process via IPC. It does not
8
11
  # include the actual logic executed by the worker process itself. For that,
9
12
  # see Puma::Cluster::Worker.
10
- class WorkerHandle
13
+ class WorkerHandle # :nodoc:
11
14
  def initialize(idx, pid, phase, options)
12
15
  @index = idx
13
16
  @pid = pid
@@ -31,11 +34,19 @@ module Puma
31
34
  @stage == :booted
32
35
  end
33
36
 
37
+ def uptime
38
+ Time.now - started_at
39
+ end
40
+
34
41
  def boot!
35
42
  @last_checkin = Time.now
36
43
  @stage = :booted
37
44
  end
38
45
 
46
+ def term!
47
+ @term = true
48
+ end
49
+
39
50
  def term?
40
51
  @term
41
52
  end