puma 5.0.2 → 5.0.3

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.

@@ -11,6 +11,7 @@ require 'puma/client'
11
11
  require 'puma/binder'
12
12
  require 'puma/util'
13
13
  require 'puma/io_buffer'
14
+ require 'puma/request'
14
15
 
15
16
  require 'socket'
16
17
  require 'forwardable'
@@ -30,19 +31,31 @@ module Puma
30
31
  class Server
31
32
 
32
33
  include Puma::Const
34
+ include Request
33
35
  extend Forwardable
34
36
 
35
37
  attr_reader :thread
36
38
  attr_reader :events
37
- attr_reader :requests_count # @version 5.0.0
39
+ attr_reader :min_threads, :max_threads # for #stats
40
+ attr_reader :requests_count # @version 5.0.0
41
+
42
+ # @todo the following may be deprecated in the future
43
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
44
+ :leak_stack_on_error,
45
+ :persistent_timeout, :reaping_time
46
+
47
+ # @deprecated v6.0.0
48
+ attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
49
+ :leak_stack_on_error, :min_threads, :max_threads,
50
+ :persistent_timeout, :reaping_time
51
+
38
52
  attr_accessor :app
53
+ attr_accessor :binder
39
54
 
40
- attr_accessor :min_threads
41
- attr_accessor :max_threads
42
- attr_accessor :persistent_timeout
43
- attr_accessor :auto_trim_time
44
- attr_accessor :reaping_time
45
- attr_accessor :first_data_timeout
55
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
56
+ :add_unix_listener, :connected_ports
57
+
58
+ ThreadLocalKey = :puma_server
46
59
 
47
60
  # Create a server for the rack app +app+.
48
61
  #
@@ -52,6 +65,10 @@ module Puma
52
65
  # Server#run returns a thread that you can join on to wait for the server
53
66
  # to do its work.
54
67
  #
68
+ # @note Several instance variables exist so they are available for testing,
69
+ # and have default values set via +fetch+. Normally the values are set via
70
+ # `::Puma::Configuration.puma_default_options`.
71
+ #
55
72
  def initialize(app, events=Events.stdio, options={})
56
73
  @app = app
57
74
  @events = events
@@ -59,24 +76,25 @@ module Puma
59
76
  @check, @notify = nil
60
77
  @status = :stop
61
78
 
62
- @min_threads = 0
63
- @max_threads = 16
64
79
  @auto_trim_time = 30
65
80
  @reaping_time = 1
66
81
 
67
82
  @thread = nil
68
83
  @thread_pool = nil
69
- @early_hints = nil
70
84
 
71
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
72
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
85
+ @options = options
73
86
 
74
- @binder = Binder.new(events)
87
+ @early_hints = options.fetch :early_hints, nil
88
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
89
+ @min_threads = options.fetch :min_threads, 0
90
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
91
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
92
+ @queue_requests = options.fetch :queue_requests, true
75
93
 
76
- @leak_stack_on_error = true
94
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
95
+ @leak_stack_on_error = @options[:environment] ? temp : true
77
96
 
78
- @options = options
79
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
97
+ @binder = Binder.new(events)
80
98
 
81
99
  ENV['RACK_ENV'] ||= "development"
82
100
 
@@ -85,19 +103,18 @@ module Puma
85
103
  @precheck_closing = true
86
104
 
87
105
  @requests_count = 0
88
-
89
- @shutdown_mutex = Mutex.new
90
106
  end
91
107
 
92
- attr_accessor :binder, :leak_stack_on_error, :early_hints
93
-
94
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
95
-
96
108
  def inherit_binder(bind)
97
109
  @binder = bind
98
110
  end
99
111
 
100
112
  class << self
113
+ # @!attribute [r] current
114
+ def current
115
+ Thread.current[ThreadLocalKey]
116
+ end
117
+
101
118
  # :nodoc:
102
119
  # @version 5.0.0
