puma 2.1.1 → 7.2.1

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.
Files changed (125) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +3273 -0
  3. data/LICENSE +23 -20
  4. data/README.md +388 -100
  5. data/bin/puma-wild +25 -0
  6. data/bin/pumactl +1 -1
  7. data/docs/architecture.md +74 -0
  8. data/docs/compile_options.md +55 -0
  9. data/docs/deployment.md +137 -0
  10. data/docs/fork_worker.md +41 -0
  11. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  12. data/docs/images/puma-connection-flow.png +0 -0
  13. data/docs/images/puma-general-arch.png +0 -0
  14. data/docs/java_options.md +54 -0
  15. data/docs/jungle/README.md +9 -0
  16. data/docs/jungle/rc.d/README.md +74 -0
  17. data/docs/jungle/rc.d/puma +61 -0
  18. data/docs/jungle/rc.d/puma.conf +10 -0
  19. data/docs/kubernetes.md +73 -0
  20. data/docs/nginx.md +5 -5
  21. data/docs/plugins.md +42 -0
  22. data/docs/rails_dev_mode.md +28 -0
  23. data/docs/restart.md +65 -0
  24. data/docs/signals.md +98 -0
  25. data/docs/stats.md +148 -0
  26. data/docs/systemd.md +253 -0
  27. data/docs/testing_benchmarks_local_files.md +150 -0
  28. data/docs/testing_test_rackup_ci_files.md +36 -0
  29. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  30. data/ext/puma_http11/extconf.rb +59 -2
  31. data/ext/puma_http11/http11_parser.c +319 -487
  32. data/ext/puma_http11/http11_parser.h +15 -13
  33. data/ext/puma_http11/http11_parser.java.rl +27 -43
  34. data/ext/puma_http11/http11_parser.rl +27 -24
  35. data/ext/puma_http11/http11_parser_common.rl +8 -8
  36. data/ext/puma_http11/mini_ssl.c +696 -38
  37. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  38. data/ext/puma_http11/org/jruby/puma/Http11.java +145 -113
  39. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +139 -172
  40. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
  41. data/ext/puma_http11/puma_http11.c +183 -175
  42. data/lib/puma/app/status.rb +77 -29
  43. data/lib/puma/binder.rb +349 -119
  44. data/lib/puma/cli.rb +164 -769
  45. data/lib/puma/client.rb +591 -132
  46. data/lib/puma/cluster/worker.rb +183 -0
  47. data/lib/puma/cluster/worker_handle.rb +127 -0
  48. data/lib/puma/cluster.rb +634 -0
  49. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  50. data/lib/puma/commonlogger.rb +115 -0
  51. data/lib/puma/configuration.rb +370 -202
  52. data/lib/puma/const.rb +266 -115
  53. data/lib/puma/control_cli.rb +219 -120
  54. data/lib/puma/detect.rb +45 -2
  55. data/lib/puma/dsl.rb +1496 -0
  56. data/lib/puma/error_logger.rb +115 -0
  57. data/lib/puma/events.rb +44 -60
  58. data/lib/puma/io_buffer.rb +48 -5
  59. data/lib/puma/jruby_restart.rb +2 -51
  60. data/lib/puma/json_serialization.rb +96 -0
  61. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  62. data/lib/puma/launcher.rb +502 -0
  63. data/lib/puma/log_writer.rb +147 -0
  64. data/lib/puma/minissl/context_builder.rb +96 -0
  65. data/lib/puma/minissl.rb +355 -37
  66. data/lib/puma/null_io.rb +79 -12
  67. data/lib/puma/plugin/systemd.rb +90 -0
  68. data/lib/puma/plugin/tmp_restart.rb +36 -0
  69. data/lib/puma/plugin.rb +111 -0
  70. data/lib/puma/rack/builder.rb +297 -0
  71. data/lib/puma/rack/urlmap.rb +93 -0
  72. data/lib/puma/rack_default.rb +21 -4
  73. data/lib/puma/reactor.rb +102 -133
  74. data/lib/puma/request.rb +703 -0
  75. data/lib/puma/runner.rb +211 -0
  76. data/lib/puma/sd_notify.rb +146 -0
  77. data/lib/puma/server.rb +544 -436
  78. data/lib/puma/single.rb +72 -0
  79. data/lib/puma/state_file.rb +69 -0
  80. data/lib/puma/thread_pool.rb +282 -56
  81. data/lib/puma/util.rb +125 -0
  82. data/lib/puma.rb +80 -6
  83. data/lib/rack/handler/puma.rb +125 -47
  84. data/tools/Dockerfile +26 -0
  85. data/tools/trickletest.rb +44 -0
  86. metadata +77 -148
  87. data/COPYING +0 -55
  88. data/Gemfile +0 -10
  89. data/History.txt +0 -291
  90. data/Manifest.txt +0 -60
  91. data/Rakefile +0 -121
  92. data/TODO +0 -5
  93. data/docs/config.md +0 -0
  94. data/ext/puma_http11/ext_help.h +0 -15
  95. data/ext/puma_http11/io_buffer.c +0 -154
  96. data/lib/puma/accept_nonblock.rb +0 -23
  97. data/lib/puma/capistrano.rb +0 -34
  98. data/lib/puma/compat.rb +0 -11
  99. data/lib/puma/daemon_ext.rb +0 -20
  100. data/lib/puma/delegation.rb +0 -11
  101. data/lib/puma/java_io_buffer.rb +0 -45
  102. data/lib/puma/rack_patch.rb +0 -25
  103. data/puma.gemspec +0 -45
  104. data/test/test_app_status.rb +0 -88
  105. data/test/test_cli.rb +0 -171
  106. data/test/test_config.rb +0 -16
  107. data/test/test_http10.rb +0 -27
  108. data/test/test_http11.rb +0 -126
  109. data/test/test_integration.rb +0 -154
  110. data/test/test_iobuffer.rb +0 -38
  111. data/test/test_minissl.rb +0 -25
  112. data/test/test_null_io.rb +0 -31
  113. data/test/test_persistent.rb +0 -238
  114. data/test/test_puma_server.rb +0 -207
  115. data/test/test_rack_handler.rb +0 -10
  116. data/test/test_rack_server.rb +0 -141
  117. data/test/test_thread_pool.rb +0 -146
  118. data/test/test_unix_socket.rb +0 -39
  119. data/test/test_ws.rb +0 -89
  120. data/tools/jungle/init.d/README.md +0 -54
  121. data/tools/jungle/init.d/puma +0 -332
  122. data/tools/jungle/init.d/run-puma +0 -3
  123. data/tools/jungle/upstart/README.md +0 -61
  124. data/tools/jungle/upstart/puma-manager.conf +0 -31
  125. data/tools/jungle/upstart/puma.conf +0 -52
