puma 7.2.2-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.
data/lib/puma/server.rb CHANGED
@@ -11,7 +11,7 @@ require_relative 'reactor'
11
11
  require_relative 'client'
12
12
  require_relative 'binder'
13
13
  require_relative 'util'
14
- require_relative 'request'
14
+ require_relative 'response'
15
15
  require_relative 'configuration'
16
16
  require_relative 'cluster_accept_loop_delay'
17
17
 
@@ -31,15 +31,15 @@ module Puma
31
31
  # Each `Puma::Server` will have one reactor and one thread pool.
32
32
  class Server
33
33
  module FiberPerRequest
34
- def handle_request(client, requests)
34
+ def handle_request(processor, client, requests)
35
35
  Fiber.new do
36
36
  super
37
37
  end.resume
38
38
  end
39
39
  end
40
40
 
41
- include Puma::Const
42
- include Request
41
+ include Const
42
+ include Response
43
43
 
44
44
  attr_reader :options
45
45
  attr_reader :thread
@@ -51,7 +51,7 @@ module Puma
51
51
  # @todo the following may be deprecated in the future
52
52
  attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
53
53
  :leak_stack_on_error,
54
- :persistent_timeout, :reaping_time, :shutdown_body_timeout
54
+ :persistent_timeout, :reaping_time
55
55
 
56
56
  attr_accessor :app
57
57
  attr_accessor :binder
@@ -98,8 +98,6 @@ module Puma
98
98
  @early_hints = @options[:early_hints]
99
99
  @first_data_timeout = @options[:first_data_timeout]
100
100
  @persistent_timeout = @options[:persistent_timeout]
101
- @shutdown_body_timeout = @options[:shutdown_body_timeout]
102
- @shutdown_body_deadline = nil
103
101
  @idle_timeout = @options[:idle_timeout]
104
102
  @min_threads = @options[:min_threads]
105
103
  @max_threads = @options[:max_threads]
@@ -260,9 +258,10 @@ module Puma
260
258
  @events.fire :state, :booting
261
259
 
262
260
  @status = :run
263
- @shutdown_body_deadline = nil
264
261
 
265
- @thread_pool = ThreadPool.new(thread_name, options, server: self) { |client| process_client client }
262
+ @thread_pool = ThreadPool.new(thread_name, options, server: self) do |processor, client|
263
+ process_client(processor, client)
264
+ end
266
265
 
267
266
  if @queue_requests