103
120
  def tcp_cork_supported?
@@ -172,10 +189,12 @@ module Puma
172
189
  end
173
190
  end
174
191
 
192
+ # @!attribute [r] backlog
175
193
  def backlog
176
194
  @thread_pool and @thread_pool.backlog
177
195
  end
178
196
 
197
+ # @!attribute [r] running
179
198
  def running
180
199
  @thread_pool and @thread_pool.spawned
181
200
  end
@@ -188,6 +207,7 @@ module Puma
188
207
  # there are 5 threads sitting idle ready to take
189
208
  # a request. If one request comes in, then the
190
209
  # value would be 4 until it finishes processing.
210
+ # @!attribute [r] pool_capacity
191
211
  def pool_capacity
192
212
  @thread_pool and @thread_pool.pool_capacity
193
213
  end
@@ -205,60 +225,19 @@ module Puma
205
225
 
206
226
  @status = :run
207
227
 
208
- @thread_pool = ThreadPool.new(@min_threads,
209
- @max_threads,
210
- ::Puma::IOBuffer) do |client, buffer|
211
-
212
- # Advertise this server into the thread
213
- Thread.current[ThreadLocalKey] = self
214
-
215
- process_now = false
216
-
217
- begin
218
- if @queue_requests
219
- process_now = client.eagerly_finish
220
- else
221
- @thread_pool.with_force_shutdown do
222
- client.finish(@first_data_timeout)
223
- end
224
- process_now = true
225
- end
226
- rescue MiniSSL::SSLError => e
227
- @events.ssl_error e, client.io
228
- client.close
229
-
230
- rescue HttpParserError => e
231
- client.write_error(400)
232
- client.close
233
-
234
- @events.parse_error e, client
235
- rescue EOFError => e
236
- client.close
237
-
238
- # Swallow, do not log
239
- rescue ConnectionError, ThreadPool::ForceShutdown => e
240
- client.close
241
-
242
- @events.connection_error e, client
243
- else
244
- process_now ||= @shutdown_mutex.synchronize do
245
- next true unless @queue_requests
246
- client.set_timeout @first_data_timeout
247
- @reactor.add client
248
- false
249
- end
250
- process_client client, buffer if process_now
251
- end
252
-
253
- process_now
254
- end
228
+ @thread_pool = ThreadPool.new(
229
+ @min_threads,
230
+ @max_threads,
231
+ ::Puma::IOBuffer,
232
+ &method(:process_client)
233
+ )
255
234
 
256
235
  @thread_pool.out_of_band_hook = @options[:out_of_band]
257
236
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
258
237
 
259
238
  if @queue_requests
260
- @reactor = Reactor.new self, @thread_pool
261
- @reactor.run_in_thread
239
+ @reactor = Reactor.new(&method(:reactor_wakeup))
240
+ @reactor.run
262
241
  end
263
242
 
264
243
  if @reaping_time
@@ -269,6 +248,8 @@ module Puma
269
248
  @thread_pool.auto_trim!(@auto_trim_time)
270
249
  end
271
250
 
251
+ @check, @notify = Puma::Util.pipe unless @notify
252
+
272
253
  @events.fire :state, :running
273
254
 
274
255
  if background
@@ -282,8 +263,45 @@ module Puma
282
263
  end
283
264
  end
284
265
 