data/lib/puma/server.rb CHANGED
@@ -1,102 +1,195 @@
1
- require 'rack'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'stringio'
3
4
 
4
- require 'puma/thread_pool'
5
- require 'puma/const'
6
- require 'puma/events'
7
- require 'puma/null_io'
8
- require 'puma/compat'
9
- require 'puma/reactor'
10
- require 'puma/client'
11
- require 'puma/binder'
12
- require 'puma/delegation'
13
- require 'puma/accept_nonblock'
14
- require 'puma/util'
15
-
16
- require 'puma/puma_http11'
17
-
18
- unless Puma.const_defined? "IOBuffer"
19
- require 'puma/io_buffer'
20
- end
5
+ require_relative 'thread_pool'
6
+ require_relative 'const'
7
+ require_relative 'log_writer'
8
+ require_relative 'events'
9
+ require_relative 'null_io'
10
+ require_relative 'reactor'
11
+ require_relative 'client'
12
+ require_relative 'binder'
13
+ require_relative 'util'
14
+ require_relative 'request'
15
+ require_relative 'configuration'
16
+ require_relative 'cluster_accept_loop_delay'
21
17
 
22
18
  require 'socket'
19
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
23
20
 
24
21
  module Puma
25
-
26
22
  # The HTTP Server itself. Serves out a single Rack app.
23
+ #
24
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
25
+ # to generate one or more `Puma::Server` instances capable of handling requests.
26
+ # Each Puma process will contain one `Puma::Server` instance.
27
+ #
28
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
29
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
30
+ #
31
+ # Each `Puma::Server` will have one reactor and one thread pool.
27
32
  class Server
33
+ module FiberPerRequest
34
+ def handle_request(client, requests)
35
+ Fiber.new do
36
+ super
37
+ end.resume
38
+ end
39
+ end
28
40
 
29
41
  include Puma::Const
30
- extend Puma::Delegation
42
+ include Request
31
43
 
44
+ attr_reader :options
32
45
  attr_reader :thread
46
+ attr_reader :log_writer
33
47
  attr_reader :events
34
- attr_accessor :app
48
+ attr_reader :min_threads, :max_threads # for #stats
49
+ attr_reader :requests_count # @version 5.0.0
35
50
 
36
- attr_accessor :min_threads
37
- attr_accessor :max_threads
38
- attr_accessor :persistent_timeout
39
- attr_accessor :auto_trim_time
51
+ # @todo the following may be deprecated in the future
52
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
53
+ :leak_stack_on_error,
54
+ :persistent_timeout, :reaping_time
55
+
56
+ attr_accessor :app
57
+ attr_accessor :binder
40
58
 
41
59
  # Create a server for the rack app +app+.
42
60
  #
43
- # +events+ is an object which will be called when certain error events occur
44
- # to be handled. See Puma::Events for the list of current methods to implement.
61
+ # +log_writer+ is a Puma::LogWriter object used to log info and error messages.
62
+ #
63
+ # +events+ is a Puma::Events object used to notify application status events.
45
64
  #
46
65
  # Server#run returns a thread that you can join on to wait for the server
47
- # to do it's work.
66
+ # to do its work.
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
+ #
72
+ # @note The `events` parameter is set to nil, and set to `Events.new` in code.
73
+ # Often `options` needs to be passed, but `events` does not. Using nil allows
74
+ # calling code to not require events.rb.
48
75
  #
49
- def initialize(app, events=Events::DEFAULT)
76
+ def initialize(app, events = nil, options = {})
50
77
  @app = app
51
- @events = events
52
-
53
- @check, @notify = Puma::Util.pipe
78
+ @events = events || Events.new
54
79
 
80
+ @check, @notify = nil
55
81
  @status = :stop
56
82
 
57
- @min_threads = 0
58
- @max_threads = 16
59
- @auto_trim_time = 1
60
-
61
83
  @thread = nil
62
84
  @thread_pool = nil
85
+ @reactor = nil
86
+
87
+ @env_set_http_version = nil
63
88
 
