puma 7.1.0-java → 8.0.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +116 -0
  3. data/README.md +18 -11
  4. data/docs/5.0-Upgrade.md +98 -0
  5. data/docs/6.0-Upgrade.md +56 -0
  6. data/docs/7.0-Upgrade.md +52 -0
  7. data/docs/8.0-Upgrade.md +100 -0
  8. data/docs/deployment.md +58 -23
  9. data/docs/grpc.md +62 -0
  10. data/docs/images/favicon.svg +1 -0
  11. data/docs/images/running-puma.svg +1 -0
  12. data/docs/images/standard-logo.svg +1 -0
  13. data/docs/jungle/README.md +1 -1
  14. data/docs/kubernetes.md +3 -10
  15. data/docs/plugins.md +2 -2
  16. data/docs/signals.md +10 -10
  17. data/docs/stats.md +1 -1
  18. data/docs/systemd.md +3 -3
  19. data/ext/puma_http11/http11_parser.java.rl +51 -65
  20. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
  23. data/ext/puma_http11/puma_http11.c +101 -109
  24. data/lib/puma/app/status.rb +10 -2
  25. data/lib/puma/cli.rb +1 -1
  26. data/lib/puma/client.rb +90 -66
  27. data/lib/puma/client_env.rb +171 -0
  28. data/lib/puma/cluster/worker.rb +10 -9
  29. data/lib/puma/cluster.rb +3 -4
  30. data/lib/puma/configuration.rb +85 -16
  31. data/lib/puma/const.rb +2 -2
  32. data/lib/puma/control_cli.rb +1 -1
  33. data/lib/puma/detect.rb +11 -0
  34. data/lib/puma/dsl.rb +90 -14
  35. data/lib/puma/launcher.rb +7 -7
  36. data/lib/puma/puma_http11.jar +0 -0
  37. data/lib/puma/reactor.rb +3 -12
  38. data/lib/puma/{request.rb → response.rb} +25 -194
  39. data/lib/puma/runner.rb +1 -1
  40. data/lib/puma/server.rb +72 -37
  41. data/lib/puma/server_plugin_control.rb +32 -0
  42. data/lib/puma/single.rb +2 -2
  43. data/lib/puma/thread_pool.rb +129 -23
  44. data/lib/rack/handler/puma.rb +1 -1
  45. data/tools/Dockerfile +13 -5
  46. metadata +17 -7
  47. data/ext/puma_http11/ext_help.h +0 -15
@@ -7,13 +7,16 @@ module Puma
7
7
  # can respond to.
8
8
  class Status
9
9
  OK_STATUS = '{ "status": "ok" }'.freeze
10
+ READ_ONLY_COMMANDS = %w[gc-stats stats].freeze
10
11
 
11
12
  # @param launcher [::Puma::Launcher]
12
13
  # @param token [String, nil] the token used for authentication
14
+ # @param data_only [Boolean] if true, restrict to read-only data commands
13
15
  #
14
- def initialize(launcher, token = nil)
16
+ def initialize(launcher, token: nil, data_only: false)
15
17
  @launcher = launcher
16
18
  @auth_token = token
19
+ @enabled_commands = READ_ONLY_COMMANDS if data_only
17
20
  end
18
21
 
19
22
  # most commands call methods in `::Puma::Launcher` based on command in
@@ -25,8 +28,13 @@ module Puma
25
28
 
26
29
  # resp_type is processed by following case statement, return
27
30
  # is a number (status) or a string used as the body of a 200 response
31
+ command = env['PATH_INFO'][/\/([^\/]+)$/, 1]
32
+ if @enabled_commands && !@enabled_commands.include?(command)
33
+ return rack_response(404, "Command #{command.inspect} unavailable", 'text/plain')
34
+ end
35
+
28
36
  resp_type =
29
- case env['PATH_INFO'][/\/([^\/]+)$/, 1]
37
+ case command
30
38
  when 'stop'
31
39
  @launcher.stop ; 200
32
40
 
data/lib/puma/cli.rb CHANGED
@@ -148,7 +148,7 @@ module Puma
148
148
 
149
149
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
150
150
  "Use -b for more advanced options" do |arg|
151
- user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
151
+ user_config.port arg, Configuration.default_tcp_host
152
152
  end
153
153
 
154
154
  o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
data/lib/puma/client.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'detect'
4
4
  require_relative 'io_buffer'
5
+ require_relative 'client_env'
5
6
  require 'tempfile'
6
7
 
7
8
  if Puma::IS_JRUBY