266
+ # This method is called from the Reactor thread when a queued Client receives data,
267
+ # times out, or when the Reactor is shutting down.
268
+ #
269
+ # It is responsible for ensuring that a request has been completely received
270
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
271
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
272
+ # such as nginx) then the application would be subject to a slow client attack.
273
+ #
274
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
275
+ #
276
+ # The method checks to see if it has the full header and body with
277
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
278
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
279
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
280
+ # The Client is then removed from the reactor (return `true`).
281
+ #
282
+ # If a client object times out, a 408 response is written, its connection is closed,
283
+ # and the object is removed from the reactor (return `true`).
284
+ #
285
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
286
+ # ThreadPool, depending on their current state (#can_close?).
287
+ #
288
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
289
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
290
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
291
+ def reactor_wakeup(client)
292
+ shutdown = !@queue_requests
293
+ if client.try_to_finish || (shutdown && !client.can_close?)
294
+ @thread_pool << client
295
+ elsif shutdown || client.timeout == 0
296
+ client.timeout!
297
+ end
298
+ rescue StandardError => e
299
+ client_error(e, client)
300
+ client.close
301
+ true
302
+ end
303
+
285
304
  def handle_servers
286
- @check, @notify = Puma::Util.pipe unless @notify
287
305
  begin
288
306
  check = @check
289
307
  sockets = [check] + @binder.ios
@@ -333,10 +351,7 @@ module Puma
333
351
  @events.fire :state, @status
334
352
 
335
353
  if queue_requests
336
- @shutdown_mutex.synchronize do
337
- @queue_requests = false
338
- end
339
- @reactor.clear!
354
+ @queue_requests = false
340
355
  @reactor.shutdown
341
356
  end
342
357
  graceful_shutdown if @status == :stop || @status == :restart
@@ -376,27 +391,48 @@ module Puma
376
391
  return false
377
392
  end
378
393
 
379
- # Given a connection on +client+, handle the incoming requests.
394
+ # Given a connection on +client+, handle the incoming requests,
395
+ # or queue the connection in the Reactor if no request is available.
380
396
  #
381
- # This method support HTTP Keep-Alive so it may, depending on if the client
397
+ # This method is called from a ThreadPool worker thread.
398
+ #
399
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
382
400
  # indicates that it supports keep alive, wait for another request before
383
401
  # returning.
384
402
  #
403
+ # Return true if one or more requests were processed.
385
404
  def process_client(client, buffer)
405
+ # Advertise this server into the thread
406
+ Thread.current[ThreadLocalKey] = self
407
+
408
+ clean_thread_locals = @options[:clean_thread_locals]
409
+ close_socket = true
410
+
411
+ requests = 0
412
+
386
413
  begin
414
+ if @queue_requests &&
415
+ !client.eagerly_finish
387
416
 
388
- clean_thread_locals = @options[:clean_thread_locals]
389
- close_socket = true
417
+ client.set_timeout(@first_data_timeout)
418
+ if @reactor.add client
419
+ close_socket = false
420
+ return false
421
+ end
422
+ end
390
423
 
391
- requests = 0
424
+ with_force_shutdown(client) do
425
+ client.finish(@first_data_timeout)
426
+ end
392
427
 
393
428
  while true
429
+ @requests_count += 1
394
430
  case handle_request(client, buffer)
395
431
  when false
396
- return
432
+ break
397
433
  when :async
398
434
  close_socket = false
399
- return
435
+ break
400
436
  when true
401
437
  buffer.reset
402
438
 
@@ -414,47 +450,25 @@ module Puma
414
450
  check_for_more_data = false
415
451
  end
416
452
 
417
- next_request_ready = @thread_pool.with_force_shutdown do
453
+ next_request_ready = with_force_shutdown(client) do
418
454
  client.reset(check_for_more_data)
419
455
  end
420
456
 
421
457
  unless next_request_ready
422
- @shutdown_mutex.synchronize do
423
- return unless @queue_requests
458
+ break unless @queue_requests
459
+ client.set_timeout @persistent_timeout
460
+ if @reactor.add client
424
461
  close_socket = false
425
- client.set_timeout @persistent_timeout
426
- @reactor.add client
427
- return
462
+ break
428
463
  end
429
464
  end
430
465
  end
431
466
  end
