puma 5.6.5 → 6.0.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +102 -11
  3. data/README.md +21 -17
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +1 -3
  7. data/docs/testing_benchmarks_local_files.md +150 -0
  8. data/docs/testing_test_rackup_ci_files.md +36 -0
  9. data/ext/puma_http11/extconf.rb +11 -8
  10. data/ext/puma_http11/http11_parser.c +1 -1
  11. data/ext/puma_http11/http11_parser.h +1 -1
  12. data/ext/puma_http11/http11_parser.java.rl +2 -2
  13. data/ext/puma_http11/http11_parser.rl +2 -2
  14. data/ext/puma_http11/http11_parser_common.rl +2 -2
  15. data/ext/puma_http11/mini_ssl.c +36 -15
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +156 -53
  19. data/ext/puma_http11/puma_http11.c +17 -9
  20. data/lib/puma/app/status.rb +3 -3
  21. data/lib/puma/binder.rb +36 -42
  22. data/lib/puma/cli.rb +11 -17
  23. data/lib/puma/client.rb +22 -12
  24. data/lib/puma/cluster/worker.rb +13 -11
  25. data/lib/puma/cluster/worker_handle.rb +4 -1
  26. data/lib/puma/cluster.rb +28 -25
  27. data/lib/puma/configuration.rb +74 -58
  28. data/lib/puma/const.rb +14 -18
  29. data/lib/puma/control_cli.rb +3 -6
  30. data/lib/puma/detect.rb +2 -0
  31. data/lib/puma/dsl.rb +93 -52
  32. data/lib/puma/error_logger.rb +17 -9
  33. data/lib/puma/events.rb +6 -126
  34. data/lib/puma/io_buffer.rb +29 -4
  35. data/lib/puma/jruby_restart.rb +2 -1
  36. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  37. data/lib/puma/launcher.rb +96 -156
  38. data/lib/puma/log_writer.rb +137 -0
  39. data/lib/puma/minissl/context_builder.rb +23 -12
  40. data/lib/puma/minissl.rb +82 -11
  41. data/lib/puma/plugin/tmp_restart.rb +1 -1
  42. data/lib/puma/rack/builder.rb +4 -4
  43. data/lib/puma/rack_default.rb +1 -1
  44. data/lib/puma/reactor.rb +3 -3
  45. data/lib/puma/request.rb +292 -161
  46. data/lib/puma/runner.rb +41 -20
  47. data/lib/puma/server.rb +53 -66
  48. data/lib/puma/single.rb +10 -10
  49. data/lib/puma/state_file.rb +1 -4
  50. data/lib/puma/systemd.rb +3 -2
  51. data/lib/puma/thread_pool.rb +16 -13
  52. data/lib/puma/util.rb +0 -11
  53. data/lib/puma.rb +11 -8
  54. data/lib/rack/handler/puma.rb +9 -9
  55. metadata +7 -3
  56. data/lib/puma/queue_close.rb +0 -26
data/lib/puma/binder.rb CHANGED
@@ -3,24 +3,15 @@
3
3
  require 'uri'
4
4
  require 'socket'
5
5
 
6
- require 'puma/const'
7
- require 'puma/util'
8
- require 'puma/configuration'
6
+ require_relative 'const'
7
+ require_relative 'util'
8
+ require_relative 'configuration'
9
9
 
10
10
  module Puma
11
11
 
12
12
  if HAS_SSL
13
- require 'puma/minissl'
14
- require 'puma/minissl/context_builder'
15
-
16
- # Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
17
- # NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
18
- # The bug was that it did not require openssl.
19
- # @todo remove when Ruby 2.3 support is dropped
20
- #
21
- if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
22
- require 'openssl'
23
- end
13
+ require_relative 'minissl'
14
+ require_relative 'minissl/context_builder'
24
15
  end
25
16
 
26
17
  class Binder
@@ -28,8 +19,8 @@ module Puma
28
19
 
29
20
  RACK_VERSION = [1,6].freeze
30
21
 
31
- def initialize(events, conf = Configuration.new)
32
- @events = events
22
+ def initialize(log_writer, conf = Configuration.new)
23
+ @log_writer = log_writer
33
24
  @conf = conf
34
25
  @listeners = []
35
26
  @inherited_fds = {}
@@ -38,7 +29,7 @@ module Puma
38
29
 
