puma 5.0.0.beta1-java → 5.0.3-java

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1188 -559
  3. data/README.md +15 -8
  4. data/bin/puma-wild +3 -9
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +10 -7
  7. data/docs/jungle/README.md +0 -4
  8. data/docs/jungle/rc.d/puma +2 -2
  9. data/docs/nginx.md +1 -1
  10. data/docs/restart.md +46 -23
  11. data/docs/signals.md +7 -7
  12. data/docs/systemd.md +1 -1
  13. data/ext/puma_http11/ext_help.h +1 -1
  14. data/ext/puma_http11/http11_parser.c +3 -1
  15. data/ext/puma_http11/http11_parser.rl +3 -1
  16. data/ext/puma_http11/mini_ssl.c +53 -38
  17. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  19. data/ext/puma_http11/puma_http11.c +22 -11
  20. data/lib/puma.rb +16 -0
  21. data/lib/puma/app/status.rb +47 -44
  22. data/lib/puma/binder.rb +40 -12
  23. data/lib/puma/client.rb +68 -82
  24. data/lib/puma/cluster.rb +30 -187
  25. data/lib/puma/cluster/worker.rb +170 -0
  26. data/lib/puma/cluster/worker_handle.rb +83 -0
  27. data/lib/puma/commonlogger.rb +2 -2
  28. data/lib/puma/configuration.rb +9 -7
  29. data/lib/puma/const.rb +2 -1
  30. data/lib/puma/control_cli.rb +2 -0
  31. data/lib/puma/detect.rb +9 -0
  32. data/lib/puma/dsl.rb +77 -39
  33. data/lib/puma/error_logger.rb +97 -0
  34. data/lib/puma/events.rb +37 -31
  35. data/lib/puma/launcher.rb +20 -10
  36. data/lib/puma/minissl.rb +55 -10
  37. data/lib/puma/minissl/context_builder.rb +0 -3
  38. data/lib/puma/puma_http11.jar +0 -0
  39. data/lib/puma/queue_close.rb +26 -0
  40. data/lib/puma/reactor.rb +77 -373
  41. data/lib/puma/request.rb +438 -0
  42. data/lib/puma/runner.rb +7 -19
  43. data/lib/puma/server.rb +229 -506
  44. data/lib/puma/single.rb +3 -2
  45. data/lib/puma/state_file.rb +1 -1
  46. data/lib/puma/thread_pool.rb +32 -5
  47. data/lib/puma/util.rb +12 -0
  48. metadata +12 -10
  49. data/docs/jungle/upstart/README.md +0 -61
  50. data/docs/jungle/upstart/puma-manager.conf +0 -31
  51. data/docs/jungle/upstart/puma.conf +0 -69
  52. data/lib/puma/accept_nonblock.rb +0 -29
@@ -5,15 +5,30 @@ require 'socket'
5
5
 
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
- require 'puma/minissl/context_builder'
8
+ require 'puma/configuration'
9
9
 
10
10
  module Puma
11
+
12
+ if HAS_SSL
13
+ require 'puma/minissl'
14
+ require 'puma/minissl/context_builder'
15
+
16
+ # Odd bug in 'pure Ruby' nio4r verion 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
24
+ end
25
+
11
26
  class Binder
12
27
  include Puma::Const
13
28
 
14
29
  RACK_VERSION = [1,6].freeze
15
30
 
16
- def initialize(events)
31
+ def initialize(events, conf = Configuration.new)
17
32
  @events = events
18
33
  @listeners = []
19
34
  @inherited_fds = {}