64
- @persistent_timeout = PERSISTENT_TIMEOUT
89
+ @options = if options.is_a?(UserFileDefaultOptions)
90
+ options
91
+ else
92
+ UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
93
+ end
94
+
95
+ @clustered = (@options.fetch :workers, 0) > 0
96
+ @worker_write = @options[:worker_write]
97
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
98
+ @early_hints = @options[:early_hints]
99
+ @first_data_timeout = @options[:first_data_timeout]
100
+ @persistent_timeout = @options[:persistent_timeout]
101
+ @idle_timeout = @options[:idle_timeout]
102
+ @min_threads = @options[:min_threads]
103
+ @max_threads = @options[:max_threads]
104
+ @queue_requests = @options[:queue_requests]
105
+ @max_keep_alive = @options[:max_keep_alive]
106
+ @enable_keep_alives = @options[:enable_keep_alives]
107
+ @enable_keep_alives &&= @queue_requests
108
+ @io_selector_backend = @options[:io_selector_backend]
109
+ @http_content_length_limit = @options[:http_content_length_limit]
110
+ @cluster_accept_loop_delay = ClusterAcceptLoopDelay.new(
111
+ workers: @options[:workers],
112
+ max_delay: @options[:wait_for_less_busy_worker] || 0 # Real default is in Configuration::DEFAULTS, this is for unit testing
113
+ )
114
+
115
+ if @options[:fiber_per_request]
116
+ singleton_class.prepend(FiberPerRequest)
117
+ end
118
+
119
+ # make this a hash, since we prefer `key?` over `include?`
120
+ @supported_http_methods =
121
+ if @options[:supported_http_methods] == :any
122
+ :any
123
+ else
124
+ if (ary = @options[:supported_http_methods])
125
+ ary
126
+ else
127
+ SUPPORTED_HTTP_METHODS
128
+ end.sort.product([nil]).to_h.freeze
129
+ end
65
130
 
66
- @binder = Binder.new(events)
67
- @own_binder = true
131
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
132
+ @leak_stack_on_error = @options[:environment] ? temp : true
68
133
 
69
- @first_data_timeout = FIRST_DATA_TIMEOUT
134
+ @binder = Binder.new(log_writer, @options)
70
135
 
71
136
  ENV['RACK_ENV'] ||= "development"
72
- end
73
137
 
74
- attr_accessor :binder
138
+ @mode = :http
75
139
 
76
- forward :add_tcp_listener, :@binder
77
- forward :add_ssl_listener, :@binder
78
- forward :add_unix_listener, :@binder
140
+ @precheck_closing = true
141
+
142
+ @requests_count = 0
143
+
144
+ @idle_timeout_reached = false
145
+ end
79
146
 
80
147
  def inherit_binder(bind)
81
148
  @binder = bind
82
- @own_binder = false
149
+ end
150
+
151
+ class << self
152
+ # @!attribute [r] current
153
+ def current
154
+ Thread.current.puma_server
155
+ end
156
+
157
+ # :nodoc:
158
+ # @version 5.0.0
159
+ def tcp_cork_supported?
160
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
161
+ end
162
+
163
+ # :nodoc:
164
+ # @version 5.0.0
165
+ def closed_socket_supported?
166
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
167
+ end
168
+ private :tcp_cork_supported?
169
+ private :closed_socket_supported?
83
170
  end
84
171
 
85
172
  # On Linux, use TCP_CORK to better control how the TCP stack
86
173
  # packetizes our stream. This improves both latency and throughput.
174
+ # socket parameter may be an MiniSSL::Socket, so use to_io
87
175
  #
88
- if RUBY_PLATFORM =~ /linux/
176
+ if tcp_cork_supported?
89
177
  # 6 == Socket::IPPROTO_TCP
90
178
  # 3 == TCP_CORK
91
179
  # 1/0 == turn on/off
92
180
  def cork_socket(socket)
93
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
181
+ skt = socket.to_io
182
+ begin
183
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
184
+ rescue IOError, SystemCallError
185
+ end
94
186
  end
95
187
 
96
188
  def uncork_socket(socket)
189
+ skt = socket.to_io
97
190
  begin
98
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
99
- rescue IOError
191
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
192
+ rescue IOError, SystemCallError
100
193
  end
101
194
  end
102
195
  else
@@ -107,12 +200,50 @@ module Puma
107
200
  end
108
201
  end
109
202
 
203
+ if closed_socket_supported?
204
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
205
+
206
+ def closed_socket?(socket)
207
+ skt = socket.to_io
208
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
209
+
210
+ begin
211
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
212
+ rescue IOError, SystemCallError
213
+ @precheck_closing = false
214
+ false
215
+ else
216
+ state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
217
+ # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
218
+ (state >= 6 && state <= 9) || state == 11
219
+ end
220
+ end
221
+ else
222
+ def closed_socket?(socket)
223
+ false
224
+ end
225
+ end
226
+
227
+ # @!attribute [r] backlog
110
228
  def backlog
111
- @thread_pool and @thread_pool.backlog
229
+ @thread_pool&.backlog
112
230
  end
113
231
 
232
+ # @!attribute [r] running
114
233
  def running
115
- @thread_pool and @thread_pool.spawned
234
+ @thread_pool&.spawned
235
+ end
236
+
237
+ # This number represents the number of requests that
238
+ # the server is capable of taking right now.
239
+ #
240
+ # For example if the number is 5 then it means
241
+ # there are 5 threads sitting idle ready to take
242
+ # a request. If one request comes in, then the
243
+ # value would be 4 until it finishes processing.
244
+ # @!attribute [r] pool_capacity
245
+ def pool_capacity
246
+ @thread_pool&.pool_capacity
116
247
  end