39
30
  @proto_env = {
40
31
  "rack.version".freeze => RACK_VERSION,
41
- "rack.errors".freeze => events.stderr,
32
+ "rack.errors".freeze => log_writer.stderr,
42
33
  "rack.multithread".freeze => conf.options[:max_threads] > 1,
43
34
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
44
35
  "rack.run_once".freeze => false,
@@ -51,7 +42,6 @@ module Puma
51
42
  # infer properly.
52
43
 
53
44
  "QUERY_STRING".freeze => "",
54
- SERVER_PROTOCOL => HTTP_11,
55
45
  SERVER_SOFTWARE => PUMA_SERVER_STRING,
56
46
  GATEWAY_INTERFACE => CGI_VER
57
47
  }
@@ -80,7 +70,7 @@ module Puma
80
70
  # @!attribute [r] connected_ports
81
71
  # @version 5.0.0
82
72
  def connected_ports
83
- ios.map { |io| io.addr[1] }.uniq
73
+ t = ios.map { |io| io.addr[1] }; t.uniq!; t
84
74
  end
85
75
 
86
76
  # @version 5.0.0
@@ -98,7 +88,7 @@ module Puma
98
88
  # @version 5.0.0
99
89
  #
100
90
  def create_activated_fds(env_hash)
101
- @events.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
91
+ @log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
102
92
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
103
93
  env_hash['LISTEN_FDS'].to_i.times do |index|
