puma 5.2.2-java → 5.4.0-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.

data/lib/puma/client.rb CHANGED
@@ -69,6 +69,7 @@ module Puma
69
69
  @hijacked = false
70
70
 
71
71
  @peerip = nil
72
+ @listener = nil
72
73
  @remote_addr_header = nil
73
74
 
74
75
  @body_remain = 0
@@ -81,7 +82,7 @@ module Puma
81
82
 
82
83
  attr_writer :peerip
83
84
 
84
- attr_accessor :remote_addr_header
85
+ attr_accessor :remote_addr_header, :listener
85
86
 
86
87
  def_delegators :@io, :closed?
87
88
 
@@ -126,7 +127,7 @@ module Puma
126
127
  @parsed_bytes = 0
127
128
  @ready = false
128
129
  @body_remain = 0
129
- @peerip = nil
130
+ @peerip = nil if @remote_addr_header
130
131
  @in_last_chunk = false
131
132
 
132
133
  if @buffer
@@ -142,8 +143,7 @@ module Puma
142
143
  return false
143
144
  else
144
145
  begin
145
- if fast_check &&
146
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
146
+ if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
147
147
  return try_to_finish
148
148
  end
149
149
  rescue IOError
@@ -201,13 +201,13 @@ module Puma
201
201
 
202
202
  def eagerly_finish
203
203
  return true if @ready
204
- return false unless IO.select([@to_io], nil, nil, 0)
204
+ return false unless @to_io.wait_readable(0)
205
205
  try_to_finish
206
206
  end
207
207
 
208
208
  def finish(timeout)
209
209
  return if @ready
210
- IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
210
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
211
211
  end
212
212
 
213
213
  def timeout!
@@ -295,6 +295,7 @@ module Puma
295
295
 
296
296
  if remain > MAX_BODY
297
297
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
298
+ @body.unlink
298
299
  @body.binmode
299
300
  @tempfile = @body
300
301
  else
@@ -307,7 +308,7 @@ module Puma
307
308
 
308
309
  @body_remain = remain
309
310
 
310
- return false
311
+ false
311
312
  end
312
313
 
313
314
  def read_body
@@ -386,6 +387,7 @@ module Puma
386
387
  @prev_chunk = ""
387
388
 
388
389
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
390
+ @body.unlink
389
391
  @body.binmode
390
392
  @tempfile = @body
391
393
  @chunked_content_length = 0
data/lib/puma/cluster.rb CHANGED
@@ -43,6 +43,7 @@ module Puma
43
43
  end
44
44
 
45
45
  def start_phased_restart
46
+ @events.fire_on_restart!
46
47
  @phase += 1
47
48
  log "- Starting phased worker restart, phase: #{@phase}"
48
49
 
@@ -317,7 +318,7 @@ module Puma
317
318
 
318
319
  stop_workers
319
320
  stop
320
-
321
+ @events.fire_on_stopped!
321
322
  raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
322
323
  exit 0 # Clean exit, workers were stopped
323
324
  end
@@ -332,16 +333,22 @@ module Puma
332
333
  # This is aligned with the output from Runner, see Runner#output_header
333
334
  log "* Workers: #{@options[:workers]}"
334
335
 
335
- # Threads explicitly marked as fork safe will be ignored.
336
- # Used in Rails, but may be used by anyone.
337
- before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
338
-
339
336
  if preload?
337
+ # Threads explicitly marked as fork safe will be ignored. Used in Rails,
338
+ # but may be used by anyone. Note that we need to explicit
339
+ # Process::Waiter check here because there's a bug in Ruby 2.6 and below
340
+ # where calling thread_variable_get on a Process::Waiter will segfault.
341
+ # We can drop that clause once those versions of Ruby are no longer
342
+ # supported.
343
+ fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
344
+
345
+ before = Thread.list.reject(&fork_safe)
346
+
340
347
  log "* Restarts: (\u2714) hot (\u2716) phased"