117
248
 
118
249
  # Runs the server.
@@ -121,101 +252,205 @@ module Puma
121
252
  # up in the background to handle requests. Otherwise requests
122
253
  # are handled synchronously.
123
254
  #
124
- def run(background=true)
255
+ def run(background=true, thread_name: 'srv')
125
256
  BasicSocket.do_not_reverse_lookup = true
126
257
 
258
+ @events.fire :state, :booting
259
+
127
260
  @status = :run
128
261
 
129
- @thread_pool = ThreadPool.new(@min_threads,
130
- @max_threads,
131
- IOBuffer) do |client, buffer|
132
- process_now = false
262
+ @thread_pool = ThreadPool.new(thread_name, options, server: self) { |client| process_client client }
133
263
 
134
- begin
135
- process_now = client.eagerly_finish
136
- rescue HttpParserError => e
137
- client.write_400
138
- client.close
139
-
140
- @events.parse_error self, client.env, e
141
- rescue ConnectionError
142
- client.close
143
- else
144
- if process_now
145
- process_client client, buffer
146
- else
147
- client.set_timeout @first_data_timeout
148
- @reactor.add client
149
- end
150
- end
264
+ if @queue_requests
265
+ @reactor = Reactor.new(@io_selector_backend) { |c|
266
+ # Inversion of control, the reactor is calling a method on the server when it
267
+ # is done buffering a request or receives a new request from a keepalive connection.
268
+ self.reactor_wakeup(c)
269
+ }
270
+ @reactor.run
151
271
  end
152
272
 
153
- @reactor = Reactor.new self, @thread_pool
273
+ @thread_pool.auto_reap! if options[:reaping_time]
274
+ @thread_pool.auto_trim! if @min_threads != @max_threads && options[:auto_trim_time]
154
275
 
155
- @reactor.run_in_thread
276
+ @check, @notify = Puma::Util.pipe unless @notify
156
277
 
157
- if @auto_trim_time
158
- @thread_pool.auto_trim!(@auto_trim_time)
159
- end
278
+ @events.fire :state, :running
160
279
 
161
280
  if background
162
- @thread = Thread.new { handle_servers }
281
+ @thread = Thread.new do
282
+ Puma.set_thread_name thread_name
283
+ handle_servers
284
+ end
163
285
  return @thread
164
286
  else
165
287
  handle_servers
166
288
  end
167
289
  end
168
290
 
291
+ # This method is called from the Reactor thread when a queued Client receives data,
292
+ # times out, or when the Reactor is shutting down.
293
+ #
294
+ # While the code lives in the Server, the logic is executed on the reactor thread, independently
295
+ # from the server.
296
+ #
297
+ # It is responsible for ensuring that a request has been completely received
298
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
299
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
300
+ # such as nginx) then the application would be subject to a slow client attack.
301
+ #
302
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/main/docs/architecture.md).
303
+ #
304
+ # The method checks to see if it has the full header and body with
305
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
306
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
307
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
308
+ # The Client is then removed from the reactor (return `true`).
309
+ #
310
+ # If a client object times out, a 408 response is written, its connection is closed,
311
+ # and the object is removed from the reactor (return `true`).
312
+ #
313
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
314
+ # ThreadPool, depending on their current state (#can_close?).
315
+ #
316
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
317
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
318
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
319
+ def reactor_wakeup(client)
320
+ shutdown = !@queue_requests
321
+ if client.try_to_finish || (shutdown && !client.can_close?)
322
+ @thread_pool << client
323
+ elsif shutdown || client.timeout == 0
324
+ client.timeout!
325
+ else
326
+ client.set_timeout(@first_data_timeout)
327
+ false
328
+ end
329
+ rescue StandardError => e
330
+ client_error(e, client)
331
+ close_client_safely(client)
332
+ true
333
+ end
334
+
169
335
  def handle_servers
336
+ @env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
337
+ Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
338
+
170
339
  begin
171
340
  check = @check
172
341
  sockets = [check] + @binder.ios
173
342
  pool = @thread_pool
343
+ queue_requests = @queue_requests
344
+ drain = options[:drain_on_shutdown] ? 0 : nil
345
+
346
+ addr_send_name, addr_value = case options[:remote_address]
347
+ when :value
348
+ [:peerip=, options[:remote_address_value]]
349
+ when :header
350
+ [:remote_addr_header=, options[:remote_address_header]]
351
+ when :proxy_protocol
352
+ [:expect_proxy_proto=, options[:remote_address_proxy_protocol]]
353
+ else
354
+ [nil, nil]
355
+ end
174
356
 
175
- while @status == :run
357
+ while @status == :run || (drain && shutting_down?)
176
358
  begin
177
- ios = IO.select sockets
359
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
360
+ unless ios
361
+ unless shutting_down?
362
+ @idle_timeout_reached = true
363
+
364
+ if @clustered
365
+ @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
366
+ next
367
+ else
368
+ @log_writer.log "- Idle timeout reached"
369
+ @status = :stop
370
+ end
371
+ end
372
+
373
+ break
374
+ end
375
+
376
+ if @idle_timeout_reached && @clustered
377
+ @idle_timeout_reached = false
378
+ @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
379
+ end
380
+
178
381
  ios.first.each do |sock|
179
382
  if sock == check
180
383
  break if handle_check
181
384
  else