104
94
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -106,11 +96,11 @@ module Puma
106
96
  [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
107
97
  rescue ArgumentError # Try to parse as a port/ip
108
98
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
109
- addr = "[#{addr}]" if addr =~ /\:/
99
+ addr = "[#{addr}]" if addr&.include? ':'
110
100
  [:tcp, addr, port]
111
101
  end
112
102
  @activated_sockets[key] = sock
113
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
103
+ @log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
114
104
  end
115
105
  ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
116
106
  end
@@ -152,17 +142,18 @@ module Puma
152
142
  end
153
143
  end
154
144
 
155
- def parse(binds, logger, log_msg = 'Listening')
145
+ def parse(binds, log_writer = nil, log_msg = 'Listening')
146
+ log_writer ||= @log_writer
156
147
  binds.each do |str|
157
148
  uri = URI.parse str
158
149
  case uri.scheme
159
150
  when "tcp"
160
151
  if fd = @inherited_fds.delete(str)
161
152
  io = inherit_tcp_listener uri.host, uri.port, fd
162
- logger.log "* Inherited #{str}"
153
+ log_writer.log "* Inherited #{str}"
163
154
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
164
155
  io = inherit_tcp_listener uri.host, uri.port, sock
165
- logger.log "* Activated #{str}"
156
+ log_writer.log "* Activated #{str}"
166
157
  else
167
158
  ios_len = @ios.length
168
159
  params = Util.parse_query uri.query
@@ -174,7 +165,7 @@ module Puma
174
165
 
175
166
  @ios[ios_len..-1].each do |i|
176
167
  addr = loc_addr_str i
177
- logger.log "* #{log_msg} on http://#{addr}"
168
+ log_writer.log "* #{log_msg} on http://#{addr}"
178
169
  end
179
170
  end
180
171
 
@@ -191,12 +182,12 @@ module Puma
191
182
  if fd = @inherited_fds.delete(str)
192
183
  @unix_paths << path unless abstract || File.exist?(path)
193
184
  io = inherit_unix_listener path, fd
194
- logger.log "* Inherited #{str}"
185
+ log_writer.log "* Inherited #{str}"
195
186
  elsif sock = @activated_sockets.delete([ :unix, path ]) ||
196
187
  @activated_sockets.delete([ :unix, File.realdirpath(path) ])
197
188
  @unix_paths << path unless abstract || File.exist?(path)
198
189
  io = inherit_unix_listener path, sock
199
- logger.log "* Activated #{str}"
190
+ log_writer.log "* Activated #{str}"
200
191
  else
201
192
  umask = nil
202
193
  mode = nil
@@ -220,11 +211,12 @@ module Puma
220
211
 
221
212
  @unix_paths << path unless abstract || File.exist?(path)
222
213
  io = add_unix_listener path, umask, mode, backlog
223
- logger.log "* #{log_msg} on #{str}"
214
+ log_writer.log "* #{log_msg} on #{str}"
224
215
  end
225
216
 
226
217
  @listeners << [str, io]
227
218
  when "ssl"
219
+ cert_key = %w[cert key]
228
220
 
229
221
  raise "Puma compiled without SSL support" unless HAS_SSL
230
222
 
@@ -233,28 +225,29 @@ module Puma
233
225
  # If key and certs are not defined and localhost gem is required.
234
226
  # localhost gem will be used for self signed
235
227
  # Load localhost authority if not loaded.
236
- if params.values_at('cert', 'key').all? { |v| v.to_s.empty? }
228
+ # Ruby 3 `values_at` accepts an array, earlier do not
229
+ if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
237
230
  ctx = localhost_authority && localhost_authority_context
238
231
  end
239
232
 
240
233
  ctx ||=
241
234
  begin
242
235
  # Extract cert_pem and key_pem from options[:store] if present
243
- ['cert', 'key'].each do |v|
244
- if params[v] && params[v].start_with?('store:')
236
+ cert_key.each do |v|
237
+ if params[v]&.start_with?('store:')
245
238
  index = Integer(params.delete(v).split('store:').last)
246
239
  params["#{v}_pem"] = @conf.options[:store][index]
247
240
  end
248
241
  end
249
- MiniSSL::ContextBuilder.new(params, @events).context
242
+ MiniSSL::ContextBuilder.new(params, @log_writer).context
250
243
  end
251
244
 
252
245
  if fd = @inherited_fds.delete(str)
253
- logger.log "* Inherited #{str}"
246
+ log_writer.log "* Inherited #{str}"
254
247
  io = inherit_ssl_listener fd, ctx
255
248
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
256
249
  io = inherit_ssl_listener sock, ctx
257
- logger.log "* Activated #{str}"
250
+ log_writer.log "* Activated #{str}"
258
251
  else
259
252
  ios_len = @ios.length
260
253
  backlog = params.fetch('backlog', 1024).to_i
@@ -262,20 +255,20 @@ module Puma
262
255
 
263
256
  @ios[ios_len..-1].each do |i|
264
257
  addr = loc_addr_str i
265
- logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
258
+ log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
266
259
  end
267
260
  end
268
261
 
269
262
  @listeners << [str, io] if io
270
263
  else
271
- logger.error "Invalid URI: #{str}"
264
+ log_writer.error "Invalid URI: #{str}"
272
265
  end
273
266
  end
274
267
 
275
268
  # If we inherited fds but didn't use them (because of a
276
269
  # configuration change), then be sure to close them.
277
270
  @inherited_fds.each do |str, fd|
278
- logger.log "* Closing unused inherited connection: #{str}"
271
+ log_writer.log "* Closing unused inherited connection: #{str}"
279
272
 
280
273
  begin
281
274
  IO.for_fd(fd).close
@@ -295,7 +288,7 @@ module Puma
295
288
  fds = @ios.map(&:to_i)
296
289
  @activated_sockets.each do |key, sock|
297
290
  next if fds.include? sock.to_i
298
- logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
291
+ log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
299
292
  begin
300
293
  sock.close
301
294
  rescue SystemCallError
@@ -319,7 +312,7 @@ module Puma
319
312
  local_certificates_path = File.expand_path("~/.localhost")
320
313
  [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
321
314
  end
322
- MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @events).context
315
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
323
316
  end
324
317
 
325
318
  # Tell the server to listen on host +host+, port +port+.
@@ -482,9 +475,10 @@ module Puma
482
475
 
483
476
  # @!attribute [r] loopback_addresses
484
477
  def loopback_addresses
485
- Socket.ip_address_list.select do |addrinfo|
478
+ t = Socket.ip_address_list.select do |addrinfo|
486
479
  addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
487
- end.map { |addrinfo| addrinfo.ip_address }.uniq
480
+ end
481
+ t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
488
482
  end
489
483
 
490
484
  def loc_addr_str(io)
data/lib/puma/cli.rb CHANGED
@@ -3,11 +3,11 @@
3
3
  require 'optparse'
4
4
  require 'uri'
5
5
 
6
- require 'puma'
7
- require 'puma/configuration'
8
- require 'puma/launcher'
9
- require 'puma/const'
10
- require 'puma/events'
6
+ require_relative '../puma'
7
+ require_relative 'configuration'
8
+ require_relative 'launcher'
9
+ require_relative 'const'
10
+ require_relative 'log_writer'
11
11
 
12
12
  module Puma
13
13
  class << self
@@ -21,19 +21,13 @@ module Puma
21
21
  # Handles invoke a Puma::Server in a command line style.
22
22
  #
23
23
  class CLI
24
- # @deprecated 6.0.0
25
- KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
26
-
27
24
  # Create a new CLI object using +argv+ as the command line
28
25
  # arguments.
29
26
  #
30
- # +stdout+ and +stderr+ can be set to IO-like objects which
31
- # this object will report status on.
32
- #
33
- def initialize(argv, events=Events.stdio)
27
+ def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
34
28
  @debug = false
35
29
  @argv = argv.dup
36
-
30
+ @log_writer = log_writer
37
31
  @events = events
38
32
 
39
33
  @conf = nil
@@ -69,7 +63,7 @@ module Puma
69
63
  end
70
64
  end
71
65
 
72
- @launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
66
+ @launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
73
67
  end
74
68
 
75
69
  attr_reader :launcher
@@ -83,7 +77,7 @@ module Puma
83
77
 
84
78
  private
85
79
  def unsupported(str)
86
- @events.error(str)
80
+ @log_writer.error(str)
87
81
  raise UnsupportedOption
88
82
  end
89
83
 
@@ -152,7 +146,7 @@ module Puma
152
146
 
153
147
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
154
148
  "Use -b for more advanced options" do |arg|
155
- user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
149
+ user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
156
150
  end
157
151
 
158
152
  o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
@@ -186,7 +180,7 @@ module Puma
186
180
  end
187
181
 
188
182
  o.on "-s", "--silent", "Do not log prompt messages other than errors" do
189
- @events = Events.new NullIO.new, $stderr
183
+ @log_writer = LogWriter.new(NullIO.new, $stderr)
190
184
  end
191
185
 
192
186
  o.on "-S", "--state PATH", "Where to store the state details" do |arg|
data/lib/puma/client.rb CHANGED
@@ -8,7 +8,7 @@ class IO
8
8
  end
9
9
  end
10
10
 
11
- require 'puma/detect'
11
+ require_relative 'detect'
12
12
  require 'tempfile'
13
13
  require 'forwardable'
14
14
 
@@ -25,6 +25,9 @@ module Puma
25
25
 
26
26
  class HttpParserError501 < IOError; end
27
27
 
28
+ #———————————————————————— DO NOT USE — this class is for internal use only ———
29
+
30
+
28
31
  # An instance of this class represents a unique request from a client.
29
32
  # For example, this could be a web request from a browser or from CURL.
30
33
  #
@@ -38,7 +41,7 @@ module Puma
38
41
  # the header and body are fully buffered via the `try_to_finish` method.
39
42
  # They can be used to "time out" a response via the `timeout_at` reader.
40
43
  #
41
- class Client
44
+ class Client # :nodoc:
42
45
 
43
46
  # this tests all values but the last, which must be chunked
44
47
  ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
@@ -63,11 +66,7 @@ module Puma
63
66
  @io = io
64
67
  @to_io = io.to_io
65
68
  @proto_env = env
66
- if !env
67
- @env = nil
68
- else
69
- @env = env.dup
70
- end
69
+ @env = env ? env.dup : nil
71
70
 
72
71
  @parser = HttpParser.new
73
72
  @parsed_bytes = 0
@@ -86,6 +85,7 @@ module Puma
86
85
  @hijacked = false
87
86
 
88
87
  @peerip = nil
88
+ @peer_family = nil
89
89
  @listener = nil
90
90
  @remote_addr_header = nil
91
91
  @expect_proxy_proto = false
@@ -273,7 +273,7 @@ module Puma
273
273
  return @peerip if @peerip
274
274
 
275
275
  if @remote_addr_header
276
- hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
276
+ hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
277
277
  @peerip = hdr
278
278
  return hdr
279
279
  end
@@ -281,6 +281,16 @@ module Puma
281
281
  @peerip ||= @io.peeraddr.last
282
282
  end
283
283
 
284
+ def peer_family
285
+ return @peer_family if @peer_family
286
+
287
+ @peer_family ||= begin
288
+ @io.local_address.afamily
289
+ rescue
290
+ Socket::AF_INET
291
+ end
292
+ end
293
+
284
294
  # Returns true if the persistent connection can be closed immediately
285
295
  # without waiting for the configured idle/shutdown timeout.
286
296
  # @version 5.0.0
@@ -304,7 +314,7 @@ module Puma
304
314
  private
305
315
 
306
316
  def setup_body
307
- @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
317
+ @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
308
318
 
309
319
  if @env[HTTP_EXPECT] == CONTINUE
310
320
  # TODO allow a hook here to check the headers before
@@ -348,7 +358,7 @@ module Puma
348
358
 
349
359
  if cl
350
360
  # cannot contain characters that are not \d
351
- if cl =~ CONTENT_LENGTH_VALUE_INVALID
361
+ if CONTENT_LENGTH_VALUE_INVALID.match? cl
352
362
  raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
353
363
  end
354
364
  else
@@ -513,7 +523,7 @@ module Puma
513
523
  # Puma doesn't process chunk extensions, but should parse if they're
514
524
  # present, which is the reason for the semicolon regex
515
525
  chunk_hex = line.strip[/\A[^;]+/]
516
- if chunk_hex =~ CHUNK_SIZE_INVALID
526
+ if CHUNK_SIZE_INVALID.match? chunk_hex
517
527
  raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
518
528
  end
519
529
  len = chunk_hex.to_i(16)
@@ -576,7 +586,7 @@ module Puma
576
586
 
577
587
  def set_ready
578
588
  if @body_read_start
579
- @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
589
+ @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
580
590
  end
581
591
  @requests_served += 1
582
592
  @ready = true
@@ -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
@@ -52,13 +54,14 @@ 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
 
57
59
  begin
58
60
  server = @server ||= start_server
59
61
  rescue Exception => e
60
62
  log "! Unable to start worker"
61
- log e.backtrace[0]
63
+ log e
64
+ log e.backtrace.join("\n ")
62
65
  exit 1
63
66
  end
64
67
 
@@ -83,8 +86,7 @@ module Puma
83
86
  if restart_server.length > 0
84
87
  restart_server.clear
85
88
  server.begin_restart(true)
86
- @launcher.config.run_hooks :before_refork, nil, @launcher.events
87
- Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
89
+ @config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
88
90
  end
89
91
  elsif idx == 0 # restart server
90
92
  restart_server << true << false
@@ -138,7 +140,7 @@ module Puma
138
140
 
139
141
  # Invoke any worker shutdown hooks so they can prevent the worker
140
142
  # exiting until any background operations are completed
141
- @launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
143
+ @config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
142
144
  ensure
143
145
  @worker_write << "t#{Process.pid}\n" rescue nil
144
146
  @worker_write.close
@@ -147,7 +149,7 @@ module Puma
147
149
  private
148
150
 
149
151
  def spawn_worker(idx)
150
- @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
152
+ @config.run_hooks(:before_worker_fork, idx, @log_writer, @hook_data)
151
153
 
152
154
  pid = fork do
153
155
  new_worker = Worker.new index: idx,
@@ -165,7 +167,7 @@ module Puma
165
167
  exit! 1
166
168
  end
167
169
 
168
- @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
170
+ @config.run_hooks(:after_worker_fork, idx, @log_writer, @hook_data)
169
171
  pid
170
172
  end
171
173
  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
data/lib/puma/cluster.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/runner'
4
- require 'puma/util'
5
- require 'puma/plugin'
6
- require 'puma/cluster/worker_handle'
7
- require 'puma/cluster/worker'
3
+ require_relative 'runner'
4
+ require_relative 'util'
5
+ require_relative 'plugin'
6
+ require_relative 'cluster/worker_handle'
7
+ require_relative 'cluster/worker'
8
8
 
9
9
  require 'time'
10
10
 
@@ -17,8 +17,8 @@ module Puma
17
17
  # via the `spawn_workers` method call. Each worker will have it's own
18
18
  # instance of a `Puma::Server`.
19
19
  class Cluster < Runner
20
- def initialize(cli, events)
21
- super cli, events
20
+ def initialize(launcher)
21
+ super(launcher)
22
22
 
23
23
  @phase = 0
24
24
  @workers = []
@@ -27,6 +27,10 @@ module Puma
27
27
  @phased_restart = false
28
28
  end
29
29
 
30
+ # Returns the list of cluster worker handles.
31
+ # @return [Array<Puma::Cluster::WorkerHandle>]
32
+ attr_reader :workers
33
+
30
34
  def stop_workers
31
35
  log "- Gracefully shutting down workers..."
32
36
  @workers.each { |x| x.term }
@@ -92,7 +96,7 @@ module Puma
92
96
 
93
97
  # @version 5.0.0
94
98
  def spawn_worker(idx, master)
95
- @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
99
+ @config.run_hooks(:before_worker_fork, idx, @log_writer)
96
100
 
97
101
  pid = fork { worker(idx, master) }
98
102
  if !pid
@@ -101,7 +105,7 @@ module Puma
101
105
  exit! 1
102
106
  end
103
107
 
104
- @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
108
+ @config.run_hooks(:after_worker_fork, idx, @log_writer)
105
109
  pid
106
110
  end
107
111
 
@@ -176,10 +180,10 @@ module Puma
176
180
  end
177
181
  end
178
182
 
179
- @next_check = [
180
- @workers.reject(&:term?).map(&:ping_timeout).min,
181
- @next_check
182
- ].compact.min
183
+ t = @workers.reject(&:term?)
184
+ t.map!(&:ping_timeout)
185
+
186
+ @next_check = [t.min, @next_check].compact.min
183
187
  end
184
188
 
185
189
  def worker(index, master)
@@ -209,8 +213,8 @@ module Puma
209
213
  stop
210
214
  end
211
215
 
212
- def phased_restart
213
- return false if @options[:preload_app]
216
+ def phased_restart(refork = false)
217
+ return false if @options[:preload_app] && !refork
214
218
 
215
219
  @phased_restart = true
216
220
  wakeup!
@@ -226,7 +230,7 @@ module Puma
226
230
  def stop_blocked
227
231
  @status = :stop if @status == :run
228
232
  wakeup!
229
- @control.stop(true) if @control
233
+ @control&.stop true
230
234
  Process.waitall
231
235
  end
232
236
 
@@ -265,7 +269,7 @@ module Puma
265
269
  booted_workers: worker_status.count { |w| w[:booted] },
266
270
  old_workers: old_worker_count,
267
271
  worker_status: worker_status,
268
- }
272
+ }.merge(super)
269
273
  end