341
348
  log "* Preloading application"
342
349
  load_and_bind
343
350
 
344
- after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
351
+ after = Thread.list.reject(&fork_safe)
345
352
 
346
353
  if after.size > before.size
347
354
  threads = (after - before)
@@ -382,6 +389,8 @@ module Puma
382
389
 
383
390
  log "Use Ctrl-C to stop"
384
391
 
392
+ single_worker_warning
393
+
385
394
  redirect_io
386
395
 
387
396
  Plugins.fire_background
@@ -403,19 +412,21 @@ module Puma
403
412
 
404
413
  begin
405
414
  booted = false
415
+ in_phased_restart = false
416
+ workers_not_booted = @options[:workers]
406
417
 
407
418
  while @status == :run
408
419
  begin
409
420
  if @phased_restart
410
421
  start_phased_restart
411
422
  @phased_restart = false
423
+ in_phased_restart = true
424
+ workers_not_booted = @options[:workers]
412
425
  end
413
426
 
414
427
  check_workers
415
428
 
416
- res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
417
-
418
- if res
429
+ if read.wait_readable([0, @next_check - Time.now].max)
419
430
  req = read.read_nonblock(1)
420
431
 
421
432
  @next_check = Time.now if req == "!"
@@ -434,8 +445,9 @@ module Puma
434
445
  case req
435
446
  when "b"
436
447
  w.boot!
437
- log "- Worker #{w.index} (PID: #{pid}) booted, phase: #{w.phase}"
448
+ log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
438
449
  @next_check = Time.now
450
+ workers_not_booted -= 1
439
451
  when "e"
440
452
  # external term, see worker method, Signal.trap "SIGTERM"
441
453
  w.instance_variable_set :@term, true
@@ -453,6 +465,10 @@ module Puma
453
465
  log "! Out-of-sync worker list, no #{pid} worker"
454
466
  end
455
467
  end
468
+ if in_phased_restart && workers_not_booted.zero?
469
+ @events.fire_on_booted!
470
+ in_phased_restart = false
471
+ end
456
472
 
457
473
  rescue Interrupt
458
474
  @status = :stop
@@ -470,6 +486,15 @@ module Puma
470
486
 
471
487
  private
472
488
 
489
+ def single_worker_warning
490
+ return if @options[:workers] != 1 || @options[:silence_single_worker_warning]
491
+
492
+ log "! WARNING: Detected running cluster mode with 1 worker."
493
+ log "! Running Puma in cluster mode with a single worker is often a misconfiguration."
494
+ log "! Consider running Puma in single-mode (workers = 0) in order to reduce memory overhead."
495
+ log "! Set the `silence_single_worker_warning` option to silence this warning message."
496
+ end
497
+
473
498
  # loops thru @workers, removing workers that exited, and calling
474
499
  # `#term` if needed
475
500
  def wait_workers
@@ -499,7 +524,12 @@ module Puma
499
524
  def timeout_workers
500
525
  @workers.each do |w|
501
526
  if !w.term? && w.ping_timeout <= Time.now
502
- log "! Terminating timed out worker: #{w.pid}"
527
+ details = if w.booted?
528
+ "(worker failed to check in within #{@options[:worker_timeout]} seconds)"
529
+ else
530
+ "(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
531
+ end
532
+ log "! Terminating timed out worker #{details}: #{w.pid}"
503
533
  w.kill
504
534
  end
505
535
  end
@@ -33,9 +33,9 @@ module Puma
33
33
  Signal.trap "SIGINT", "IGNORE"
34
34
  Signal.trap "SIGCHLD", "DEFAULT"
35
35
 
36
- Thread.new do
36
+ Thread.new do
37
37
  Puma.set_thread_name "worker check pipe"
38
- IO.select [@check_pipe]
38
+ @check_pipe.wait_readable
39
39
  log "! Detected parent died, dying"
40
40
  exit! 1
41
41
  end
@@ -54,7 +54,14 @@ module Puma
54
54
  # things in shape before booting the app.