182
- begin
183
- if io = sock.accept_nonblock
184
- c = Client.new io, @binder.env(sock)
185
- pool << c
186
- end
187
- rescue SystemCallError
385
+ # if ThreadPool out_of_band code is running, we don't want to add
386
+ # clients until the code is finished.
387
+ pool.wait_while_out_of_band_running
388
+
389
+ # A well rested herd (cluster) runs faster
390
+ if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
391
+ delay = @cluster_accept_loop_delay.calculate(
392
+ max_threads: @max_threads,
393
+ busy_threads_plus_todo: busy_threads_plus_todo
394
+ )
395
+ sleep(delay)
188
396
  end
397
+
398
+ io = begin
399
+ sock.accept_nonblock
400
+ rescue IO::WaitReadable
401
+ next
402
+ end
403
+ drain += 1 if shutting_down?
404
+ client = new_client(io, sock)
405
+ client.send(addr_send_name, addr_value) if addr_value
406
+ pool << client
189
407
  end
190
408
  end
191
- rescue Errno::ECONNABORTED
192
- # client closed the socket even before accept
193
- client.close rescue nil
194
- rescue Object => e
195
- @events.unknown_error self, e, "Listen loop"
409
+ rescue IOError, Errno::EBADF
410
+ # In the case that any of the sockets are unexpectedly close.
411
+ raise
412
+ rescue StandardError => e
413
+ @log_writer.unknown_error e, nil, "Listen loop"
196
414
  end
197
415
  end
198
416
 
199
- graceful_shutdown if @status == :stop || @status == :restart
200
- @reactor.clear! if @status == :restart
417
+ @log_writer.debug "Drained #{drain} additional connections." if drain
418
+ @events.fire :state, @status
201
419
 
202
- @reactor.shutdown
420
+ if queue_requests
421
+ @queue_requests = false
422
+ @reactor.shutdown
423
+ end
424
+
425
+ graceful_shutdown if @status == :stop || @status == :restart
203
426
  rescue Exception => e
204
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
205
- STDERR.puts e.backtrace
427
+ @log_writer.unknown_error e, nil, "Exception handling servers"
206
428
  ensure
207
- @check.close
208
- @notify.close
209
-
210
- if @status != :restart and @own_binder
211
- @binder.close
429
+ # Errno::EBADF is infrequently raised
430
+ [@check, @notify].each do |io|
431
+ begin
432
+ io.close unless io.closed?
433
+ rescue Errno::EBADF
434
+ end
212
435
  end
436
+ @notify = nil
437
+ @check = nil
213
438
  end
439
+
440
+ @events.fire :state, :done
441
+ end
442
+
443
+ # :nodoc:
444
+ def new_client(io, sock)
445
+ client = Client.new(io, @binder.env(sock))
446
+ client.listener = sock
447
+ client.http_content_length_limit = @http_content_length_limit
448
+ client
214
449
  end
215
450
 
216
451
  # :nodoc:
217
452
  def handle_check
218
- cmd = @check.read(1)
453
+ cmd = @check.read(1)
219
454
 
220
455
  case cmd
221
456
  when STOP_COMMAND
@@ -229,402 +464,275 @@ module Puma
229
464
  return true
230
465
  end
231
466
 
232
- return false
467
+ false
233
468
  end
234
469
 
235
- # Given a connection on +client+, handle the incoming requests.
470
+ # Given a connection on +client+, handle the incoming requests,
471
+ # or queue the connection in the Reactor if no request is available.
236
472
  #
237
- # This method support HTTP Keep-Alive so it may, depending on if the client
473
+ # This method is called from a ThreadPool worker thread.
474
+ #
475
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
238
476
  # indicates that it supports keep alive, wait for another request before
239
477
  # returning.
240
478
  #
241
- def process_client(client, buffer)
479
+ # Return true if one or more requests were processed.
480
+ def process_client(client)
481
+ close_socket = true
482
+
483
+ requests = 0
484
+
242
485
  begin
243
- close_socket = true
486
+ if @queue_requests && !client.eagerly_finish
244
487
 
245
- while true
246
- case handle_request(client, buffer)
247
- when false
248
- return
249
- when :async
488
+ client.set_timeout(@first_data_timeout)
489
+ if @reactor.add client
250
490
  close_socket = false
251
- return
252
- when true
253
- buffer.reset
254
-
255
- unless client.reset(@status == :run)
256
- close_socket = false
257
- client.set_timeout @persistent_timeout
258
- @reactor.add client
259
- return
260
- end
491
+ return false
261
492
  end
262
493
  end
263
494
 
264
- # The client disconnected while we were reading data
265
- rescue ConnectionError
266
- # Swallow them. The ensure tries to close +client+ down
495
+ with_force_shutdown(client) do
496
+ client.finish(@first_data_timeout)
497
+ end
267
498
 
268
- # The client doesn't know HTTP well
269
- rescue HttpParserError => e
270
- client.write_400
499
+ can_loop = true
500
+ while can_loop
501
+ can_loop = false
502
+ @requests_count += 1
503
+ case handle_request(client, requests + 1)
504
+ when :close
505
+ when :async
506
+ close_socket = false
507
+ when :keep_alive
508
+ requests += 1
509
+
510
+ client.reset
271
511
 
272
- @events.parse_error self, client.env, e
512
+ # This indicates data exists in the client read buffer and there may be
513
+ # additional requests on it, so process them
514
+ next_request_ready = if client.has_back_to_back_requests?
515
+ with_force_shutdown(client) { client.process_back_to_back_requests }
516
+ else
517
+ with_force_shutdown(client) { client.eagerly_finish }
518
+ end
273
519
 