432
-
433
- # The client disconnected while we were reading data
434
- rescue ConnectionError, ThreadPool::ForceShutdown
435
- # Swallow them. The ensure tries to close +client+ down
436
-
437
- # SSL handshake error
438
- rescue MiniSSL::SSLError => e
439
- lowlevel_error e, client.env
440
- @events.ssl_error e, client.io
441
- close_socket = true
442
-
443
- # The client doesn't know HTTP well
444
- rescue HttpParserError => e
445
- lowlevel_error(e, client.env)
446
-
447
- client.write_error(400)
448
-
449
- @events.parse_error e, client
450
-
451
- # Server error
467
+ true
452
468
  rescue StandardError => e
453
- lowlevel_error(e, client.env)
454
-
455
- client.write_error(500)
456
-
457
- @events.unknown_error e, nil, "Read"
469
+ client_error(e, client)
470
+ # The ensure tries to close +client+ down
471
+ requests > 0
458
472
  ensure
459
473
  buffer.reset
460
474
 
@@ -469,350 +483,15 @@ module Puma
469
483
  end
470
484
  end
471
485
 
472
- # Given a Hash +env+ for the request read from +client+, add
473
- # and fixup keys to comply with Rack's env guidelines.
474
- #
475
- def normalize_env(env, client)
476
- if host = env[HTTP_HOST]
477
- if colon = host.index(":")
478
- env[SERVER_NAME] = host[0, colon]
479
- env[SERVER_PORT] = host[colon+1, host.bytesize]
480
- else
481
- env[SERVER_NAME] = host
482
- env[SERVER_PORT] = default_server_port(env)
483
- end
484
- else
485
- env[SERVER_NAME] = LOCALHOST
486
- env[SERVER_PORT] = default_server_port(env)
487
- end
488
-
489
- unless env[REQUEST_PATH]
490
- # it might be a dumbass full host request header
491
- uri = URI.parse(env[REQUEST_URI])
492
- env[REQUEST_PATH] = uri.path
493
-
494
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
495
-
496
- # A nil env value will cause a LintError (and fatal errors elsewhere),
497
- # so only set the env value if there actually is a value.
498
- env[QUERY_STRING] = uri.query if uri.query
499
- end
500
-
501
- env[PATH_INFO] = env[REQUEST_PATH]
502
-
503
- # From https://www.ietf.org/rfc/rfc3875 :
504
- # "Script authors should be aware that the REMOTE_ADDR and
505
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
506
- # may not identify the ultimate source of the request.
507
- # They identify the client for the immediate request to the
508
- # server; that client may be a proxy, gateway, or other
509
- # intermediary acting on behalf of the actual source client."
510
- #
511
-
512
- unless env.key?(REMOTE_ADDR)
513
- begin
514
- addr = client.peerip
515
- rescue Errno::ENOTCONN
516
- # Client disconnects can result in an inability to get the
517
- # peeraddr from the socket; default to localhost.
518
- addr = LOCALHOST_IP
519
- end
520
-
521
- # Set unix socket addrs to localhost
522
- addr = LOCALHOST_IP if addr.empty?
523
-
524
- env[REMOTE_ADDR] = addr
525
- end
526
- end
527
-
528
- def default_server_port(env)
529
- if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
530
- PORT_443
531
- else
532
- PORT_80
533
- end
534
- end
535
-
536
- # Takes the request +req+, invokes the Rack application to construct
537
- # the response and writes it back to +req.io+.
538
- #
539
- # The second parameter +lines+ is a IO-like object unique to this thread.
540
- # This is normally an instance of Puma::IOBuffer.
541
- #
542
- # It'll return +false+ when the connection is closed, this doesn't mean
543
- # that the response wasn't successful.
544
- #
545
- # It'll return +:async+ if the connection remains open but will be handled
546
- # elsewhere, i.e. the connection has been hijacked by the Rack application.
547
- #
548
- # Finally, it'll return +true+ on keep-alive connections.
549
- def handle_request(req, lines)
550
- @requests_count +=1
551
-
552
- env = req.env
553
- client = req.io
554
-
555
- return false if closed_socket?(client)
556
-
557
- normalize_env env, req
558
-
559
- env[PUMA_SOCKET] = client
560
-
561
- if env[HTTPS_KEY] && client.peercert
562
- env[PUMA_PEERCERT] = client.peercert
563
- end
564
-
565
- env[HIJACK_P] = true
566
- env[HIJACK] = req
567
-
568
- body = req.body
569
-
570
- head = env[REQUEST_METHOD] == HEAD
571
-
572
- env[RACK_INPUT] = body
573
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
574
-
575
- if @early_hints
576
- env[EARLY_HINTS] = lambda { |headers|
577
- begin
578
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
579
-
580
- headers.each_pair do |k, vs|
581
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
582
- vs.to_s.split(NEWLINE).each do |v|
583
- next if possible_header_injection?(v)
584
- fast_write client, "#{k}: #{v}\r\n"
585
- end
586
- else
587
- fast_write client, "#{k}: #{vs}\r\n"
588
- end
589
- end
590
-
591
- fast_write client, "\r\n".freeze
592
- rescue ConnectionError => e
593
- @events.debug_error e
594
- # noop, if we lost the socket we just won't send the early hints
595
- end
596
- }
597
- end
598
-
599
- # Fixup any headers with , in the name to have _ now. We emit
600
- # headers with , in them during the parse phase to avoid ambiguity
601
- # with the - to _ conversion for critical headers. But here for
602
- # compatibility, we'll convert them back. This code is written to
603
- # avoid allocation in the common case (ie there are no headers
604
- # with , in their names), that's why it has the extra conditionals.
605
-
606
- to_delete = nil
607
- to_add = nil
608
-
609
- env.each do |k,v|
610
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
611
- if to_delete
612
- to_delete << k
613
- else
614
- to_delete = [k]
615
- end
616
-
617
- unless to_add
618
- to_add = {}
619
- end
620
-
621
- to_add[k.tr(",", "_")] = v
622
- end
623
- end
624
-
625
- if to_delete
626
- to_delete.each { |k| env.delete(k) }
627
- env.merge! to_add
628
- end
629
-
630
- # A rack extension. If the app writes #call'ables to this
631
- # array, we will invoke them when the request is done.
632
- #
633
- after_reply = env[RACK_AFTER_REPLY] = []
634
-
635
- begin
636
- begin
637
- status, headers, res_body = @thread_pool.with_force_shutdown do
638
- @app.call(env)
639
- end
640
-
641
- return :async if req.hijacked
642
-
643
- status = status.to_i
644
-
645
- if status == -1
646
- unless headers.empty? and res_body == []
647
- raise "async response must have empty headers and body"
648
- end
649
-
650
- return :async
651
- end
652
- rescue ThreadPool::ForceShutdown => e
653
- @events.unknown_error e, req, "Rack app"
654
- @events.log "Detected force shutdown of a thread"
655
-
656
- status, headers, res_body = lowlevel_error(e, env, 503)
657
- rescue Exception => e
658
- @events.unknown_error e, req, "Rack app"
659
-
660
- status, headers, res_body = lowlevel_error(e, env, 500)
661
- end
662
-
663
- content_length = nil
664
- no_body = head
665
-
666
- if res_body.kind_of? Array and res_body.size == 1
667
- content_length = res_body[0].bytesize
668
- end
669
-
670
- cork_socket client
671
-
672
- line_ending = LINE_END
673
- colon = COLON
674
-
675
- http_11 = env[HTTP_VERSION] == HTTP_11
676
- if http_11
677
- allow_chunked = true
678
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
679
-
680
- # An optimization. The most common response is 200, so we can
681
- # reply with the proper 200 status without having to compute
682
- # the response header.
683
- #
684
- if status == 200
685
- lines << HTTP_11_200
686
- else
687
- lines.append "HTTP/1.1 ", status.to_s, " ",
688
- fetch_status_code(status), line_ending
689
-
690
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
691
- end
692
- else
693
- allow_chunked = false
694
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
695
-
696
- # Same optimization as above for HTTP/1.1
697
- #
698
- if status == 200
699
- lines << HTTP_10_200
700
- else
701
- lines.append "HTTP/1.0 ", status.to_s, " ",
702
- fetch_status_code(status), line_ending
703
-
704
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
705
- end
706
- end
707
-
708
- # regardless of what the client wants, we always close the connection
709
- # if running without request queueing
710
- keep_alive &&= @queue_requests
711
-
712
- response_hijack = nil
713
-
714
- headers.each do |k, vs|
715
- case k.downcase
716
- when CONTENT_LENGTH2
717
- next if possible_header_injection?(vs)
718
- content_length = vs
719
- next
720
- when TRANSFER_ENCODING
721
- allow_chunked = false
722
- content_length = nil
723
- when HIJACK
724
- response_hijack = vs
725
- next
726
- end
727
-
728
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
729
- vs.to_s.split(NEWLINE).each do |v|
730
- next if possible_header_injection?(v)
731
- lines.append k, colon, v, line_ending
732
- end
733
- else
734
- lines.append k, colon, line_ending
735
- end
736
- end
737
-
738
- # HTTP/1.1 & 1.0 assume different defaults:
739
- # - HTTP 1.0 assumes the connection will be closed if not specified
740
- # - HTTP 1.1 assumes the connection will be kept alive if not specified.
741
- # Only set the header if we're doing something which is not the default
742
- # for this protocol version
743
- if http_11
744
- lines << CONNECTION_CLOSE if !keep_alive
745
- else
746
- lines << CONNECTION_KEEP_ALIVE if keep_alive
747
- end
748
-
749
- if no_body
750
- if content_length and status != 204
751
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
752
- end
753
-
754
- lines << line_ending
755
- fast_write client, lines.to_s
756
- return keep_alive
757
- end
758
-
759
- if content_length
760
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
761
- chunked = false
762
- elsif !response_hijack and allow_chunked
763
- lines << TRANSFER_ENCODING_CHUNKED
764
- chunked = true
765
- end
766
-
767
- lines << line_ending
768
-
769
- fast_write client, lines.to_s
770
-
771
- if response_hijack
772
- response_hijack.call client
773
- return :async
774
- end
775
-
776
- begin
777
- res_body.each do |part|
778
- next if part.bytesize.zero?
779
- if chunked
780
- fast_write client, part.bytesize.to_s(16)
781
- fast_write client, line_ending
782
- fast_write client, part
783
- fast_write client, line_ending
784
- else
785
- fast_write client, part
786
- end
787
-
788
- client.flush
789
- end
790
-
791
- if chunked
792
- fast_write client, CLOSE_CHUNKED
793
- client.flush
794
- end
795
- rescue SystemCallError, IOError
796
- raise ConnectionError, "Connection error detected during write"
797
- end
798
-
799
- ensure
800
- uncork_socket client
801
-
802
- body.close
803
- req.tempfile.unlink if req.tempfile
804
- res_body.close if res_body.respond_to? :close
805
-
806
- after_reply.each { |o| o.call }
807
- end
808
-
809
- return keep_alive
486
+ # Triggers a client timeout if the thread-pool shuts down
487
+ # during execution of the provided block.
488
+ def with_force_shutdown(client, &block)
489
+ @thread_pool.with_force_shutdown(&block)
490
+ rescue ThreadPool::ForceShutdown
491
+ client.timeout!
810
492
  end
