puma 6.3.1 → 6.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/puma/server.rb CHANGED
@@ -15,7 +15,6 @@ require_relative 'request'
15
15
 
16
16
  require 'socket'
17
17
  require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
- require 'forwardable'
19
18
 
20
19
  module Puma
21
20
 
@@ -32,7 +31,6 @@ module Puma
32
31
  class Server
33
32
  include Puma::Const
34
33
  include Request
35
- extend Forwardable
36
34
 
37
35
  attr_reader :thread
38
36
  attr_reader :log_writer
@@ -48,9 +46,6 @@ module Puma
48
46
  attr_accessor :app
49
47
  attr_accessor :binder
50
48
 
51
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
52
- :add_unix_listener, :connected_ports
53
-
54
49
  THREAD_LOCAL_KEY = :puma_server
55
50
 
56
51
  # Create a server for the rack app +app+.
@@ -86,15 +81,18 @@ module Puma
86
81
  UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
87
82
  end
88
83
 
89
- @log_writer = @options.fetch :log_writer, LogWriter.stdio
90
- @early_hints = @options[:early_hints]
91
- @first_data_timeout = @options[:first_data_timeout]
92
- @min_threads = @options[:min_threads]
93
- @max_threads = @options[:max_threads]
94
- @persistent_timeout = @options[:persistent_timeout]
95
- @queue_requests = @options[:queue_requests]
96
- @max_fast_inline = @options[:max_fast_inline]
97
- @io_selector_backend = @options[:io_selector_backend]
84
+ @clustered = (@options.fetch :workers, 0) > 0
85
+ @worker_write = @options[:worker_write]
86
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
87
+ @early_hints = @options[:early_hints]
88
+ @first_data_timeout = @options[:first_data_timeout]
89
+ @persistent_timeout = @options[:persistent_timeout]
90
+ @idle_timeout = @options[:idle_timeout]
91
+ @min_threads = @options[:min_threads]
92
+ @max_threads = @options[:max_threads]
93
+ @queue_requests = @options[:queue_requests]
94
+ @max_fast_inline = @options[:max_fast_inline]
95
+ @io_selector_backend = @options[:io_selector_backend]
98
96
  @http_content_length_limit = @options[:http_content_length_limit]
99
97
 
100
98
  # make this a hash, since we prefer `key?` over `include?`
@@ -121,6 +119,8 @@ module Puma
121
119
  @precheck_closing = true
122
120
 
123
121
  @requests_count = 0
122
+
123
+ @idle_timeout_reached = false
124
124
  end
125
125
 
126
126
  def inherit_binder(bind)
@@ -330,8 +330,28 @@ module Puma
330
330
 
331
331
  while @status == :run || (drain && shutting_down?)
332
332
  begin
333
- ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
334
- break unless ios
333
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
334
+ unless ios
335
+ unless shutting_down?
336
+ @idle_timeout_reached = true
337
+
338
+ if @clustered
339
+ @worker_write << "i#{Process.pid}\n" rescue nil
340
+ next
341
+ else
342
+ @log_writer.log "- Idle timeout reached"
343
+ @status = :stop
344
+ end
345
+ end
346
+
347
+ break
348
+ end
349
+
350
+ if @idle_timeout_reached && @clustered
351
+ @idle_timeout_reached = false
352
+ @worker_write << "i#{Process.pid}\n" rescue nil
353
+ end
354
+
335
355
  ios.first.each do |sock|
336
356
  if sock == check
337
357
  break if handle_check
@@ -367,6 +387,7 @@ module Puma
367
387
  @queue_requests = false
368
388
  @reactor.shutdown
369
389
  end
390
+
370
391
  graceful_shutdown if @status == :stop || @status == :restart
371
392
  rescue Exception => e
372
393
  @log_writer.unknown_error e, nil, "Exception handling servers"
@@ -476,7 +497,7 @@ module Puma
476
497
  end
477
498
  true
478
499
  rescue StandardError => e
479
- client_error(e, client)
500
+ client_error(e, client, requests)
480
501
  # The ensure tries to close +client+ down
481
502
  requests > 0
482
503
  ensure
@@ -504,22 +525,22 @@ module Puma
504
525
  # :nocov:
505
526
 
506
527
  # Handle various error types thrown by Client I/O operations.
507
- def client_error(e, client)
528
+ def client_error(e, client, requests = 1)
508
529
  # Swallow, do not log
509
530
  return if [ConnectionError, EOFError].include?(e.class)
510
531
 
511
- lowlevel_error(e, client.env)
512
532
  case e
513
533
  when MiniSSL::SSLError
534
+ lowlevel_error(e, client.env)
514
535
  @log_writer.ssl_error e, client.io
515
536
  when HttpParserError
516
- client.write_error(400)
537
+ response_to_error(client, requests, e, 400)
517
538
  @log_writer.parse_error e, client
518
539
  when HttpParserError501
519
- client.write_error(501)
540
+ response_to_error(client, requests, e, 501)
520
541
  @log_writer.parse_error e, client