@@ -20,8 +21,8 @@ module Puma
20
21
  #———————————————————————— DO NOT USE — this class is for internal use only ———
21
22
 
22
23
 
23
- # An instance of this class represents a unique request from a client.
24
- # For example, this could be a web request from a browser or from CURL.
24
+ # An instance of this class wraps a connection/socket.
25
+ # For example, this could be an http request from a browser or from CURL.
25
26
  #
26
27
  # An instance of `Puma::Client` can be used as if it were an IO object
27
28
  # by the reactor. The reactor is expected to call `#to_io`
@@ -29,12 +30,18 @@ module Puma
29
30
  # `IO::try_convert` (which may call `#to_io`) when a new socket is
30
31
  # registered.
31
32
  #
32
- # Instances of this class are responsible for knowing if
33
- # the header and body are fully buffered via the `try_to_finish` method.
33
+ # Instances of this class are responsible for knowing if the request line,
34
+ # headers and body are fully buffered and verified via the `try_to_finish` method.
35
+ # All verification of each request is done in the `Client` object.
34
36
  # They can be used to "time out" a response via the `timeout_at` reader.
35
37
  #
38
+ # Most of the code for env processing and verification is contained
39
+ # in `Puma::ClientEnv`, which is included.
40
+ #
36
41
  class Client # :nodoc:
37
42
 
43
+ include ClientEnv
44
+
38
45
  # this tests all values but the last, which must be chunked
39
46
  ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
40
47
 
@@ -65,7 +72,13 @@ module Puma
65
72
  # no body share this one object since it has no state.
66
73
  EmptyBody = NullIO.new
67
74
 
68
- include Puma::Const
75
+ attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
76
+ :tempfile, :io_buffer, :http_content_length_limit_exceeded,
77
+ :requests_served, :error_status_code
78
+
79
+ attr_writer :peerip, :http_content_length_limit, :supported_http_methods
80
+
81
+ attr_accessor :remote_addr_header, :listener, :env_set_http_version
69
82
 
70
83
  def initialize(io, env=nil)
71
84
  @io = io
@@ -91,7 +104,8 @@ module Puma
91
104
  @hijacked = false
92
105
 
93
106
  @http_content_length_limit = nil
94
- @http_content_length_limit_exceeded = false
107
+ @http_content_length_limit_exceeded = nil
108
+ @error_status_code = nil
95
109
 
96
110
  @peerip = nil
97
111
  @peer_family = nil
@@ -107,14 +121,6 @@ module Puma
107
121
  @read_buffer = String.new # rubocop: disable Performance/UnfreezeString
108
122
  end
109
123
 
110
- attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
111
- :tempfile, :io_buffer, :http_content_length_limit_exceeded,
112
- :requests_served
113
-
114
- attr_writer :peerip, :http_content_length_limit
115
-
116
- attr_accessor :remote_addr_header, :listener
117
-
118
124
  # Remove in Puma 7?
119
125
  def closed?
120
126
  @to_io.closed?
@@ -164,7 +170,8 @@ module Puma
164
170
  @body_remain = 0
165
171
  @peerip = nil if @remote_addr_header
166
172
  @in_last_chunk = false
167
- @http_content_length_limit_exceeded = false
173
+ @http_content_length_limit_exceeded = nil
174
+ @error_status_code = nil
168
175
  end
169
176
 
170
177
  # only used with back-to-back requests contained in the buffer
@@ -174,12 +181,7 @@ module Puma
174
181
 
175
182
  @parsed_bytes = parser_execute
176
183
 
177
- if @parser.finished?
178
- return setup_body
179
- elsif @parsed_bytes >= MAX_HEADER
180
- raise HttpParserError,
181
- "HEADER is longer than allowed, aborting client early."
182
- end
184
+ @parser.finished? ? process_env_body : false
183
185
  end
184
186
  end
185
187
 
@@ -231,17 +233,6 @@ module Puma
231
233
  end
232
234
 
233
235
  def try_to_finish
234
- if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
235
- @http_content_length_limit_exceeded = true
236
- end
237
-
238
- if @http_content_length_limit_exceeded
239
- @buffer = nil
240
- @body = EmptyBody
241
- set_ready
242
- return true
243
- end
244
-
245
236
  return read_body if in_data_phase
246
237
 
247
238
  data = nil
@@ -272,18 +263,7 @@ module Puma
272
263
 
273
264
  @parsed_bytes = parser_execute
274
265
 