811
493
 
812
- def fetch_status_code(status)
813
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
814
- end
815
- private :fetch_status_code
494
+ # :nocov:
816
495
 
817
496
  # Given the request +env+ from +client+ and the partial body +body+
818
497
  # plus a potential Content-Length value +cl+, finish reading
@@ -820,6 +499,7 @@ module Puma
820
499
  #
821
500
  # If the body is larger than MAX_BODY, a Tempfile object is used
822
501
  # for the body, otherwise a StringIO is used.
502
+ # @deprecated 6.0.0
823
503
  #
824
504
  def read_body(env, client, body, cl)
825
505
  content_length = cl.to_i
@@ -852,7 +532,7 @@ module Puma
852
532
 
853
533
  remain -= stream.write(chunk)
854
534
 
855
- # Raed the rest of the chunks
535
+ # Read the rest of the chunks
856
536
  while remain > 0
857
537
  chunk = client.readpartial(CHUNK_SIZE)
858
538
  unless chunk
@@ -867,6 +547,25 @@ module Puma
867
547
 
868
548
  return stream
869
549
  end
550
+ # :nocov:
551
+
552
+ # Handle various error types thrown by Client I/O operations.
553
+ def client_error(e, client)
554
+ # Swallow, do not log
555
+ return if [ConnectionError, EOFError].include?(e.class)
556
+
557
+ lowlevel_error(e, client.env)
558
+ case e
559
+ when MiniSSL::SSLError
560
+ @events.ssl_error e, client.io
561
+ when HttpParserError
562
+ client.write_error(400)
563
+ @events.parse_error e, client
564
+ else
565
+ client.write_error(500)
566
+ @events.unknown_error e, nil, "Read"
567
+ end
568
+ end
870
569
 