268
267
  @reactor = Reactor.new(@io_selector_backend) { |c|
@@ -307,15 +306,12 @@ module Puma
307
306
  # The method checks to see if it has the full header and body with
308
307
  # the `Puma::Client#try_to_finish` method. If the full request has been sent,
309
308
  # then the request is passed to the ThreadPool (`@thread_pool << client`)
310
- # so that a "worker thread" can pick up the request and begin to execute application logic.
309
+ # so that a "processor thread" can pick up the request and begin to execute application logic.
311
310
  # The Client is then removed from the reactor (return `true`).
312
311
  #
313
312
  # If a client object times out, a 408 response is written, its connection is closed,
314
313
  # and the object is removed from the reactor (return `true`).
315
314
  #
316
- # If the request cannot be read, the Client is passed to the ThreadPool with its
317
- # `reactor_error` set, so that a processor thread writes the error response.
318
- #
319
315
  # If the Reactor is shutting down, all Clients are either timed out or passed to the
320
316
  # ThreadPool, depending on their current state (#can_close?).
321
317
  #
@@ -324,32 +320,17 @@ module Puma
324
320
  # will wake up and again be checked to see if it's ready to be passed to the thread pool.
325
321
  def reactor_wakeup(client)
326
322
  shutdown = !@queue_requests
327
- read = client.bytes_read
328
- if client.try_to_finish { shutdown_body_limit } || (shutdown && !client.can_close?)
329
- hand_to_thread_pool client
323
+ if client.try_to_finish || (shutdown && !client.can_close?)
324
+ @thread_pool << client
330
325
  elsif shutdown || client.timeout == 0
331
326
  client.timeout!
332
327
  else
333
- client.set_timeout(@first_data_timeout) if client.bytes_read > read
328
+ client.set_timeout(@first_data_timeout)
334
329
  false
335
330
  end
336
- rescue ConnectionError, EOFError
337
- close_client_safely(client)
338
- true
339
331
  rescue StandardError => e
340
- # Writing the error response may block on a client that is not reading,
341
- # so leave it to a thread pool thread.
342
- client.reactor_error = e
343
- hand_to_thread_pool client
344
- end
345
-
346
- def hand_to_thread_pool(client)
347
- @thread_pool << client
348
- true
349
- rescue ThreadError
350
- true
351
- rescue StandardError
352
- close_client_safely client
332
+ client_error(e, client)
333
+ close_client_safely(client)
353
334
  true
354
335
  end
355
336
 
@@ -380,16 +361,13 @@ module Puma
380
361
  ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
381
362
  unless ios
382
363
  unless shutting_down?
364
+ @idle_timeout_reached = true
365
+
383
366
  if @clustered
384
- unless @idle_timeout_reached
385
- @idle_timeout_reached = true
386
- @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
387
- end
367
+ @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
388
368
  next
389
369
  else
390
- @idle_timeout_reached = true
391
370
  @log_writer.log "- Idle timeout reached"
392
- shutdown_body_deadline
393
371
  @status = :stop
394
372
  end
395
373
  end
@@ -423,11 +401,9 @@ module Puma
423
401
  sock.accept_nonblock
424
402
  rescue IO::WaitReadable
425
403
  next
426
- rescue Errno::EMFILE, Errno::ENFILE, Errno::ENOBUFS, Errno::ENOMEM => e
427
- accept_resource_error e
428
- next
429
404
  end
430
405
  drain += 1 if shutting_down?
406
+
431
407
  client = new_client(io, sock)
432
408
  client.send(addr_send_name, addr_value) if addr_value
433
409
  pool << client
@@ -437,17 +413,11 @@ module Puma
437
413
  # In the case that any of the sockets are unexpectedly close.
438
414
  raise
439
415
  rescue StandardError => e
440
- begin
441
- @log_writer.unknown_error e, nil, "Listen loop"
442
- rescue StandardError
443
- end
416
+ @log_writer.unknown_error e, nil, "Listen loop"
444
417
  end
445
418
  end
446
419
 
447
- begin
448
- @log_writer.debug "Drained #{drain} additional connections." if drain
449
- rescue StandardError
450
- end
420
+ @log_writer.debug "Drained #{drain} additional connections." if drain
451
421
  @events.fire :state, @status
452
422
 
453
423
  if queue_requests
@@ -457,10 +427,7 @@ module Puma
457
427
 
458
428
  graceful_shutdown if @status == :stop || @status == :restart
459
429
  rescue Exception => e
460
- begin
461
- @log_writer.unknown_error e, nil, "Exception handling servers"
462
- rescue StandardError
463
- end
430
+ @log_writer.unknown_error e, nil, "Exception handling servers"
464
431
  ensure
465
432
  # Errno::EBADF is infrequently raised
466
433
  [@check, @notify].each do |io|
@@ -480,7 +447,9 @@ module Puma
480
447
  def new_client(io, sock)
481
448
  client = Client.new(io, @binder.env(sock))
482
449
  client.listener = sock
450
+ client.env_set_http_version = @env_set_http_version
483
451
  client.http_content_length_limit = @http_content_length_limit
452
+ client.supported_http_methods = @supported_http_methods
484
453
  client
485
454
  end
486
455
 
@@ -490,14 +459,12 @@ module Puma
490
459
 
491
460
  case cmd
492
461
  when STOP_COMMAND
493
- shutdown_body_deadline
494
462
  @status = :stop
495
463
  return true
496
464
  when HALT_COMMAND
497
465
  @status = :halt
498
466
  return true
499
467
  when RESTART_COMMAND
500
- shutdown_body_deadline
501
468
  @status = :restart
502
469
  return true
503
470
  end
@@ -508,22 +475,20 @@ module Puma
508
475
  # Given a connection on +client+, handle the incoming requests,
509
476
  # or queue the connection in the Reactor if no request is available.
510
477
  #
511
- # This method is called from a ThreadPool worker thread.
478
+ # This method is called from a ThreadPool processor thread.
512
479
  #
513
480
  # This method supports HTTP Keep-Alive so it may, depending on if the client
514
481
  # indicates that it supports keep alive, wait for another request before
515
482
  # returning.
516
483
  #
517
484
  # Return true if one or more requests were processed.
518
- def process_client(client)
485
+ def process_client(processor, client)
519
486
  close_socket = true
520
487
 
521
488
  requests = 0
522
489
 
523
490
  begin
524
- raise client.reactor_error if client.reactor_error
525
-
526
- if @queue_requests && !client.eagerly_finish { shutdown_body_limit }
491
+ if @queue_requests && !client.eagerly_finish
527
492
 
528
493
  client.set_timeout(@first_data_timeout)
529
494
  if @reactor.add client
@@ -533,14 +498,14 @@ module Puma
533
498
  end
534
499
 
535
500
  with_force_shutdown(client) do
536
- client.finish(@first_data_timeout) { shutdown_body_limit }
501
+ client.finish(@first_data_timeout)
537
502
  end
538
503
 
539
504
  can_loop = true
540
505
  while can_loop
541
506
  can_loop = false
542
507
  @requests_count += 1
543
- case handle_request(client, requests + 1)
508
+ case handle_request(processor, client, requests + 1)
544
509
  when :close
545
510
  when :async
546
511
  close_socket = false
@@ -554,7 +519,7 @@ module Puma
554
519
  next_request_ready = if client.has_back_to_back_requests?
555
520
  with_force_shutdown(client) { client.process_back_to_back_requests }
556
521
  else
557
- with_force_shutdown(client) { client.eagerly_finish { shutdown_body_limit } }
522
+ with_force_shutdown(client) { client.eagerly_finish }
558
523
  end
559
524
 
560
525
  if next_request_ready
@@ -586,25 +551,6 @@ module Puma
586
551
  end
587
552
  end
588
553
 
589
- # Accepting fails while the process is out of file descriptors or memory.
590
- # The connection stays in the backlog, so retrying at once would spin and
591
- # log at full speed. Wait briefly and log at most once per second.
592
- def accept_resource_error(error)
593
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
594
- if @accept_error_logged_at.nil? || now - @accept_error_logged_at >= 1
595
- suffix = @accept_errors_suppressed.to_i > 0 ? " (#{@accept_errors_suppressed} more suppressed)" : ""
596
- @accept_error_logged_at = now
597
- @accept_errors_suppressed = 0
598
- begin
599
- @log_writer.unknown_error error, nil, "Listen loop#{suffix}"
600
- rescue StandardError
601
- end
602
- else
603
- @accept_errors_suppressed += 1
604
- end
605
- sleep ACCEPT_ERROR_DELAY
606
- end
607
-
608
554
  # :nodoc:
609
555
  def close_client_safely(client)
610
556
  client.close
@@ -636,7 +582,7 @@ module Puma
636
582
  lowlevel_error(e, client.env)
637
583
  @log_writer.ssl_error e, client.io
638
584
  when HttpParserError
639
- response_to_error(client, requests, e, 400)
585
+ response_to_error(client, requests, e, client.error_status_code || 400)
640
586
  @log_writer.parse_error e, client
641
587
  when HttpParserError501
642
588
  response_to_error(client, requests, e, 501)
@@ -669,50 +615,27 @@ module Puma
669
615
  end
670
616
 
671
617
  def response_to_error(client, requests, err, status_code)
672
- status, headers, res_body = lowlevel_error(err, client.env, status_code)
618
+ # @todo remove sometime later
619
+ if status_code == 413
620
+ status = 413
621
+ res_body = ["Payload Too Large"]
622
+ headers = {}
623
+ headers[CONTENT_LENGTH2] = 17
624
+ else
625
+ status, headers, res_body = lowlevel_error(err, client.env, status_code)
626
+ end
673
627
  prepare_response(status, headers, res_body, requests, client)
674
628
  end
675
629
  private :response_to_error
676
630
 
677
- def shutdown_body_deadline
678
- @shutdown_body_deadline ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) + @shutdown_body_timeout
679
- end
680
-
681
- def shutdown_body_limit
682
- @shutdown_body_deadline
683
- end
684
-
685
631
  # Wait for all outstanding requests to finish.