274
- # Server error
520
+ if next_request_ready
521
+ # When Puma has spare threads, allow this one to be monopolized
522
+ # Perf optimization for https://github.com/puma/puma/issues/3788
523
+ if @thread_pool.waiting > 0
524
+ can_loop = true
525
+ else
526
+ @thread_pool << client
527
+ close_socket = false
528
+ end
529
+ elsif @queue_requests
530
+ client.set_timeout @persistent_timeout
531
+ if @reactor.add client
532
+ close_socket = false
533
+ end
534
+ end
535
+ end
536
+ end
537
+ true
275
538
  rescue StandardError => e
276
- client.write_500
539
+ client_error(e, client, requests)
540
+ # The ensure tries to close +client+ down
541
+ requests > 0
542
+ ensure
543
+ client.io_buffer.reset
277
544
 
278
- @events.unknown_error self, e, "Read"
545
+ close_client_safely(client) if close_socket
546
+ end
547
+ end
279
548
 
280
- ensure
281
- buffer.reset
549
+ # :nodoc:
550
+ def close_client_safely(client)
551
+ client.close
552
+ rescue IOError, SystemCallError
553
+ # Already closed
554
+ rescue MiniSSL::SSLError => e
555
+ @log_writer.ssl_error e, client.io
556
+ rescue StandardError => e
557
+ @log_writer.unknown_error e, nil, "Client"
558
+ end
282
559
 
283
- begin
284
- client.close if close_socket
285
- rescue IOError, SystemCallError
286
- # Already closed
287
- rescue StandardError => e
288
- @events.unknown_error self, e, "Client"
289
- end
560
+ # Triggers a client timeout if the thread-pool shuts down
561
+ # during execution of the provided block.
562
+ def with_force_shutdown(client, &block)
563
+ @thread_pool.with_force_shutdown(&block)
564
+ rescue ThreadPool::ForceShutdown
565
+ client.timeout!
566
+ end
567
+
568
+ # :nocov:
569
+
570
+ # Handle various error types thrown by Client I/O operations.
571
+ def client_error(e, client, requests = 1)
572
+ # Swallow, do not log
573
+ return if [ConnectionError, EOFError].include?(e.class)
574
+
575
+ case e
576
+ when MiniSSL::SSLError
577
+ lowlevel_error(e, client.env)
578
+ @log_writer.ssl_error e, client.io
579
+ when HttpParserError
580
+ response_to_error(client, requests, e, 400)
581
+ @log_writer.parse_error e, client
582
+ when HttpParserError501
583
+ response_to_error(client, requests, e, 501)
584
+ @log_writer.parse_error e, client
585
+ else
586
+ response_to_error(client, requests, e, 500)
587
+ @log_writer.unknown_error e, nil, "Read"
290
588
  end
291
589
  end
292
590
 
293
- # Given a Hash +env+ for the request read from +client+, add
294
- # and fixup keys to comply with Rack's env guidelines.
591
+ # A fallback rack response if +@app+ raises as exception.
295
592
  #
296
- def normalize_env(env, client)
297
- if host = env[HTTP_HOST]
298
- if colon = host.index(":")
299
- env[SERVER_NAME] = host[0, colon]
300
- env[SERVER_PORT] = host[colon+1, host.bytesize]
593
+ def lowlevel_error(e, env, status=500)
594
+ if handler = options[:lowlevel_error_handler]
595
+ if handler.arity == 1
596
+ return handler.call(e)
597
+ elsif handler.arity == 2
598
+ return handler.call(e, env)
301
599
  else
302
- env[SERVER_NAME] = host
303
- env[SERVER_PORT] = default_server_port(env)
600
+ return handler.call(e, env, status)
304
601
  end
305
- else
306
- env[SERVER_NAME] = LOCALHOST
307
- env[SERVER_PORT] = default_server_port(env)
308
602
  end
309
603
 
310
- unless env[REQUEST_PATH]
311
- # it might be a dumbass full host request header
312
- uri = URI.parse(env[REQUEST_URI])
313
- env[REQUEST_PATH] = uri.path
314
-
315
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
604
+ if @leak_stack_on_error
605
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
606
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
607
+ else
608
+ [status, {}, [""]]
316
609
  end
317
-
318
- env[PATH_INFO] = env[REQUEST_PATH]
319
-
320
- # From http://www.ietf.org/rfc/rfc3875 :
321
- # "Script authors should be aware that the REMOTE_ADDR and
322
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
323
- # may not identify the ultimate source of the request.
324
- # They identify the client for the immediate request to the
325
- # server; that client may be a proxy, gateway, or other
326
- # intermediary acting on behalf of the actual source client."
327
- #
328
- env[REMOTE_ADDR] = client.peeraddr.last
329
610
  end
330
611
 
331
- def default_server_port(env)
332
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
612
+ def response_to_error(client, requests, err, status_code)
613
+ status, headers, res_body = lowlevel_error(err, client.env, status_code)
614
+ prepare_response(status, headers, res_body, requests, client)
333
615
  end
616
+ private :response_to_error
334
617
 
335
- # Given the request +env+ from +client+ and a partial request body
336
- # in +body+, finish reading the body if there is one and invoke
337
- # the rack app. Then construct the response and write it back to
338
- # +client+
339
- #
340
- # +cl+ is the previously fetched Content-Length header if there
341
- # was one. This is an optimization to keep from having to look
342
- # it up again.
618
+ # Wait for all outstanding requests to finish.
343
619
  #