@@ -23,8 +38,8 @@ module Puma
23
38
  @proto_env = {
24
39
  "rack.version".freeze => RACK_VERSION,
25
40
  "rack.errors".freeze => events.stderr,
26
- "rack.multithread".freeze => true,
27
- "rack.multiprocess".freeze => false,
41
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
28
43
  "rack.run_once".freeze => false,
29
44
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
45
 
@@ -43,7 +58,12 @@ module Puma
43
58
  @ios = []
44
59
  end
45
60
 
46
- attr_reader :ios, :listeners, :unix_paths, :proto_env, :envs, :activated_sockets, :inherited_fds
61
+ attr_reader :ios
62
+
63
+ # @version 5.0.0
64
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
65
+
66
+ # @version 5.0.0
47
67
  attr_writer :ios, :listeners
48
68
 
49
69
  def env(sock)
@@ -54,10 +74,13 @@ module Puma
54
74
  @ios.each { |i| i.close }
55
75
  end
56
76
 
77
+ # @!attribute [r] connected_ports
78
+ # @version 5.0.0
57
79
  def connected_ports
58
80
  ios.map { |io| io.addr[1] }.uniq
59
81
  end
60
82
 
83
+ # @version 5.0.0
61
84
  def create_inherited_fds(env_hash)
62
85
  env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
63
86
  fd, url = v.split(":", 2)
@@ -68,7 +91,9 @@ module Puma
68
91
  # systemd socket activation.
69
92
  # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
70
93
  # LISTEN_PID = PID of the service process, aka us
71
- # see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
94
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
95
+ # @version 5.0.0
96
+ #
72
97
  def create_activated_fds(env_hash)
73
98
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
74
99
  env_hash['LISTEN_FDS'].to_i.times do |index|
@@ -113,7 +138,7 @@ module Puma
113
138
  i.local_address.ip_unpack.join(':')
114
139
  end
115
140
 
116
- logger.log "* #{log_msg} on tcp://#{addr}"
141
+ logger.log "* #{log_msg} on http://#{addr}"
117
142
  end
118
143
  end
119
144
 
@@ -154,6 +179,9 @@ module Puma
154
179
 
155
180
  @listeners << [str, io]
156
181
  when "ssl"
182
+
183
+ raise "Puma compiled without SSL support" unless HAS_SSL
184
+
157
185
  params = Util.parse_query uri.query
158
186
  ctx = MiniSSL::ContextBuilder.new(params, @events).context
159
187
 
@@ -244,9 +272,8 @@ module Puma
244
272
 
245
273
  def add_ssl_listener(host, port, ctx,
246
274
  optimize_for_latency=true, backlog=1024)
247
- require 'puma/minissl'
248
275
 
249
- MiniSSL.check
276
+ raise "Puma compiled without SSL support" unless HAS_SSL
250
277
 
251
278
  if host == "localhost"
252
279
  loopback_addresses.each do |addr|
@@ -263,7 +290,6 @@ module Puma
263
290
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
264
291
  s.listen backlog
265
292
 
266
-
267
293
  ssl = MiniSSL::Server.new s, ctx
268
294
  env = @proto_env.dup
269
295
  env[HTTPS_KEY] = HTTPS
@@ -274,8 +300,7 @@ module Puma
274
300
  end
275
301
 
276
302
  def inherit_ssl_listener(fd, ctx)
277
- require 'puma/minissl'
278
- MiniSSL.check
303
+ raise "Puma compiled without SSL support" unless HAS_SSL
279
304
 
280
305
  if fd.kind_of? TCPServer
281
306
  s = fd
@@ -366,6 +391,7 @@ module Puma
366
391
  redirects
367
392
  end
368
393
 
394
+ # @version 5.0.0
369
395
  def redirects_for_restart_env
370
396
  listeners.each_with_object({}).with_index do |(listen, memo), i|
371
397
  memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
@@ -374,12 +400,14 @@ module Puma
374
400
 
375
401
  private
376
402
 
403
+ # @!attribute [r] loopback_addresses
377
404
  def loopback_addresses
378
405
  Socket.ip_address_list.select do |addrinfo|
379
406
  addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
380
407
  end.map { |addrinfo| addrinfo.ip_address }.uniq
381
408
  end
382
409
 
410
+ # @version 5.0.0
383
411
  def socket_activation_fd(int)
384
412
  int + 3 # 3 is the magic number you add to follow the SA protocol
385
413
  end
@@ -85,6 +85,13 @@ module Puma
85
85
 
86
86
  def_delegators :@io, :closed?
87
87
 
88
+ # Test to see if io meets a bare minimum of functioning, @to_io needs to be
89
+ # used for MiniSSL::Socket
90
+ def io_ok?
91
+ @to_io.is_a?(::BasicSocket) && !closed?
92
+ end
93
+
94
+ # @!attribute [r] inspect
88
95
  def inspect
89
96
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
90
97
  end
@@ -96,12 +103,18 @@ module Puma
96
103
  env[HIJACK_IO] ||= @io
97
104
  end
98
105
 
106
+ # @!attribute [r] in_data_phase
99
107
  def in_data_phase
100
108
  !@read_header
101
109
  end
102
110
 
103
111
  def set_timeout(val)
104
- @timeout_at = Time.now + val
112
+ @timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
113
+ end
114
+
115
+ # Number of seconds until the timeout elapses.
116
+ def timeout
117
+ [@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
105
118
  end
106
119
 
107
120
  def reset(fast_check=true)
@@ -155,7 +168,9 @@ module Puma
155
168
  data = @io.read_nonblock(CHUNK_SIZE)
156
169
  rescue IO::WaitReadable
157
170
  return false
158
- rescue SystemCallError, IOError, EOFError
171
+ rescue EOFError
172
+ # Swallow error, don't log
173
+ rescue SystemCallError, IOError
159
174
  raise ConnectionError, "Connection error detected during read"
160
175
  end
161
176
 
@@ -184,79 +199,20 @@ module Puma
184
199
  false
185
200
  end
186
201
 
187
- if IS_JRUBY
188
- def jruby_start_try_to_finish
189
- return read_body unless @read_header
190
-
191
- begin
192
- data = @io.sysread_nonblock(CHUNK_SIZE)
193
- rescue OpenSSL::SSL::SSLError => e
194
- return false if e.kind_of? IO::WaitReadable
195
- raise e
196
- end
197
-
198
- # No data means a closed socket
199
- unless data
200
- @buffer = nil
201
- set_ready
202
- raise EOFError
203
- end
204
-
205
- if @buffer
206
- @buffer << data
207
- else
208
- @buffer = data
209
- end
210
-
211
- @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
212
-
213
- if @parser.finished?
214
- return setup_body
215
- elsif @parsed_bytes >= MAX_HEADER
216
- raise HttpParserError,
217
- "HEADER is longer than allowed, aborting client early."
218
- end
219
-
220
- false
221
- end
222
-
223
- def eagerly_finish
224
- return true if @ready
225
-
226
- if @io.kind_of? OpenSSL::SSL::SSLSocket
227
- return true if jruby_start_try_to_finish
228
- end
229
-
230
- return false unless IO.select([@to_io], nil, nil, 0)
231
- try_to_finish
232
- end
233
-
234
- else
235
-
236
- def eagerly_finish
237
- return true if @ready
238
- return false unless IO.select([@to_io], nil, nil, 0)
239
- try_to_finish
240
- end
241
-
242
- # For documentation, see https://github.com/puma/puma/issues/1754
243
- send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
244
- end # IS_JRUBY
202
+ def eagerly_finish
203
+ return true if @ready
204
+ return false unless IO.select([@to_io], nil, nil, 0)
205
+ try_to_finish
206
+ end
245
207
 
246
208
  def finish(timeout)
247
- return true if @ready
248
- until try_to_finish
249
- can_read = begin
250
- IO.select([@to_io], nil, nil, timeout)
251
- rescue ThreadPool::ForceShutdown
252
- nil
253
- end
254
- unless can_read
255
- write_error(408) if in_data_phase
256
- raise ConnectionError
257
- end
258
- end
259
- true
209
+ return if @ready
210
+ IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
211
+ end
212
+
213
+ def timeout!
214
+ write_error(408) if in_data_phase
215
+ raise ConnectionError
260
216
  end
261
217
 
262
218
  def write_error(status_code)
@@ -280,6 +236,8 @@ module Puma
280
236
 
281
237
  # Returns true if the persistent connection can be closed immediately
282
238
  # without waiting for the configured idle/shutdown timeout.
239
+ # @version 5.0.0
240
+ #
283
241
  def can_close?
284
242
  # Allow connection to close if it's received at least one full request
285
243
  # and hasn't received any data for a future request.
@@ -308,8 +266,16 @@ module Puma
308
266
 
309
267
  te = @env[TRANSFER_ENCODING2]
310
268
 
311
- if te && CHUNKED.casecmp(te) == 0
312
- return setup_chunked_body(body)
269
+ if te
270
+ if te.include?(",")
271
+ te.split(",").each do |part|
272
+ if CHUNKED.casecmp(part.strip) == 0
273
+ return setup_chunked_body(body)
274
+ end
275
+ end
276
+ elsif CHUNKED.casecmp(te) == 0
277
+ return setup_chunked_body(body)
278
+ end
313
279
  end
314
280
 
315
281
  @chunked_body = false
@@ -412,7 +378,10 @@ module Puma
412
378
  raise EOFError
413
379
  end
414
380
 
415
- return true if decode_chunk(chunk)
381
+ if decode_chunk(chunk)
382
+ @env[CONTENT_LENGTH] = @chunked_content_length
383
+ return true
384
+ end
416
385
  end
417
386
  end
418
387
 
@@ -424,20 +393,37 @@ module Puma
424
393
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
425
394
  @body.binmode
426
395
  @tempfile = @body
396
+ @chunked_content_length = 0
397
+
398
+ if decode_chunk(body)
399
+ @env[CONTENT_LENGTH] = @chunked_content_length
400
+ return true
401
+ end
402
+ end
427
403
 
428
- return decode_chunk(body)
404
+ # @version 5.0.0
405
+ def write_chunk(str)
406
+ @chunked_content_length += @body.write(str)
429
407
  end
430
408
 
431
409
  def decode_chunk(chunk)
432
410
  if @partial_part_left > 0
433
411
  if @partial_part_left <= chunk.size
434
412
  if @partial_part_left > 2
435
- @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
413
+ write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
436
414
  end
437
415
  chunk = chunk[@partial_part_left..-1]
438
416
  @partial_part_left = 0
439
417
  else
440
- @body << chunk if @partial_part_left > 2 # don't include the last \r\n
418
+ if @partial_part_left > 2
419
+ if @partial_part_left == chunk.size + 1
420
+ # Don't include the last \r
421
+ write_chunk(chunk[0..(@partial_part_left-3)])
422
+ else
423
+ # don't include the last \r\n
424
+ write_chunk(chunk)
425
+ end
426
+ end
441
427
  @partial_part_left -= chunk.size
442
428
  return false
443
429
  end
@@ -484,12 +470,12 @@ module Puma
484
470
 
485
471
  case
486
472
  when got == len
487
- @body << part[0..-3] # to skip the ending \r\n
473
+ write_chunk(part[0..-3]) # to skip the ending \r\n
488
474
  when got <= len - 2
489
- @body << part
475
+ write_chunk(part)
490
476
  @partial_part_left = len - part.size
491
477
  when got == len - 1 # edge where we get just \r but not \n
492
- @body << part[0..-2]
478
+ write_chunk(part[0..-2])
493
479
  @partial_part_left = len - part.size
494
480
  end
495
481
  else
@@ -3,19 +3,16 @@
3
3
  require 'puma/runner'
4
4
  require 'puma/util'
5
5
  require 'puma/plugin'
6
+ require 'puma/cluster/worker_handle'
7
+ require 'puma/cluster/worker'
6
8
 
7
9
  require 'time'
8
- require 'json'
9
10
 
10
11
  module Puma
11
12
  # This class is instantiated by the `Puma::Launcher` and used
12
13
  # to boot and serve a Ruby application when puma "workers" are needed
13
14
  # i.e. when using multi-processes. For example `$ puma -w 5`
14
15
  #
15
- # At the core of this class is running an instance of `Puma::Server` which
16
- # gets created via the `start_server` method from the `Puma::Runner` class
17
- # that this inherits from.
18
- #
19
16
  # An instance of this class will spawn the number of processes passed in
20
17
  # via the `spawn_workers` method call. Each worker will have it's own
21
18
  # instance of a `Puma::Server`.
@@ -62,74 +59,6 @@ module Puma
62
59
  @workers.each { |x| x.hup }
63
60
  end
64
61
 
65
- class Worker
66
- def initialize(idx, pid, phase, options)
67
- @index = idx
68
- @pid = pid
69
- @phase = phase
70
- @stage = :started
71
- @signal = "TERM"
72
- @options = options
73
- @first_term_sent = nil
74
- @started_at = Time.now
75
- @last_checkin = Time.now
76
- @last_status = {}
77
- @term = false
78
- end
79
-
80
- attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
81
- attr_writer :pid, :phase
82
-
83
- def booted?
84
- @stage == :booted
85
- end
86
-
87
- def boot!
88
- @last_checkin = Time.now
89
- @stage = :booted
90
- end
91
-
92
- def term?
93
- @term
94
- end
95
-
96
- def ping!(status)
97
- @last_checkin = Time.now
98
- @last_status = JSON.parse(status, symbolize_names: true)
99
- end
100
-
101
- def ping_timeout
102
- @last_checkin +
103
- (booted? ?
104
- @options[:worker_timeout] :
105
- @options[:worker_boot_timeout]
106
- )
107
- end
108
-
109
- def term
110
- begin
111
- if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
112
- @signal = "KILL"
113
- else
114
- @term ||= true
115
- @first_term_sent ||= Time.now
116
- end
117
- Process.kill @signal, @pid if @pid
118
- rescue Errno::ESRCH
119
- end
120
- end
121
-
122
- def kill
123
- @signal = 'KILL'
124
- term
125
- end
126
-
127
- def hup
128
- Process.kill "HUP", @pid
129
- rescue Errno::ESRCH
130
- end
131
- end
132
-
133
62
  def spawn_workers
134
63
  diff = @options[:workers] - @workers.size
135
64
  return if diff < 1
@@ -150,7 +79,7 @@ module Puma
150
79
  end
151
80
 
152
81
  debug "Spawned worker: #{pid}"
153
- @workers << Worker.new(idx, pid, @phase, @options)
82
+ @workers << WorkerHandle.new(idx, pid, @phase, @options)
154
83
  end
155
84
 
156
85
  if @options[:fork_worker] &&
@@ -160,6 +89,7 @@ module Puma
160
89
  end
161
90
  end
162
91
 
92
+ # @version 5.0.0
163
93
  def spawn_worker(idx, master)
164
94
  @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
165
95
 
@@ -189,6 +119,7 @@ module Puma
189
119
  end
190
120
  end
191
121
 
122
+ # @!attribute [r] next_worker_index
192
123
  def next_worker_index
193
124
  all_positions = 0...@options[:workers]
194
125
  occupied_positions = @workers.map { |w| w.index }
@@ -243,109 +174,23 @@ module Puma
243
174
  end
244
175
 
245
176
  def worker(index, master)
246
- title = "puma: cluster worker #{index}: #{master}"
247
- title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
248
- $0 = title
249
-
250
- Signal.trap "SIGINT", "IGNORE"
251
-
252
- fork_worker = @options[:fork_worker] && index == 0
253
-
254
177
  @workers = []
255
- if !@options[:fork_worker] || fork_worker
256
- @master_read.close
257
- @suicide_pipe.close
258
- @fork_writer.close
259
- end
260
178
 
261
- Thread.new do
262
- Puma.set_thread_name "worker check pipe"
263
- IO.select [@check_pipe]
264
- log "! Detected parent died, dying"
265
- exit! 1
266
- end
179
+ @master_read.close
180
+ @suicide_pipe.close
181
+ @fork_writer.close
267
182
 
268
- # If we're not running under a Bundler context, then
269
- # report the info about the context we will be using
270
- if !ENV['BUNDLE_GEMFILE']
271
- if File.exist?("Gemfile")
272
- log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
273
- elsif File.exist?("gems.rb")
274
- log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
275
- end
276
- end
277
-
278
- # Invoke any worker boot hooks so they can get
279
- # things in shape before booting the app.
280
- @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
281
-
282
- server = @server ||= start_server
283
- restart_server = Queue.new << true << false
284
-
285
- if fork_worker
286
- restart_server.clear
287
- Signal.trap "SIGCHLD" do
288
- Process.wait(-1, Process::WNOHANG) rescue nil
289
- wakeup!
290
- end
291
-
292
- Thread.new do
293
- Puma.set_thread_name "worker fork pipe"
294
- while (idx = @fork_pipe.gets)
295
- idx = idx.to_i
296
- if idx == -1 # stop server
297
- if restart_server.length > 0
298
- restart_server.clear
299
- server.begin_restart(true)
300
- @launcher.config.run_hooks :before_refork, nil, @launcher.events
301
- nakayoshi_gc
302
- end
303
- elsif idx == 0 # restart server
304
- restart_server << true << false
305
- else # fork worker
306
- pid = spawn_worker(idx, master)
307
- @worker_write << "f#{pid}:#{idx}\n" rescue nil
308
- end
309
- end
310
- end
311
- end
312
-
313
- Signal.trap "SIGTERM" do
314
- @worker_write << "e#{Process.pid}\n" rescue nil
315
- server.stop
316
- restart_server << false
317
- end
318
-
319
- begin
320
- @worker_write << "b#{Process.pid}:#{index}\n"
321
- rescue SystemCallError, IOError
322
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
323
- STDERR.puts "Master seems to have exited, exiting."
324
- return
325
- end
326
-
327
- Thread.new(@worker_write) do |io|
328
- Puma.set_thread_name "stat payload"
329
-
330
- while true
331
- sleep Const::WORKER_CHECK_INTERVAL
332
- begin
333
- io << "p#{Process.pid}#{server.stats.to_json}\n"
334
- rescue IOError
335
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
336
- break
337
- end
338
- end
183
+ pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
184
+ if @options[:fork_worker]
185
+ pipes[:fork_pipe] = @fork_pipe
186
+ pipes[:wakeup] = @wakeup
339
187
  end
340
188
 
341
- server.run.join while restart_server.pop
342
-
343
- # Invoke any worker shutdown hooks so they can prevent the worker
344
- # exiting until any background operations are completed
345
- @launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
346
- ensure
347
- @worker_write << "t#{Process.pid}\n" rescue nil
348
- @worker_write.close
189
+ new_worker = Worker.new index: index,
190
+ master: master,
191
+ launcher: @launcher,
192
+ pipes: pipes
193
+ new_worker.run
349
194
  end
350
195
 
351
196
  def restart
@@ -387,6 +232,7 @@ module Puma
387
232
 
388
233
  # Inside of a child process, this will return all zeroes, as @workers is only populated in
389
234
  # the master process.
235
+ # @!attribute [r] stats
390
236
  def stats
391
237
  old_worker_count = @workers.count { |w| w.phase != @phase }
392
238
  worker_status = @workers.map do |w|
@@ -415,6 +261,7 @@ module Puma
415
261
  @options[:preload_app]
416
262
  end
417
263
 
264
+ # @version 5.0.0
418
265
  def fork_worker!
419
266
  if (worker = @workers.find { |w| w.index == 0 })
420
267
  worker.phase += 1
@@ -540,7 +387,7 @@ module Puma
540
387
  @master_read, @worker_write = read, @wakeup
541
388
 
542
389
  @launcher.config.run_hooks :before_fork, nil, @launcher.events
543
- nakayoshi_gc
390
+ Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
544
391
 
545
392
  spawn_workers
546
393
 
@@ -548,9 +395,9 @@ module Puma
548
395
  stop
549
396
  end
550
397
 
551
- @launcher.events.fire_on_booted!
552
-
553
398
  begin
399
+ booted = false
400
+
554
401
  while @status == :run
555
402
  begin
556
403
  if @phased_restart
@@ -591,6 +438,10 @@ module Puma
591
438
  when "p"
592
439
  w.ping!(result.sub(/^\d+/,'').chomp)
593
440
  @launcher.events.fire(:ping!, w)
441
+ if !booted && @workers.none? {|worker| worker.last_status.empty?}
442
+ @launcher.events.fire_on_booted!
443
+ booted = true
444
+ end
594
445
  end
595
446
  else
596
447
  log "! Out-of-sync worker list, no #{pid} worker"
@@ -628,7 +479,9 @@ module Puma
628
479
  rescue Errno::ECHILD
629
480
  begin
630
481
  Process.kill(0, w.pid)
631
- false # child still alive, but has another parent
482
+ # child still alive but has another parent (e.g., using fork_worker)
483
+ w.term if w.term?
484
+ false
632
485
  rescue Errno::ESRCH, Errno::EPERM
633
486
  true # child is already terminated
634
487
  end
@@ -636,6 +489,7 @@ module Puma
636
489
  end
637
490
  end
638
491
 
492
+ # @version 5.0.0
639
493
  def timeout_workers
640
494
  @workers.each do |w|
641
495
  if !w.term? && w.ping_timeout <= Time.now
@@ -644,16 +498,5 @@ module Puma
644
498
  end
645
499
  end
646
500
  end
647
-
648
- def nakayoshi_gc
649
- return unless @options[:nakayoshi_fork]
650
- log "! Promoting existing objects to old generation..."
651
- 4.times { GC.start(full_mark: false) }
652
- if GC.respond_to?(:compact)
653
- log "! Compacting..."
654
- GC.compact
655
- end
656
- log "! Friendly fork preparation complete."
657
- end
658
501
  end
659
502
  end