871
570
  # A fallback rack response if +@app+ raises as exception.
872
571
  #
@@ -942,19 +641,16 @@ module Puma
942
641
  end
943
642
 
944
643
  def notify_safely(message)
945
- @check, @notify = Puma::Util.pipe unless @notify
946
- begin
947
- @notify << message
948
- rescue IOError, NoMethodError, Errno::EPIPE
949
- # The server, in another thread, is shutting down
644
+ @notify << message
645
+ rescue IOError, NoMethodError, Errno::EPIPE
646
+ # The server, in another thread, is shutting down
647
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
648
+ rescue RuntimeError => e
649
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
650
+ if e.message.include?('IOError')
950
651
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
951
- rescue RuntimeError => e
952
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
953
- if e.message.include?('IOError')
954
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
955
- else
956
- raise e
957
- end
652
+ else
653
+ raise e
958
654
  end
959
655
  end
960
656
  private :notify_safely
@@ -977,48 +673,17 @@ module Puma
977
673
  @thread.join if @thread && sync
978
674
  end
979
675
 
980
- def fast_write(io, str)
981
- n = 0
982
- while true
983
- begin
984
- n = io.syswrite str
985
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
986
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
987
- raise ConnectionError, "Socket timeout writing data"
988
- end
989
-
990
- retry
991
- rescue Errno::EPIPE, SystemCallError, IOError
992
- raise ConnectionError, "Socket timeout writing data"
993
- end
994
-
995
- return if n == str.bytesize
996
- str = str.byteslice(n..-1)
997
- end
998
- end
999
- private :fast_write
1000
-
1001
- ThreadLocalKey = :puma_server
1002
-
1003
- def self.current
1004
- Thread.current[ThreadLocalKey]
1005
- end
1006
-
1007
676
  def shutting_down?
1008
677
  @status == :stop || @status == :restart
1009
678
  end
1010
679
 
1011
- def possible_header_injection?(header_value)
1012
- HTTP_INJECTION_REGEX =~ header_value.to_s
1013
- end
1014
- private :possible_header_injection?
1015
-
1016
680
  # List of methods invoked by #stats.
1017
681
  # @version 5.0.0
1018
682
  STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1019
683
 
1020
684
  # Returns a hash of stats about the running server for reporting purposes.
1021
685
  # @version 5.0.0
686
+ # @!attribute [r] stats
1022
687
  def stats
1023
688
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1024
689
  end