275
- if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
276
- @http_content_length_limit_exceeded = true
277
- end
278
-
279
- if @parser.finished?
280
- setup_body
281
- elsif @parsed_bytes >= MAX_HEADER
282
- raise HttpParserError,
283
- "HEADER is longer than allowed, aborting client early."
284
- else
285
- false
286
- end
266
+ @parser.finished? ? process_env_body : false
287
267
  end
288
268
 
289
269
  def eagerly_finish
@@ -303,7 +283,12 @@ module Puma
303
283
  # @return [Integer] bytes of buffer read by parser
304
284
  #
305
285
  def parser_execute
306
- @parser.execute(@env, @buffer, @parsed_bytes)
286
+ ret = @parser.execute(@env, @buffer, @parsed_bytes)
287
+
288
+ if @env[REQUEST_METHOD] && @supported_http_methods != :any && !@supported_http_methods.key?(@env[REQUEST_METHOD])
289
+ raise HttpParserError501, "#{@env[REQUEST_METHOD]} method is not supported"
290
+ end
291
+ ret
307
292
  rescue => e
308
293
  @env[HTTP_CONNECTION] = 'close'
309
294
  raise e unless HttpParserError === e && e.message.include?('non-SSL')
@@ -337,6 +322,15 @@ module Puma
337
322
  end
338
323
  end
339
324
 
325
+ # processes the `env` and the request body
326
+ def process_env_body
327
+ temp = setup_body
328
+ normalize_env
329
+ req_env_post_parse
330
+ raise HttpParserError if @error_status_code
331
+ temp
332
+ end
333
+
340
334
  def timeout!
341
335
  write_error(408) if in_data_phase
342
336
  raise ConnectionError
@@ -353,12 +347,12 @@ module Puma
353
347
  return @peerip if @peerip
354
348
 
355
349
  if @remote_addr_header
356
- hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
350
+ hdr = (@env[@remote_addr_header] || socket_peerip).split(/[\s,]/).first
357
351
  @peerip = hdr
358
352
  return hdr
359
353
  end
360
354
 
361
- @peerip ||= @io.peeraddr.last
355
+ @peerip ||= socket_peerip
362
356
  end
363
357
 
364
358
  def peer_family
@@ -393,19 +387,36 @@ module Puma
393
387
 
394
388
  private
395
389
 
390
+ IPV4_MAPPED_IPV6_PREFIX = "::ffff:"
391
+ private_constant :IPV4_MAPPED_IPV6_PREFIX
392
+
393
+ def socket_peerip
394
+ unmap_ipv6(@io.peeraddr.last)
395
+ end
396
+
397
+ # Converts IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1) back to
398
+ # their IPv4 form. These addresses appear when IPv4 clients connect to
399
+ # a dual-stack IPv6 socket.
400
+ def unmap_ipv6(addr)
401
+ addr.delete_prefix(IPV4_MAPPED_IPV6_PREFIX)
402
+ end
403
+
404
+ # Checks the request `Transfer-Encoding` and/or `Content-Length` to see if
405
+ # they are valid. Raises errors if not, otherwise reads the body.
406
+ # @return [Boolean] true if the body can be completely read, false otherwise
407
+ #
396
408
  def setup_body
397
409
  @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
398
410
 
399
411
  if @env[HTTP_EXPECT] == CONTINUE
400
- # TODO allow a hook here to check the headers before
401
- # going forward
412
+ # TODO allow a hook here to check the headers before going forward
402
413
  @io << HTTP_11_100
403
414
  @io.flush
404
415
  end
405
416
 
406
417
  @read_header = false
407
418
 
408
- body = @parser.body
419
+ parser_body = @parser.body
409
420
 
410
421
  te = @env[TRANSFER_ENCODING2]
411
422
  if te
@@ -422,10 +433,10 @@ module Puma
422
433
  raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
423
434
  end
424
435
  @env.delete TRANSFER_ENCODING2
425
- return setup_chunked_body body
436
+ return setup_chunked_body parser_body
426
437
  elsif te_lwr == CHUNKED
427
438
  @env.delete TRANSFER_ENCODING2
428
- return setup_chunked_body body
439
+ return setup_chunked_body parser_body
429
440
  elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
430
441
  raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
431
442
  else
@@ -440,10 +451,12 @@ module Puma
440
451
  if cl
441
452
  # cannot contain characters that are not \d, or be empty
442
453
  if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
454
+ @error_status_code = 400
455
+ @env[HTTP_CONNECTION] = 'close'
443
456
  raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
444
457
  end