344
- def handle_request(req, lines)
345
- env = req.env
346
- client = req.io
347
-
348
- normalize_env env, client
349
-
350
- env[PUMA_SOCKET] = client
351
-
352
- env[HIJACK_P] = true
353
- env[HIJACK] = req
354
-
355
- body = req.body
356
-
357
- head = env[REQUEST_METHOD] == HEAD
358
-
359
- env[RACK_INPUT] = body
360
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
361
-
362
- # A rack extension. If the app writes #call'ables to this
363
- # array, we will invoke them when the request is done.
364
- #
365
- after_reply = env[RACK_AFTER_REPLY] = []
366
-
367
- begin
368
- begin
369
- status, headers, res_body = @app.call(env)
370
-
371
- return :async if req.hijacked
372
-
373
- status = status.to_i
374
-
375
- if status == -1
376
- unless headers.empty? and res_body == []
377
- raise "async response must have empty headers and body"
378
- end
379
-
380
- return :async
381
- end
382
- rescue => e
383
- @events.unknown_error self, e, "Rack app"
620
+ def graceful_shutdown
621
+ if options[:shutdown_debug]
622
+ threads = Thread.list
623
+ total = threads.size
384
624
 
385
- status, headers, res_body = lowlevel_error(e)
386
- end
625
+ pid = Process.pid
387
626
 
388
- content_length = nil
389
- no_body = head
627
+ $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
390
628
 
391
- if res_body.kind_of? Array and res_body.size == 1
392
- content_length = res_body[0].bytesize
629
+ threads.each_with_index do |t,i|
630
+ $stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
631
+ $stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
393
632
  end
633
+ $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
634
+ end
394
635
 
395
- cork_socket client
396
-
397
- line_ending = LINE_END
398
- colon = COLON
399
-
400
- if env[HTTP_VERSION] == HTTP_11
401
- allow_chunked = true
402
- keep_alive = env[HTTP_CONNECTION] != CLOSE
403
- include_keepalive_header = false
404
-
405
- # An optimization. The most common response is 200, so we can
406
- # reply with the proper 200 status without having to compute
407
- # the response header.
408
- #
409
- if status == 200
410
- lines << HTTP_11_200
411
- else
412
- lines.append "HTTP/1.1 ", status.to_s, " ",
413
- HTTP_STATUS_CODES[status], line_ending
636
+ if @status != :restart
637
+ @binder.close
638
+ end
414
639
 
415
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
416
- end
640
+ if @thread_pool
641
+ if timeout = options[:force_shutdown_after]
642
+ @thread_pool.shutdown timeout.to_f
417
643
  else
418
- allow_chunked = false
419
- keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
420
- include_keepalive_header = keep_alive
421
-
422
- # Same optimization as above for HTTP/1.1
423
- #
424
- if status == 200
425
- lines << HTTP_10_200
426
- else
427
- lines.append "HTTP/1.0 ", status.to_s, " ",
428
- HTTP_STATUS_CODES[status], line_ending
429
-
430
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
431
- end
432
- end
433
-
434
- response_hijack = nil
435
-
436
- headers.each do |k, vs|
437
- case k
438
- when CONTENT_LENGTH2
439
- content_length = vs
440
- next
441
- when TRANSFER_ENCODING
442
- allow_chunked = false
443
- content_length = nil
444
- when CONTENT_TYPE
445
- next if no_body
446
- when HIJACK
447
- response_hijack = vs
448
- next
449
- end
450
-
451
- vs.split(NEWLINE).each do |v|
452
- lines.append k, colon, v, line_ending
453
- end
454
- end
455
-
456
- if no_body
457
- lines << line_ending
458
- fast_write client, lines.to_s
459
- return keep_alive
460
- end
461
-
462
- if include_keepalive_header
463
- lines << CONNECTION_KEEP_ALIVE
464
- elsif !keep_alive
465
- lines << CONNECTION_CLOSE
466
- end
467
-
468
- unless response_hijack
469
- if content_length
470
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
471
- chunked = false
472
- elsif allow_chunked
473
- lines << TRANSFER_ENCODING_CHUNKED
474
- chunked = true
475
- end
476
- end
477
-
478
- lines << line_ending
479
-
480
- fast_write client, lines.to_s
481
-
482
- if response_hijack
483
- response_hijack.call client
484
- return :async
485
- end
486
-
487
- res_body.each do |part|
488
- if chunked
489
- client.syswrite part.bytesize.to_s(16)
490
- client.syswrite line_ending
491
- fast_write client, part
492
- client.syswrite line_ending
493
- else
494
- fast_write client, part
495
- end
496
-
497
- client.flush
498
- end
499
-
500
- if chunked
501
- client.syswrite CLOSE_CHUNKED
502
- client.flush
644
+ @thread_pool.shutdown
503
645
  end
504
-
505
- ensure
506
- uncork_socket client
507
-
508
- body.close
509
- res_body.close if res_body.respond_to? :close
510
-
511
- after_reply.each { |o| o.call }
512
646
  end
513
-
514
- return keep_alive
515
647
  end
516
648
 