686
632
  #
687
633
  def graceful_shutdown
688
- shutdown_body_deadline
689
-
690
- if options[:shutdown_debug]
691
- threads = Thread.list
692
- total = threads.size
693
-
694
- pid = Process.pid
695
-
696
- $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
697
-
698
- threads.each_with_index do |t,i|
699
- $stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
700
- $stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
701
- end
702
- $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
703
- end
704
-
705
634
  if @status != :restart
706
635
  @binder.close
707
636
  end
708
637
 
709
- if @thread_pool
710
- if timeout = options[:force_shutdown_after]
711
- @thread_pool.shutdown timeout.to_f
712
- else
713
- @thread_pool.shutdown
714
- end
715
- end
638
+ @thread_pool.shutdown(options[:force_shutdown_after])
716
639
  end
717
640
 
718
641
  def notify_safely(message)
@@ -729,11 +652,10 @@ module Puma
729
652
  end
730
653
  private :notify_safely
731
654
 
732
- # Stops the acceptor thread and then causes the worker threads to finish
655
+ # Stops the acceptor thread and then causes the processor threads to finish
733
656
  # off the request queue before finally exiting.
734
657
 
735
658
  def stop(sync=false)
736
- shutdown_body_deadline
737
659
  notify_safely(STOP_COMMAND)