55
55
  @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
56
56
 
57
+ begin
57
58
  server = @server ||= start_server
59
+ rescue Exception => e
60
+ log "! Unable to start worker"
61
+ log e.backtrace[0]
62
+ exit 1
63
+ end
64
+
58
65
  restart_server = Queue.new << true << false
59
66
 
60
67
  fork_worker = @options[:fork_worker] && index == 0
@@ -31,6 +31,10 @@ module Puma
31
31
  @stage == :booted
32
32
  end
33
33
 
34
+ def uptime
35
+ Time.now - started_at
36
+ end
37
+
34
38
  def boot!
35
39
  @last_checkin = Time.now
36
40
  @stage = :booted
@@ -193,6 +193,7 @@ module Puma
193
193
  :debug => false,
194
194
  :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
195
195
  :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
196
+ :silence_single_worker_warning => false,
196
197
  :mode => :http,
197
198
  :worker_timeout => DefaultWorkerTimeout,
198
199
  :worker_boot_timeout => DefaultWorkerTimeout,
@@ -342,6 +343,8 @@ module Puma
342
343
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
343
344
 
344
345
  rack_app, rack_options = rack_builder.parse_file(rackup)
346
+ rack_options = rack_options || {}
347
+
345
348
  @options.file_options.merge!(rack_options)
346
349
 
347
350
  config_ru_binds = []
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.2.2".freeze
104
- CODE_NAME = "Fettisdagsbulle".freeze
103
+ PUMA_VERSION = VERSION = "5.4.0".freeze
104
+ CODE_NAME = "Super Flight".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -235,7 +235,7 @@ module Puma
235
235
 
236
236
  EARLY_HINTS = "rack.early_hints".freeze
237
237
 
238
- # Mininum interval to checks worker health
238
+ # Minimum interval to checks worker health
239
239
  WORKER_CHECK_INTERVAL = 5
240
240
 
241
241
  # Illegal character in the key or value of response header
@@ -176,7 +176,9 @@ module Puma
176
176
  when 'tcp'
177
177
  TCPSocket.new uri.host, uri.port
178
178
  when 'unix'
179
- UNIXSocket.new "#{uri.host}#{uri.path}"
179
+ # check for abstract UNIXSocket
180
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
181
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
180
182
  else
181
183
  raise "Invalid scheme: #{uri.scheme}"
182
184
  end
data/lib/puma/dsl.rb CHANGED
@@ -201,7 +201,7 @@ module Puma
201
201
  # * Set the socket backlog depth with +backlog+, default is 1024.
202
202
  # * Set up an SSL certificate with +key+ & +cert+.
203
203
  # * Set whether to optimize for low latency instead of throughput with
204
- # +low_latency+, default is to optimize for low latency. This is done
204
+ # +low_latency+, default is to not optimize for low latency. This is done
205
205
  # via +Socket::TCP_NODELAY+.
206
206
  # * Set socket permissions with +umask+.
207
207
  #
@@ -381,6 +381,13 @@ module Puma
381
381
  @options[:rackup] ||= path.to_s
382
382
  end
383
383
 
384
+ # Allows setting `env['rack.url_scheme']`.
385
+ # Only necessary if X-Forwarded-Proto is not being set by your proxy
386
+ # Normal values are 'http' or 'https'.
387
+ def rack_url_scheme(scheme=nil)
388
+ @options[:rack_url_scheme] = scheme
389
+ end
390
+
384
391
  def early_hints(answer=true)
385
392
  @options[:early_hints] = answer
386
393
  end
@@ -482,6 +489,24 @@ module Puma
482
489
  @options[:workers] = count.to_i
483
490
  end
484
491
 