521
542
  else
522
- client.write_error(500)
543
+ response_to_error(client, requests, e, 500)
523
544
  @log_writer.unknown_error e, nil, "Read"
524
545
  end
525
546
  end
@@ -541,10 +562,17 @@ module Puma
541
562
  backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
542
563
  [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
543
564
  else
544
- [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
565
+ [status, {}, [""]]
545
566
  end
546
567
  end
547
568
 
569
+ def response_to_error(client, requests, err, status_code)
570
+ status, headers, res_body = lowlevel_error(err, client.env, status_code)
571
+ prepare_response(status, headers, res_body, requests, client)
572
+ client.write_error(status_code)
573
+ end
574
+ private :response_to_error
575
+
548
576
  # Wait for all outstanding requests to finish.
549
577
  #
550
578
  def graceful_shutdown
@@ -623,5 +651,27 @@ module Puma
623
651
  def stats
624
652
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
625
653
  end
654
+
655
+ # below are 'delegations' to binder
656
+ # remove in Puma 7?
657
+
658
+
659
+ def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
660
+ @binder.add_tcp_listener host, port, optimize_for_latency, backlog
661
+ end
662
+
663
+ def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
664
+ backlog = 1024)
665
+ @binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
666
+ end
667
+
668
+ def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
669
+ @binder.add_unix_listener path, umask, mode, backlog
670
+ end
671
+
672
+ # @!attribute [r] connected_ports
673
+ def connected_ports
674
+ @binder.connected_ports
675
+ end
626
676
  end
627
677
  end
@@ -56,11 +56,11 @@ module Puma
56
56
  end
57
57
 
58
58
  ALLOWED_FIELDS.each do |f|
59
- define_method f do
59
+ define_method f.to_sym do
60
60
  @options[f]
61
61
  end
62
62
 
63
- define_method "#{f}=" do |v|
63
+ define_method :"#{f}=" do |v|
64
64
  @options[f] = v
65
65
  end
66
66
  end
@@ -51,6 +51,8 @@ module Puma
51
51
  @block = block
52
52
  @out_of_band = options[:out_of_band]
53
53
  @clean_thread_locals = options[:clean_thread_locals]
54
+ @before_thread_start = options[:before_thread_start]
55
+ @before_thread_exit = options[:before_thread_exit]
54
56
  @reaping_time = options[:reaping_time]
55
57
  @auto_trim_time = options[:auto_trim_time]
56
58
 
@@ -107,6 +109,7 @@ module Puma
107
109
  def spawn_thread
108
110
  @spawned += 1
109
111
 
112
+ trigger_before_thread_start_hooks
110
113
  th = Thread.new(@spawned) do |spawned|
111
114
  Puma.set_thread_name '%s tp %03i' % [@name, spawned]
112
115
  todo = @todo
@@ -125,6 +128,7 @@ module Puma
125
128
  @spawned -= 1
126
129
  @workers.delete th
127
130
  not_full.signal
131
+ trigger_before_thread_exit_hooks
128
132
  Thread.exit
129
133
  end
130
134
 
@@ -162,6 +166,36 @@ module Puma
162
166
 
163
167
  private :spawn_thread
164
168
 
169
+ def trigger_before_thread_start_hooks
170
+ return unless @before_thread_start&.any?
171
+
172
+ @before_thread_start.each do |b|
173
+ begin
174
+ b.call
175
+ rescue Exception => e
176
+ STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
177
+ end
178
+ end
179
+ nil
180
+ end
181
+
182
+ private :trigger_before_thread_start_hooks
183
+
184
+ def trigger_before_thread_exit_hooks
185
+ return unless @before_thread_exit&.any?
186
+
187
+ @before_thread_exit.each do |b|
188
+ begin
189
+ b.call
190
+ rescue Exception => e
191
+ STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
192
+ end
193
+ end
194
+ nil
195
+ end
196
+
197
+ private :trigger_before_thread_exit_hooks
198
+
165
199
  # @version 5.0.0
166
200
  def trigger_out_of_band_hook
167
201
  return false unless @out_of_band&.any?
data/tools/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use this Dockerfile to create minimal reproductions of issues
2
2
 
3
- FROM ruby:3.1
3
+ FROM ruby:3.2
4
4
 
5
5
  # throw errors if Gemfile has been modified since Gemfile.lock
6
6
  RUN bundle config --global frozen 1
@@ -8,7 +8,7 @@ RUN bundle config --global frozen 1
8
8
  WORKDIR /usr/src/app
9
9
 
10
10
  COPY . .
11
- RUN gem install bundler
11
+
12
12
  RUN bundle install
13
13
  RUN bundle exec rake compile
14
14
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.1
4
+ version: 6.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 1980-01-01 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -145,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  requirements: []
148
- rubygems_version: 3.4.12
148
+ rubygems_version: 3.5.16
149
149
  signing_key:
150
150
  specification_version: 4
151
151
  summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for