270
274
 
271
275
  def preload?
@@ -277,7 +281,7 @@ module Puma
277
281
  if (worker = @workers.find { |w| w.index == 0 })
278
282
  worker.phase += 1
279
283
  end
280
- phased_restart
284
+ phased_restart(true)
281
285
  end
282
286
 
283
287
  # We do this in a separate method to keep the lambda scope
@@ -290,7 +294,7 @@ module Puma
290
294
 
291
295
  # Auto-fork after the specified number of requests.
292
296
  if (fork_requests = @options[:fork_worker].to_i) > 0
293
- @launcher.events.register(:ping!) do |w|
297
+ @events.register(:ping!) do |w|
294
298
  fork_worker! if w.index == 0 &&
295
299
  w.phase == 0 &&
296
300
  w.last_status[:requests_count] >= fork_requests
@@ -372,12 +376,12 @@ module Puma
372
376
  else
373
377
  log "* Restarts: (\u2714) hot (\u2714) phased"
374
378
 
375
- unless @launcher.config.app_configured?
379
+ unless @config.app_configured?
376
380
  error "No application configured, nothing to run"
377
381
  exit 1
378
382
  end
379
383
 
380
- @launcher.binder.parse @options[:binds], self
384
+ @launcher.binder.parse @options[:binds]
381
385
  end
382
386
 
383
387
  read, @wakeup = Puma::Util.pipe
@@ -409,8 +413,7 @@ module Puma
409
413
 
410
414
  @master_read, @worker_write = read, @wakeup
411
415
 
412
- @launcher.config.run_hooks :before_fork, nil, @launcher.events
413
- Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
416
+ @config.run_hooks(:before_fork, nil, @log_writer)
414
417
 
415
418
  spawn_workers
416
419
 
@@ -463,9 +466,9 @@ module Puma
463
466
  w.term unless w.term?
464
467
  when "p"
465
468
  w.ping!(result.sub(/^\d+/,'').chomp)
466
- @launcher.events.fire(:ping!, w)
469
+ @events.fire(:ping!, w)
467
470
  if !booted && @workers.none? {|worker| worker.last_status.empty?}
468
- @launcher.events.fire_on_booted!
471
+ @events.fire_on_booted!
469
472
  booted = true
470
473
  end
471
474
  end