492
+ # Disable warning message when running in cluster mode with a single worker.
493
+ #
494
+ # Cluster mode has some overhead of running an additional 'control' process
495
+ # in order to manage the cluster. If only running a single worker it is
496
+ # likely not worth paying that overhead vs running in single mode with
497
+ # additional threads instead.
498
+ #
499
+ # There are some scenarios where running cluster mode with a single worker
500
+ # may still be warranted and valid under certain deployment scenarios, see
501
+ # https://github.com/puma/puma/issues/2534
502
+ #
503
+ # Moving from workers = 1 to workers = 0 will save 10-30% of memory use.
504
+ #
505
+ # @note Cluster mode only.
506
+ def silence_single_worker_warning
507
+ @options[:silence_single_worker_warning] = true
508
+ end
509
+
485
510
  # Code to run immediately before master process
486
511
  # forks workers (once on boot). These hooks can block if necessary
487
512
  # to wait for background operations unknown to Puma to finish before
@@ -23,7 +23,7 @@ module Puma
23
23
  new $stderr
24
24
  end
25
25
 
26
- # Print occured error details.
26
+ # Print occurred error details.
27
27
  # +options+ hash with additional options:
28
28
  # - +error+ is an exception object
29
29
  # - +req+ the http request
@@ -34,7 +34,7 @@ module Puma
34
34
  log title(options)
35
35
  end
36
36
 
37
- # Print occured error details only if
37
+ # Print occurred error details only if
38
38
  # environment variable PUMA_DEBUG is defined.
39
39
  # +options+ hash with additional options:
40
40
  # - +error+ is an exception object
@@ -17,7 +17,7 @@ module Puma
17
17
  # be particularly full-featured or fast. It just has to handle the few places
18
18
  # where Puma relies on JSON serialization internally.
19
19
 
20
- module JSON
20
+ module JSONSerialization
21
21
  QUOTE = /"/
22
22
  BACKSLASH = /\\/
23
23
  CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
data/lib/puma/minissl.rb CHANGED
@@ -162,7 +162,7 @@ module Puma
162
162
  end
163
163
 
164
164
  def read_and_drop(timeout = 1)
165
- return :timeout unless IO.select([@socket], nil, nil, timeout)
165
+ return :timeout unless @socket.wait_readable(timeout)
166
166
  case @socket.read_nonblock(1024, exception: false)
167
167
  when nil
168
168
  :eof
data/lib/puma/plugin.rb CHANGED
@@ -91,7 +91,7 @@ module Puma
91
91
  path = ary.first[CALLER_FILE]
92
92
 
93
93
  m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
94
- return m[1]
94
+ m[1]
95
95
  end
96
96
 
97
97
  def self.create(&blk)
Binary file
@@ -5,22 +5,22 @@ module Puma
5
5
  # Add a simple implementation for earlier Ruby versions.
6
6
  #
7
7
  module QueueClose
8
- def initialize
9
- @closed = false
10
- super
11
- end
12
8
  def close
9
+ num_waiting.times {push nil}
13
10
  @closed = true
14
11
  end
15
12
  def closed?
16
- @closed
13
+ @closed ||= false
17
14
  end
18
15
  def push(object)
19
- @closed ||= false
20
- raise ClosedQueueError if @closed
16
+ raise ClosedQueueError if closed?
21
17
  super
22
18
  end
23
19
  alias << push
20
+ def pop(non_block=false)
21
+ return nil if !non_block && closed? && empty?
22
+ super
23
+ end
24
24
  end
25
25
  ::Queue.prepend QueueClose
26
26
  end
@@ -165,7 +165,7 @@ module Puma::Rack
165
165
  require config
166
166
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
167
167
  end
168
- return app, options
168
+ [app, options]
169
169
  end
170
170
 
171
171
  def self.new_from_string(builder_script, file="(rackup)")
data/lib/puma/request.rb CHANGED
@@ -26,9 +26,10 @@ module Puma
26
26
  # Finally, it'll return +true+ on keep-alive connections.
27
27
  # @param client [Puma::Client]
28
28
  # @param lines [Puma::IOBuffer]
29
+ # @param requests [Integer]
29
30
  # @return [Boolean,:async]
30
31
  #