445
458
  else
446
- @buffer = body.empty? ? nil : body
459
+ @buffer = parser_body.empty? ? nil : parser_body
447
460
  @body = EmptyBody
448
461
  set_ready
449
462
  return true
@@ -451,23 +464,25 @@ module Puma
451
464
 
452
465
  content_length = cl.to_i
453
466
 
454
- remain = content_length - body.bytesize
467
+ raise_above_http_content_limit if @http_content_length_limit&.< content_length
468
+
469
+ remain = content_length - parser_body.bytesize
455
470
 
456
471
  if remain <= 0
457
- # Part of the body is a pipelined request OR garbage. We'll deal with that later.
472
+ # Part of the parser_body is a pipelined request OR garbage. We'll deal with that later.
458
473
  if content_length == 0
459
474
  @body = EmptyBody
460
- if body.empty?
475
+ if parser_body.empty?
461
476
  @buffer = nil
462
477
  else
463
- @buffer = body
478
+ @buffer = parser_body
464
479
  end
465
480
  elsif remain == 0
466
- @body = StringIO.new body
481
+ @body = StringIO.new parser_body
467
482
  @buffer = nil
468
483
  else
469
- @body = StringIO.new(body[0,content_length])
470
- @buffer = body[content_length..-1]
484
+ @body = StringIO.new(parser_body[0,content_length])
485
+ @buffer = parser_body[content_length..-1]
471
486
  end
472
487
  set_ready
473
488
  return true
@@ -479,12 +494,12 @@ module Puma
479
494
  @body.binmode
480
495
  @tempfile = @body
481
496
  else
482
- # The body[0,0] trick is to get an empty string in the same
483
- # encoding as body.
484
- @body = StringIO.new body[0,0]
497
+ # The parser_body[0,0] trick is to get an empty string in the same
498
+ # encoding as parser_body.
499
+ @body = StringIO.new parser_body[0,0]
485
500
  end
486
501
 
487
- @body.write body
502
+ @body.write parser_body
488
503
 
489
504
  @body_remain = remain
490
505
 
@@ -577,6 +592,10 @@ module Puma
577
592
 
578
593
  # @version 5.0.0
579
594
  def write_chunk(str)
595
+ if @http_content_length_limit&.< @chunked_content_length + str.bytesize
596
+ raise_above_http_content_limit
597
+ end
598
+
580
599
  @chunked_content_length += @body.write(str)
581
600
  end
582
601
 
@@ -709,8 +728,13 @@ module Puma
709
728
  @ready = true
710
729
  end
711
730
 
712
- def above_http_content_limit(value)
713
- @http_content_length_limit&.< value
731
+ def raise_above_http_content_limit
732
+ @http_content_length_limit_exceeded = true
733
+ @buffer = nil
734
+ @body = EmptyBody
735
+ @error_status_code = 413
736
+ @env[HTTP_CONNECTION] = 'close'
737
+ raise HttpParserError, "Payload Too Large"
714
738
  end
715
739
  end
716
740
  end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+