517
- # Given the requset +env+ from +client+ and the partial body +body+
518
- # plus a potential Content-Length value +cl+, finish reading
519
- # the body and return it.
520
- #
521
- # If the body is larger than MAX_BODY, a Tempfile object is used
522
- # for the body, otherwise a StringIO is used.
523
- #
524
- def read_body(env, client, body, cl)
525
- content_length = cl.to_i
526
-
527
- remain = content_length - body.bytesize
528
-
529
- return StringIO.new(body) if remain <= 0
530
-
531
- # Use a Tempfile if there is a lot of data left
532
- if remain > MAX_BODY
533
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
534
- stream.binmode
649
+ def notify_safely(message)
650
+ @notify << message
651
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
652
+ # The server, in another thread, is shutting down
653
+ rescue RuntimeError => e
654
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
655
+ if e.message.include?('IOError')
656
+ # ignore
535
657
  else
536
- # The body[0,0] trick is to get an empty string in the same
537
- # encoding as body.
538
- stream = StringIO.new body[0,0]
539
- end
540
-
541
- stream.write body
542
-
543
- # Read an odd sized chunk so we can read even sized ones
544
- # after this
545
- chunk = client.readpartial(remain % CHUNK_SIZE)
546
-
547
- # No chunk means a closed socket
548
- unless chunk
549
- stream.close
550
- return nil
658
+ raise e
551
659
  end
660
+ end
661
+ private :notify_safely
552
662
 
553
- remain -= stream.write(chunk)
554
-
555
- # Raed the rest of the chunks
556
- while remain > 0
557
- chunk = client.readpartial(CHUNK_SIZE)
558
- unless chunk
559
- stream.close
560
- return nil
561
- end
562
-
563
- remain -= stream.write(chunk)
564
- end
663
+ # Stops the acceptor thread and then causes the worker threads to finish
664
+ # off the request queue before finally exiting.
565
665
 
566
- stream.rewind
666
+ def stop(sync=false)
667
+ notify_safely(STOP_COMMAND)
668
+ @thread.join if @thread && sync
669
+ end
567
670
 
568
- return stream
671
+ def halt(sync=false)
672
+ notify_safely(HALT_COMMAND)
673
+ @thread.join if @thread && sync
569
674
  end
570
675
 
571
- # A fallback rack response if +@app+ raises as exception.
572
- #
573
- def lowlevel_error(e)
574
- [500, {}, ["Puma caught this error: #{e}\n#{e.backtrace.join("\n")}"]]
676
+ def begin_restart(sync=false)
677
+ notify_safely(RESTART_COMMAND)
678
+ @thread.join if @thread && sync
575
679
  end
576
680
 
577
- # Wait for all outstanding requests to finish.
578
- #
579
- def graceful_shutdown
580
- @thread_pool.shutdown if @thread_pool
681
+ def shutting_down?
682
+ @status == :stop || @status == :restart
581
683
  end
582
684
 
583
- # Stops the acceptor thread and then causes the worker threads to finish
584
- # off the request queue before finally exiting.
585
- #
586
- def stop(sync=false)
587
- begin
588
- @notify << STOP_COMMAND
589
- rescue IOError
590
- # The server, in another thread, is shutting down
591
- end
685
+ # List of methods invoked by #stats.
686
+ # @version 5.0.0
687
+ STAT_METHODS = [
688
+ :backlog,
689
+ :running,
690
+ :pool_capacity,
691
+ :busy_threads,
692
+ :backlog_max,
693
+ :max_threads,
694
+ :requests_count,
695
+ :reactor_max,
696
+ ].freeze
697
+
698
+ # Returns a hash of stats about the running server for reporting purposes.
699
+ # @version 5.0.0
700
+ # @!attribute [r] stats
701
+ # @return [Hash] hash containing stat info from `Server` and `ThreadPool`
702
+ def stats
703
+ stats = @thread_pool&.stats || {}
704
+ stats[:max_threads] = @max_threads
705
+ stats[:requests_count] = @requests_count
706
+ stats[:reactor_max] = @reactor.reactor_max if @reactor
707
+ reset_max
708
+ stats
709
+ end
592
710
 
593
- @thread.join if @thread && sync
711
+ def reset_max
712
+ @reactor.reactor_max = 0 if @reactor
713
+ @thread_pool&.reset_max
594
714
  end
595
715
 
596
- def halt(sync=false)
597
- begin
598
- @notify << HALT_COMMAND
599
- rescue IOError
600
- # The server, in another thread, is shutting down
601
- end
716
+ # below are 'delegations' to binder
717
+ # remove in Puma 7?
602
718
 
603
- @thread.join if @thread && sync
719
+
720
+ def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
721
+ @binder.add_tcp_listener host, port, optimize_for_latency, backlog
604
722
  end
605
723
 
606
- def begin_restart
607
- begin
608
- @notify << RESTART_COMMAND
609
- rescue IOError
610
- # The server, in another thread, is shutting down
611
- end
724
+ def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
725
+ backlog = 1024)
726
+ @binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
612
727
  end
613
728
 
614
- def fast_write(io, str)
615
- n = 0
616
- while true
617
- begin
618
- n = io.syswrite str
619
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
620
- IO.select(nil, [io], nil, 1)
621
- retry
622
- end
729
+ def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
730
+ @binder.add_unix_listener path, umask, mode, backlog
731
+ end
623
732
 
624
- return if n == str.bytesize
625
- str = str.byteslice(n..-1)
626
- end
733
+ # @!attribute [r] connected_ports
734
+ def connected_ports
735
+ @binder.connected_ports
627
736
  end
628
- private :fast_write
629
737
  end
630
738
  end