738
660
  @thread.join if @thread && sync
739
661
  end
@@ -744,7 +666,6 @@ module Puma
744
666
  end
745
667
 
746
668
  def begin_restart(sync=false)
747
- shutdown_body_deadline
748
669
  notify_safely(RESTART_COMMAND)
749
670
  @thread.join if @thread && sync
750
671
  end
@@ -801,6 +722,49 @@ module Puma
801
722
  @binder.add_unix_listener path, umask, mode, backlog
802
723
  end
803
724
 
725
+ # Updates the minimum and maximum number of threads in the thread pool.
726
+ #
727
+ # This method allows dynamic adjustment of the thread pool size while the server
728
+ # is running. It validates the provided values and updates both the thread pool
729
+ # and the server's thread configuration.
730
+ #
731
+ # @param min [Integer] The minimum number of threads to maintain in the pool.
732
+ # Defaults to the current minimum if not specified. Must be greater than 0
733
+ # and less than or equal to max.
734
+ # @param max [Integer] The maximum number of threads allowed in the pool.
735
+ # Defaults to the current maximum if not specified. Must be greater than or
736
+ # equal to min.
737
+ #
738
+ # @return [void]
739
+ #
740
+ # @note If validation fails, a warning message is logged and no changes are made.
741
+ #
742
+ # @example Update both min and max threads
743
+ # server.update_thread_pool_min_max(min: 2, max: 8)
744
+ #
745
+ # @example Update only the minimum threads
746
+ # server.update_thread_pool_min_max(min: 4)
747
+ #
748
+ # @example Update only the maximum threads
749
+ # server.update_thread_pool_min_max(max: 16)
750
+ #
751
+ def update_thread_pool_min_max(min: @min_threads, max: @max_threads)
752
+ if min > max
753
+ @log_writer.log "`min' value cannot be greater than `max' value."
754
+ return
755
+ end
756
+
757
+ if min < 0
758
+ @log_writer.log "`min' value cannot be less than 0"
759
+ return
760
+ end
761
+
762
+ @thread_pool&.with_mutex do
763
+ @thread_pool.min, @thread_pool.max = min, max
764
+ @min_threads, @max_threads = min, max
765
+ end
766
+ end
767
+
804
768
  # @!attribute [r] connected_ports
805
769
  def connected_ports
806
770
  @binder.connected_ports
@@ -0,0 +1,32 @@
1
+ module Puma
2
+ # ServerPluginControl provides a control interface for server plugins to
3
+ # interact with and manage server settings dynamically.
4
+ #
5
+ # This class acts as a facade between plugins and the Puma server,
6
+ # allowing plugins to safely modify server configuration and thread pool
7
+ # settings without direct access to the server's internal state.
8
+ #
9
+ class ServerPluginControl
10
+ def initialize(server)
11
+ @server = server
12
+ end
13
+
14
+ # Returns the maximum number of threads in the thread pool.
15
+ def max_threads
16
+ @server.max_threads
17
+ end
18
+
19
+ # Returns the minimum number of threads in the thread pool.
20
+ def min_threads
21
+ @server.min_threads
22
+ end
23
+
24
+ # Updates the minimum and maximum number of threads in the thread pool.
25
+ #
26
+ # @see Puma::Server#update_thread_pool_min_max
27
+ #
28
+ def update_thread_pool_min_max(min: max_threads, max: min_threads)
29
+ @server.update_thread_pool_min_max(min: min, max: max)
30
+ end
31
+ end
32
+ end