31
- def handle_request(client, lines)
32
+ def handle_request(client, lines, requests)
32
33
  env = client.env
33
34
  io = client.io # io may be a MiniSSL::Socket
34
35
 
@@ -50,7 +51,7 @@ module Puma
50
51
  head = env[REQUEST_METHOD] == HEAD
51
52
 
52
53
  env[RACK_INPUT] = body
53
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
54
+ env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
54
55
 
55
56
  if @early_hints
56
57
  env[EARLY_HINTS] = lambda { |headers|
@@ -110,7 +111,7 @@ module Puma
110
111
 
111
112
  cork_socket io
112
113
 
113
- str_headers(env, status, headers, res_info, lines)
114
+ str_headers(env, status, headers, res_info, lines, requests, client)
114
115
 
115
116
  line_ending = LINE_END
116
117
 
@@ -148,8 +149,9 @@ module Puma
148
149
  res_body.each do |part|
149
150
  next if part.bytesize.zero?
150
151
  if chunked
151
- str = part.bytesize.to_s(16) << line_ending << part << line_ending
152
- fast_write io, str
152
+ fast_write io, (part.bytesize.to_s(16) << line_ending)
153
+ fast_write io, part # part may have different encoding
154
+ fast_write io, line_ending
153
155
  else
154
156
  fast_write io, part
155
157
  end
@@ -174,7 +176,7 @@ module Puma
174
176
  after_reply.each { |o| o.call }
175
177
  end
176
178
 
177
- return res_info[:keep_alive]
179
+ res_info[:keep_alive]
178
180
  end
179
181
 
180
182
  # @param env [Hash] see Puma::Client#env, from request
@@ -199,7 +201,7 @@ module Puma
199
201
  begin
200
202
  n = io.syswrite str
201
203
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
202
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
204
+ unless io.wait_writable WRITE_TIMEOUT
203
205
  raise ConnectionError, "Socket timeout writing data"
204
206
  end
205
207
 
@@ -230,7 +232,11 @@ module Puma
230
232
  #
231
233
  def normalize_env(env, client)
232
234
  if host = env[HTTP_HOST]
233
- if colon = host.index(":")
235
+ # host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
236
+ if colon = host.rindex("]:") # IPV6 with port
237
+ env[SERVER_NAME] = host[0, colon+1]
238
+ env[SERVER_PORT] = host[colon+2, host.bytesize]
239
+ elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
234
240
  env[SERVER_NAME] = host[0, colon]
235
241
  env[SERVER_PORT] = host[colon+1, host.bytesize]
236
242
  else
@@ -362,9 +368,11 @@ module Puma
362
368
  # @param headers [Hash] the headers returned by the Rack application
363
369
  # @param res_info [Hash] used to pass info between this method and #handle_request
364
370
  # @param lines [Puma::IOBuffer] modified inn place
371
+ # @param requests [Integer] number of inline requests handled
372
+ # @param client [Puma::Client]
365
373
  # @version 5.0.3
366
374
  #
367
- def str_headers(env, status, headers, res_info, lines)
375
+ def str_headers(env, status, headers, res_info, lines, requests, client)
368
376
  line_ending = LINE_END
369
377
  colon = COLON
370
378
 
@@ -405,6 +413,14 @@ module Puma
405
413
  # if running without request queueing
406
414
  res_info[:keep_alive] &&= @queue_requests
407
415
 
416
+ # Close the connection after a reasonable number of inline requests
417
+ # if the server is at capacity and the listener has a new connection ready.
418
+ # This allows Puma to service connections fairly when the number
419
+ # of concurrent connections exceeds the size of the threadpool.
420
+ res_info[:keep_alive] &&= requests < @max_fast_inline ||
421
+ @thread_pool.busy_threads < @max_threads ||
422
+ !client.listener.to_io.wait_readable(0)
423
+
408
424
  res_info[:response_hijack] = nil
409
425
 
410
426
  headers.each do |k, vs|