5
+ #———————————————————————— DO NOT USE — this class is for internal use only ———
6
+
7
+
8
+ # This module is included in `Client`. It contains code to process the `env`
9
+ # before it is passed to the app.
10
+ #
11
+ module ClientEnv # :nodoc:
12
+
13
+ include Puma::Const
14
+
15
+ # Given a Hash +env+ for the request read from +client+, add
16
+ # and fixup keys to comply with Rack's env guidelines.
17
+ # @param env [Hash] see Puma::Client#env, from request
18
+ # @param client [Puma::Client] only needed for Client#peerip
19
+ #
20
+ def normalize_env
21
+ if host = @env[HTTP_HOST]
22
+ # host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
23
+ if colon = host.rindex("]:") # IPV6 with port
24
+ @env[SERVER_NAME] = host[0, colon+1]
25
+ @env[SERVER_PORT] = host[colon+2, host.bytesize]
26
+ elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
27
+ @env[SERVER_NAME] = host[0, colon]
28
+ @env[SERVER_PORT] = host[colon+1, host.bytesize]
29
+ else
30
+ @env[SERVER_NAME] = host
31
+ @env[SERVER_PORT] = default_server_port
32
+ end
33
+ else
34
+ @env[SERVER_NAME] = LOCALHOST
35
+ @env[SERVER_PORT] = default_server_port
36
+ end
37
+
38
+ unless @env[REQUEST_PATH]
39
+ # it might be a dumbass full host request header
40
+ uri = begin
41
+ URI.parse(@env[REQUEST_URI])
42
+ rescue URI::InvalidURIError
43
+ raise Puma::HttpParserError
44
+ end
45
+ @env[REQUEST_PATH] = uri.path
46
+
47
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
48
+ # so only set the env value if there actually is a value.
49
+ @env[QUERY_STRING] = uri.query if uri.query
50
+ end
51
+
52
+ @env[PATH_INFO] = @env[REQUEST_PATH].to_s # #to_s in case it's nil
53
+
54
+ # From https://www.ietf.org/rfc/rfc3875 :
55
+ # "Script authors should be aware that the REMOTE_ADDR and
56
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
57
+ # may not identify the ultimate source of the request.
58
+ # They identify the client for the immediate request to the
59
+ # server; that client may be a proxy, gateway, or other
60
+ # intermediary acting on behalf of the actual source client."
61
+ #
62
+
63
+ unless @env.key?(REMOTE_ADDR)
64
+ begin
65
+ addr = peerip
66
+ rescue Errno::ENOTCONN
67
+ # Client disconnects can result in an inability to get the
68
+ # peeraddr from the socket; default to unspec.
69
+ if peer_family == Socket::AF_INET6
70
+ addr = UNSPECIFIED_IPV6
71
+ else
72
+ addr = UNSPECIFIED_IPV4
73
+ end
74
+ end
75
+
76
+ # Set unix socket addrs to localhost
77
+ if addr.empty?
78
+ addr = peer_family == Socket::AF_INET6 ? LOCALHOST_IPV6 : LOCALHOST_IPV4
79
+ end
80
+
81
+ @env[REMOTE_ADDR] = addr
82
+ end
83
+
84
+ # The legacy HTTP_VERSION header can be sent as a client header.
85
+ # Rack v4 may remove using HTTP_VERSION. If so, remove this line.
86
+ @env[HTTP_VERSION] = @env[SERVER_PROTOCOL] if @env_set_http_version
87
+
88
+ @env[PUMA_SOCKET] = @io
89
+
90
+ if @env[HTTPS_KEY] && @io.peercert
91
+ @env[PUMA_PEERCERT] = @io.peercert
92
+ end
93
+
94
+ @env[HIJACK_P] = true
95
+ @env[HIJACK] = method(:full_hijack).to_proc
96
+
97
+ @env[RACK_INPUT] = @body || EmptyBody
98
+ @env[RACK_URL_SCHEME] ||= default_server_port == PORT_443 ? HTTPS : HTTP
99
+ end
100
+
101
+ # Fixup any headers with `,` in the name to have `_` now. We emit
102
+ # headers with `,` in them during the parse phase to avoid ambiguity
103
+ # with the `-` to `_` conversion for critical headers. But here for
104
+ # compatibility, we'll convert them back. This code is written to
105
+ # avoid allocation in the common case (ie there are no headers
106
+ # with `,` in their names), that's why it has the extra conditionals.
107
+ #
108
+ # @note If a normalized version of a `,` header already exists, we ignore
109
+ # the `,` version. This prevents clobbering headers managed by proxies
110
+ # but not by clients (Like X-Forwarded-For).
111
+ #
112
+ # @param env [Hash] see Puma::Client#env, from request, modifies in place
113
+ # @version 5.0.3
114
+ #
115
+ def req_env_post_parse
116
+ to_delete = nil
117
+ to_add = nil
118
+
119
+ @env.each do |k,v|
120
+ if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
121
+ if to_delete
122
+ to_delete << k
123
+ else
124
+ to_delete = [k]
125
+ end
126
+
127
+ new_k = k.tr(",", "_")
128
+ if @env.key?(new_k)
129
+ next
130
+ end
131
+
132
+ unless to_add
133
+ to_add = {}
134
+ end
135
+
136
+ to_add[new_k] = v
137
+ end
138
+ end
139
+
140
+ if to_delete # rubocop:disable Style/SafeNavigation
141
+ to_delete.each { |k| env.delete(k) }
142
+ end
143
+
144
+ if to_add
145
+ @env.merge! to_add
146
+ end
147
+
148
+ # A rack extension. If the app writes #call'ables to this
149
+ # array, we will invoke them when the request is done.
150
+ #
151
+ env[RACK_AFTER_REPLY] ||= []
152
+ env[RACK_RESPONSE_FINISHED] ||= []
153
+ end
154
+
155
+ HTTP_ON_VALUES = { "on" => true, HTTPS => true }
156
+ private_constant :HTTP_ON_VALUES
157
+
158
+ # @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
159
+ #
160
+ def default_server_port
161
+ if HTTP_ON_VALUES[@env[HTTPS_KEY]] ||
162
+ @env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
163
+ @env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
164
+ @env[HTTP_X_FORWARDED_SSL] == "on"
165
+ PORT_443
166
+ else
167
+ PORT_80
168
+ end
169
+ end
170
+ end
171
+ end
@@ -14,7 +14,7 @@ module Puma
14
14
  class Worker < Puma::Runner # :nodoc:
15
15
  attr_reader :index, :master
16
16
 
17
- def initialize(index:, master:, launcher:, pipes:, server: nil)
17
+ def initialize(index:, master:, launcher:, pipes:, app: nil)
18
18
  super(launcher)
19
19
 
20
20
  @index = index
@@ -23,7 +23,8 @@ module Puma
23
23
  @worker_write = pipes[:worker_write]
24
24
  @fork_pipe = pipes[:fork_pipe]
25
25
  @wakeup = pipes[:wakeup]
26
- @server = server
26
+ @app = app
27
+ @server = nil
27
28
  @hook_data = {}
28
29
  end
29
30
 
@@ -57,7 +58,7 @@ module Puma
57
58
  @config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
58
59
 
59
60
  begin
60
- server = @server ||= start_server
61
+ @server = start_server
61
62
  rescue Exception => e
62
63
  log "! Unable to start worker"
63
64
  log e
@@ -85,7 +86,7 @@ module Puma
85
86
  if idx == -1 # stop server
86
87
  if restart_server.length > 0
87
88
  restart_server.clear
88
- server.begin_restart(true)
89
+ @server.begin_restart(true)
89
90
  @config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
90
91
  end
91
92
  elsif idx == -2 # refork cycle is done
@@ -103,7 +104,7 @@ module Puma
103
104
  Signal.trap "SIGTERM" do
104
105
  @worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
105
106
  restart_server.clear
106
- server.stop
107
+ @server.stop
107
108
  restart_server << false
108
109
  end
109
110
 
@@ -115,7 +116,7 @@ module Puma
115
116
  end
116
117
 
117
118
  while restart_server.pop
118
- server_thread = server.run
119
+ server_thread = @server.run
119
120
 
120
121
  if @log_writer.debug? && index == 0
121
122
  debug_loaded_extensions "Loaded Extensions - worker 0:"
@@ -129,13 +130,13 @@ module Puma
129
130
  begin
130
131
  payload = base_payload.dup
131
132
 
132
- hsh = server.stats
133
+ hsh = @server.stats
133
134
  hsh.each do |k, v|
134
135
  payload << %Q! "#{k}":#{v || 0},!
135
136
  end
136
137
  # sub call properly adds 'closing' string
137
138
  io << payload.sub(/,\z/, " }\n")
138
- server.reset_max
139
+ @server.reset_max
139
140
  rescue IOError
140
141
  break
141
142
  end
@@ -164,7 +165,7 @@ module Puma
164
165
  launcher: @launcher,
165
166
  pipes: { check_pipe: @check_pipe,
166
167
  worker_write: @worker_write },
167
- server: @server
168
+ app: @app
168
169
  new_worker.run
169
170
  end
170
171
 
data/lib/puma/cluster.rb CHANGED
@@ -87,7 +87,7 @@ module Puma
87
87
  end
88
88
 
89
89
  debug "Spawned worker: #{pid}"
90
- @workers << WorkerHandle.new(idx, pid, @phase, @options)
90
+ @workers.insert(idx, WorkerHandle.new(idx, pid, @phase, @options))
91
91
  end
92
92
 
93
93
  if @options[:fork_worker] && all_workers_in_phase?
@@ -186,7 +186,7 @@ module Puma
186
186
  # we need to phase any workers out (which will restart
187
187
  # in the right phase).
188
188
  #
189
- w = @workers.find { |x| x.phase != @phase }
189
+ w = @workers.find { |x| x.phase < @phase }
190
190
 
191
191
  if w
192
192
  if refork
@@ -221,12 +221,11 @@ module Puma
221
221
  pipes[:wakeup] = @wakeup
222
222
  end
223
223
 
224
- server = start_server if preload?
225
224
  new_worker = Worker.new index: index,
226
225
  master: master,
227
226
  launcher: @launcher,
228
227
  pipes: pipes,
229
- server: server
228
+ app: (app if preload?)
230
229
  new_worker.run
231
230
  end
232
231