tiny-fast-gem 0.0.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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/puma-8.0.2/History.md +3334 -0
  3. data/puma-8.0.2/LICENSE +29 -0
  4. data/puma-8.0.2/README.md +484 -0
  5. data/puma-8.0.2/bin/puma +10 -0
  6. data/puma-8.0.2/bin/puma-wild +25 -0
  7. data/puma-8.0.2/bin/pumactl +12 -0
  8. data/puma-8.0.2/docs/5.0-Upgrade.md +98 -0
  9. data/puma-8.0.2/docs/6.0-Upgrade.md +56 -0
  10. data/puma-8.0.2/docs/7.0-Upgrade.md +52 -0
  11. data/puma-8.0.2/docs/8.0-Upgrade.md +100 -0
  12. data/puma-8.0.2/docs/architecture.md +74 -0
  13. data/puma-8.0.2/docs/compile_options.md +55 -0
  14. data/puma-8.0.2/docs/deployment.md +137 -0
  15. data/puma-8.0.2/docs/fork_worker.md +41 -0
  16. data/puma-8.0.2/docs/grpc.md +62 -0
  17. data/puma-8.0.2/docs/images/favicon.svg +1 -0
  18. data/puma-8.0.2/docs/images/puma-connection-flow-no-reactor.png +0 -0
  19. data/puma-8.0.2/docs/images/puma-connection-flow.png +0 -0
  20. data/puma-8.0.2/docs/images/puma-general-arch.png +0 -0
  21. data/puma-8.0.2/docs/images/running-puma.svg +1 -0
  22. data/puma-8.0.2/docs/images/standard-logo.svg +1 -0
  23. data/puma-8.0.2/docs/java_options.md +54 -0
  24. data/puma-8.0.2/docs/jungle/README.md +9 -0
  25. data/puma-8.0.2/docs/jungle/rc.d/README.md +74 -0
  26. data/puma-8.0.2/docs/jungle/rc.d/puma +61 -0
  27. data/puma-8.0.2/docs/jungle/rc.d/puma.conf +10 -0
  28. data/puma-8.0.2/docs/kubernetes.md +73 -0
  29. data/puma-8.0.2/docs/nginx.md +80 -0
  30. data/puma-8.0.2/docs/plugins.md +42 -0
  31. data/puma-8.0.2/docs/rails_dev_mode.md +28 -0
  32. data/puma-8.0.2/docs/restart.md +65 -0
  33. data/puma-8.0.2/docs/signals.md +98 -0
  34. data/puma-8.0.2/docs/stats.md +148 -0
  35. data/puma-8.0.2/docs/systemd.md +253 -0
  36. data/puma-8.0.2/docs/testing_benchmarks_local_files.md +150 -0
  37. data/puma-8.0.2/docs/testing_test_rackup_ci_files.md +36 -0
  38. data/puma-8.0.2/ext/puma_http11/PumaHttp11Service.java +17 -0
  39. data/puma-8.0.2/ext/puma_http11/extconf.rb +65 -0
  40. data/puma-8.0.2/ext/puma_http11/http11_parser.c +1057 -0
  41. data/puma-8.0.2/ext/puma_http11/http11_parser.h +65 -0
  42. data/puma-8.0.2/ext/puma_http11/http11_parser.java.rl +131 -0
  43. data/puma-8.0.2/ext/puma_http11/http11_parser.rl +149 -0
  44. data/puma-8.0.2/ext/puma_http11/http11_parser_common.rl +54 -0
  45. data/puma-8.0.2/ext/puma_http11/mini_ssl.c +852 -0
  46. data/puma-8.0.2/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  47. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  48. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11.java +321 -0
  49. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11Parser.java +441 -0
  50. data/puma-8.0.2/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
  51. data/puma-8.0.2/ext/puma_http11/puma_http11.c +499 -0
  52. data/puma-8.0.2/lib/puma/app/status.rb +104 -0
  53. data/puma-8.0.2/lib/puma/binder.rb +511 -0
  54. data/puma-8.0.2/lib/puma/cli.rb +245 -0
  55. data/puma-8.0.2/lib/puma/client.rb +756 -0
  56. data/puma-8.0.2/lib/puma/client_env.rb +171 -0
  57. data/puma-8.0.2/lib/puma/cluster/worker.rb +183 -0
  58. data/puma-8.0.2/lib/puma/cluster/worker_handle.rb +127 -0
  59. data/puma-8.0.2/lib/puma/cluster.rb +634 -0
  60. data/puma-8.0.2/lib/puma/cluster_accept_loop_delay.rb +91 -0
  61. data/puma-8.0.2/lib/puma/commonlogger.rb +115 -0
  62. data/puma-8.0.2/lib/puma/configuration.rb +522 -0
  63. data/puma-8.0.2/lib/puma/const.rb +308 -0
  64. data/puma-8.0.2/lib/puma/control_cli.rb +320 -0
  65. data/puma-8.0.2/lib/puma/detect.rb +58 -0
  66. data/puma-8.0.2/lib/puma/dsl.rb +1562 -0
  67. data/puma-8.0.2/lib/puma/error_logger.rb +115 -0
  68. data/puma-8.0.2/lib/puma/events.rb +72 -0
  69. data/puma-8.0.2/lib/puma/io_buffer.rb +50 -0
  70. data/puma-8.0.2/lib/puma/jruby_restart.rb +11 -0
  71. data/puma-8.0.2/lib/puma/json_serialization.rb +96 -0
  72. data/puma-8.0.2/lib/puma/launcher/bundle_pruner.rb +102 -0
  73. data/puma-8.0.2/lib/puma/launcher.rb +501 -0
  74. data/puma-8.0.2/lib/puma/log_writer.rb +153 -0
  75. data/puma-8.0.2/lib/puma/minissl/context_builder.rb +96 -0
  76. data/puma-8.0.2/lib/puma/minissl.rb +458 -0
  77. data/puma-8.0.2/lib/puma/null_io.rb +101 -0
  78. data/puma-8.0.2/lib/puma/plugin/systemd.rb +90 -0
  79. data/puma-8.0.2/lib/puma/plugin/tmp_restart.rb +36 -0
  80. data/puma-8.0.2/lib/puma/plugin.rb +111 -0
  81. data/puma-8.0.2/lib/puma/rack/builder.rb +297 -0
  82. data/puma-8.0.2/lib/puma/rack/urlmap.rb +93 -0
  83. data/puma-8.0.2/lib/puma/rack_default.rb +24 -0
  84. data/puma-8.0.2/lib/puma/reactor.rb +131 -0
  85. data/puma-8.0.2/lib/puma/response.rb +532 -0
  86. data/puma-8.0.2/lib/puma/runner.rb +211 -0
  87. data/puma-8.0.2/lib/puma/sd_notify.rb +146 -0
  88. data/puma-8.0.2/lib/puma/server.rb +773 -0
  89. data/puma-8.0.2/lib/puma/server_plugin_control.rb +32 -0
  90. data/puma-8.0.2/lib/puma/single.rb +72 -0
  91. data/puma-8.0.2/lib/puma/state_file.rb +69 -0
  92. data/puma-8.0.2/lib/puma/thread_pool.rb +517 -0
  93. data/puma-8.0.2/lib/puma/util.rb +134 -0
  94. data/puma-8.0.2/lib/puma.rb +88 -0
  95. data/puma-8.0.2/lib/rack/handler/puma.rb +144 -0
  96. data/puma-8.0.2/tools/Dockerfile +26 -0
  97. data/puma-8.0.2/tools/trickletest.rb +44 -0
  98. data/tiny-fast-gem.gemspec +12 -0
  99. metadata +138 -0
@@ -0,0 +1,773 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
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 'response'
15
+ require_relative 'configuration'
16
+ require_relative 'cluster_accept_loop_delay'
17
+
18
+ require 'socket'
19
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
20
+
21
+ module Puma
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.
32
+ class Server
33
+ module FiberPerRequest
34
+ def handle_request(processor, client, requests)
35
+ Fiber.new do
36
+ super
37
+ end.resume
38
+ end
39
+ end
40
+
41
+ include Const
42
+ include Response
43
+
44
+ attr_reader :options
45
+ attr_reader :thread
46
+ attr_reader :log_writer
47
+ attr_reader :events
48
+ attr_reader :min_threads, :max_threads # for #stats
49
+ attr_reader :requests_count # @version 5.0.0
50
+
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
58
+
59
+ # Create a server for the rack app +app+.
60
+ #
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.
64
+ #
65
+ # Server#run returns a thread that you can join on to wait for the server
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.
75
+ #
76
+ def initialize(app, events = nil, options = {})
77
+ @app = app
78
+ @events = events || Events.new
79
+
80
+ @check, @notify = nil
81
+ @status = :stop
82
+
83
+ @thread = nil
84
+ @thread_pool = nil
85
+ @reactor = nil
86
+
87
+ @env_set_http_version = nil
88
+
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
130
+
131
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
132
+ @leak_stack_on_error = @options[:environment] ? temp : true
133
+
134
+ @binder = Binder.new(log_writer, @options)
135
+
136
+ ENV['RACK_ENV'] ||= "development"
137
+
138
+ @mode = :http
139
+
140
+ @precheck_closing = true
141
+
142
+ @requests_count = 0
143
+
144
+ @idle_timeout_reached = false
145
+ end
146
+
147
+ def inherit_binder(bind)
148
+ @binder = bind
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?
170
+ end
171
+
172
+ # On Linux, use TCP_CORK to better control how the TCP stack
173
+ # packetizes our stream. This improves both latency and throughput.
174
+ # socket parameter may be an MiniSSL::Socket, so use to_io
175
+ #
176
+ if tcp_cork_supported?
177
+ # 6 == Socket::IPPROTO_TCP
178
+ # 3 == TCP_CORK
179
+ # 1/0 == turn on/off
180
+ def cork_socket(socket)
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
186
+ end
187
+
188
+ def uncork_socket(socket)
189
+ skt = socket.to_io
190
+ begin
191
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
192
+ rescue IOError, SystemCallError
193
+ end
194
+ end
195
+ else
196
+ def cork_socket(socket)
197
+ end
198
+
199
+ def uncork_socket(socket)
200
+ end
201
+ end
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
228
+ def backlog
229
+ @thread_pool&.backlog
230
+ end
231
+
232
+ # @!attribute [r] running
233
+ def running
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
247
+ end
248
+
249
+ # Runs the server.
250
+ #
251
+ # If +background+ is true (the default) then a thread is spun
252
+ # up in the background to handle requests. Otherwise requests
253
+ # are handled synchronously.
254
+ #
255
+ def run(background=true, thread_name: 'srv')
256
+ BasicSocket.do_not_reverse_lookup = true
257
+
258
+ @events.fire :state, :booting
259
+
260
+ @status = :run
261
+
262
+ @thread_pool = ThreadPool.new(thread_name, options, server: self) do |processor, client|
263
+ process_client(processor, client)
264
+ end
265
+
266
+ if @queue_requests
267
+ @reactor = Reactor.new(@io_selector_backend) { |c|
268
+ # Inversion of control, the reactor is calling a method on the server when it
269
+ # is done buffering a request or receives a new request from a keepalive connection.
270
+ self.reactor_wakeup(c)
271
+ }
272
+ @reactor.run
273
+ end
274
+
275
+ @thread_pool.auto_reap! if options[:reaping_time]
276
+ @thread_pool.auto_trim! if @min_threads != @max_threads && options[:auto_trim_time]
277
+
278
+ @check, @notify = Puma::Util.pipe unless @notify
279
+
280
+ @events.fire :state, :running
281
+
282
+ if background
283
+ @thread = Thread.new do
284
+ Puma.set_thread_name thread_name
285
+ handle_servers
286
+ end
287
+ return @thread
288
+ else
289
+ handle_servers
290
+ end
291
+ end
292
+
293
+ # This method is called from the Reactor thread when a queued Client receives data,
294
+ # times out, or when the Reactor is shutting down.
295
+ #
296
+ # While the code lives in the Server, the logic is executed on the reactor thread, independently
297
+ # from the server.
298
+ #
299
+ # It is responsible for ensuring that a request has been completely received
300
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
301
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
302
+ # such as nginx) then the application would be subject to a slow client attack.
303
+ #
304
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/main/docs/architecture.md).
305
+ #
306
+ # The method checks to see if it has the full header and body with
307
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
308
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
309
+ # so that a "processor thread" can pick up the request and begin to execute application logic.
310
+ # The Client is then removed from the reactor (return `true`).
311
+ #
312
+ # If a client object times out, a 408 response is written, its connection is closed,
313
+ # and the object is removed from the reactor (return `true`).
314
+ #
315
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
316
+ # ThreadPool, depending on their current state (#can_close?).
317
+ #
318
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
319
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
320
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
321
+ def reactor_wakeup(client)
322
+ shutdown = !@queue_requests
323
+ if client.try_to_finish || (shutdown && !client.can_close?)
324
+ @thread_pool << client
325
+ elsif shutdown || client.timeout == 0
326
+ client.timeout!
327
+ else
328
+ client.set_timeout(@first_data_timeout)
329
+ false
330
+ end
331
+ rescue StandardError => e
332
+ client_error(e, client)
333
+ close_client_safely(client)
334
+ true
335
+ end
336
+
337
+ def handle_servers
338
+ @env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
339
+ Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
340
+
341
+ begin
342
+ check = @check
343
+ sockets = [check] + @binder.ios
344
+ pool = @thread_pool
345
+ queue_requests = @queue_requests
346
+ drain = options[:drain_on_shutdown] ? 0 : nil
347
+
348
+ addr_send_name, addr_value = case options[:remote_address]
349
+ when :value
350
+ [:peerip=, options[:remote_address_value]]
351
+ when :header
352
+ [:remote_addr_header=, options[:remote_address_header]]
353
+ when :proxy_protocol
354
+ [:expect_proxy_proto=, options[:remote_address_proxy_protocol]]
355
+ else
356
+ [nil, nil]
357
+ end
358
+
359
+ while @status == :run || (drain && shutting_down?)
360
+ begin
361
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
362
+ unless ios
363
+ unless shutting_down?
364
+ @idle_timeout_reached = true
365
+
366
+ if @clustered
367
+ @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
368
+ next
369
+ else
370
+ @log_writer.log "- Idle timeout reached"
371
+ @status = :stop
372
+ end
373
+ end
374
+
375
+ break
376
+ end
377
+
378
+ if @idle_timeout_reached && @clustered
379
+ @idle_timeout_reached = false
380
+ @worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
381
+ end
382
+
383
+ ios.first.each do |sock|
384
+ if sock == check
385
+ break if handle_check
386
+ else
387
+ # if ThreadPool out_of_band code is running, we don't want to add
388
+ # clients until the code is finished.
389
+ pool.wait_while_out_of_band_running
390
+
391
+ # A well rested herd (cluster) runs faster
392
+ if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
393
+ delay = @cluster_accept_loop_delay.calculate(
394
+ max_threads: @max_threads,
395
+ busy_threads_plus_todo: busy_threads_plus_todo
396
+ )
397
+ sleep(delay)
398
+ end
399
+
400
+ io = begin
401
+ sock.accept_nonblock
402
+ rescue IO::WaitReadable
403
+ next
404
+ end
405
+ drain += 1 if shutting_down?
406
+
407
+ client = new_client(io, sock)
408
+ client.send(addr_send_name, addr_value) if addr_value
409
+ pool << client
410
+ end
411
+ end
412
+ rescue IOError, Errno::EBADF
413
+ # In the case that any of the sockets are unexpectedly close.
414
+ raise
415
+ rescue StandardError => e
416
+ @log_writer.unknown_error e, nil, "Listen loop"
417
+ end
418
+ end
419
+
420
+ @log_writer.debug { "Drained #{drain} additional connections." } if drain
421
+ @events.fire :state, @status
422
+
423
+ if queue_requests
424
+ @queue_requests = false
425
+ @reactor.shutdown
426
+ end
427
+
428
+ graceful_shutdown if @status == :stop || @status == :restart
429
+ rescue Exception => e
430
+ @log_writer.unknown_error e, nil, "Exception handling servers"
431
+ ensure
432
+ # Errno::EBADF is infrequently raised
433
+ [@check, @notify].each do |io|
434
+ begin
435
+ io.close unless io.closed?
436
+ rescue Errno::EBADF
437
+ end
438
+ end
439
+ @notify = nil
440
+ @check = nil
441
+ end
442
+
443
+ @events.fire :state, :done
444
+ end
445
+
446
+ # :nodoc:
447
+ def new_client(io, sock)
448
+ client = Client.new(io, @binder.env(sock))
449
+ client.listener = sock
450
+ client.env_set_http_version = @env_set_http_version
451
+ client.http_content_length_limit = @http_content_length_limit
452
+ client.supported_http_methods = @supported_http_methods
453
+ client
454
+ end
455
+
456
+ # :nodoc:
457
+ def handle_check
458
+ cmd = @check.read(1)
459
+
460
+ case cmd
461
+ when STOP_COMMAND
462
+ @status = :stop
463
+ return true
464
+ when HALT_COMMAND
465
+ @status = :halt
466
+ return true
467
+ when RESTART_COMMAND
468
+ @status = :restart
469
+ return true
470
+ end
471
+
472
+ false
473
+ end
474
+
475
+ # Given a connection on +client+, handle the incoming requests,
476
+ # or queue the connection in the Reactor if no request is available.
477
+ #
478
+ # This method is called from a ThreadPool processor thread.
479
+ #
480
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
481
+ # indicates that it supports keep alive, wait for another request before
482
+ # returning.
483
+ #
484
+ # Return true if one or more requests were processed.
485
+ def process_client(processor, client)
486
+ close_socket = true
487
+
488
+ requests = 0
489
+
490
+ begin
491
+ if @queue_requests && !client.eagerly_finish
492
+
493
+ client.set_timeout(@first_data_timeout)
494
+ if @reactor.add client
495
+ close_socket = false
496
+ return false
497
+ end
498
+ end
499
+
500
+ with_force_shutdown(client) do
501
+ client.finish(@first_data_timeout)
502
+ end
503
+
504
+ can_loop = true
505
+ while can_loop
506
+ can_loop = false
507
+ @requests_count += 1
508
+ case handle_request(processor, client, requests + 1)
509
+ when :close
510
+ when :async
511
+ close_socket = false
512
+ when :keep_alive
513
+ requests += 1
514
+
515
+ client.reset
516
+
517
+ # This indicates data exists in the client read buffer and there may be
518
+ # additional requests on it, so process them
519
+ next_request_ready = if client.has_back_to_back_requests?
520
+ with_force_shutdown(client) { client.process_back_to_back_requests }
521
+ else
522
+ with_force_shutdown(client) { client.eagerly_finish }
523
+ end
524
+
525
+ if next_request_ready
526
+ # When Puma has spare threads, allow this one to be monopolized
527
+ # Perf optimization for https://github.com/puma/puma/issues/3788
528
+ if @thread_pool.waiting > 0
529
+ can_loop = true
530
+ else
531
+ @thread_pool << client
532
+ close_socket = false
533
+ end
534
+ elsif @queue_requests
535
+ client.set_timeout @persistent_timeout
536
+ if @reactor.add client
537
+ close_socket = false
538
+ end
539
+ end
540
+ end
541
+ end
542
+ true
543
+ rescue StandardError => e
544
+ client_error(e, client, requests)
545
+ # The ensure tries to close +client+ down
546
+ requests > 0
547
+ ensure
548
+ client.io_buffer.reset
549
+
550
+ close_client_safely(client) if close_socket
551
+ end
552
+ end
553
+
554
+ # :nodoc:
555
+ def close_client_safely(client)
556
+ client.close
557
+ rescue IOError, SystemCallError
558
+ # Already closed
559
+ rescue MiniSSL::SSLError => e
560
+ @log_writer.ssl_error e, client.io
561
+ rescue StandardError => e
562
+ @log_writer.unknown_error e, nil, "Client"
563
+ end
564
+
565
+ # Triggers a client timeout if the thread-pool shuts down
566
+ # during execution of the provided block.
567
+ def with_force_shutdown(client, &block)
568
+ @thread_pool.with_force_shutdown(&block)
569
+ rescue ThreadPool::ForceShutdown
570
+ client.timeout!
571
+ end
572
+
573
+ # :nocov:
574
+
575
+ # Handle various error types thrown by Client I/O operations.
576
+ def client_error(e, client, requests = 1)
577
+ # Swallow, do not log
578
+ return if [ConnectionError, EOFError].include?(e.class)
579
+
580
+ case e
581
+ when MiniSSL::SSLError
582
+ lowlevel_error(e, client.env)
583
+ @log_writer.ssl_error e, client.io
584
+ when HttpParserError
585
+ response_to_error(client, requests, e, client.error_status_code || 400)
586
+ @log_writer.parse_error e, client
587
+ when HttpParserError501
588
+ response_to_error(client, requests, e, 501)
589
+ @log_writer.parse_error e, client
590
+ else
591
+ response_to_error(client, requests, e, 500)
592
+ @log_writer.unknown_error e, nil, "Read"
593
+ end
594
+ end
595
+
596
+ # A fallback rack response if +@app+ raises as exception.
597
+ #
598
+ def lowlevel_error(e, env, status=500)
599
+ if handler = options[:lowlevel_error_handler]
600
+ if handler.arity == 1
601
+ return handler.call(e)
602
+ elsif handler.arity == 2
603
+ return handler.call(e, env)
604
+ else
605
+ return handler.call(e, env, status)
606
+ end
607
+ end
608
+
609
+ if @leak_stack_on_error
610
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
611
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
612
+ else
613
+ [status, {}, [""]]
614
+ end
615
+ end
616
+
617
+ def response_to_error(client, requests, err, 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
627
+ prepare_response(status, headers, res_body, requests, client)
628
+ end
629
+ private :response_to_error
630
+
631
+ # Wait for all outstanding requests to finish.
632
+ #
633
+ def graceful_shutdown
634
+ if @status != :restart
635
+ @binder.close
636
+ end
637
+
638
+ @thread_pool.shutdown(options[:force_shutdown_after])
639
+ end
640
+
641
+ def notify_safely(message)
642
+ @notify << message
643
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
644
+ # The server, in another thread, is shutting down
645
+ rescue RuntimeError => e
646
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
647
+ if e.message.include?('IOError')
648
+ # ignore
649
+ else
650
+ raise e
651
+ end
652
+ end
653
+ private :notify_safely
654
+
655
+ # Stops the acceptor thread and then causes the processor threads to finish
656
+ # off the request queue before finally exiting.
657
+
658
+ def stop(sync=false)
659
+ notify_safely(STOP_COMMAND)
660
+ @thread.join if @thread && sync
661
+ end
662
+
663
+ def halt(sync=false)
664
+ notify_safely(HALT_COMMAND)
665
+ @thread.join if @thread && sync
666
+ end
667
+
668
+ def begin_restart(sync=false)
669
+ notify_safely(RESTART_COMMAND)
670
+ @thread.join if @thread && sync
671
+ end
672
+
673
+ def shutting_down?
674
+ @status == :stop || @status == :restart
675
+ end
676
+
677
+ # List of methods invoked by #stats.
678
+ # @version 5.0.0
679
+ STAT_METHODS = [
680
+ :backlog,
681
+ :running,
682
+ :pool_capacity,
683
+ :busy_threads,
684
+ :backlog_max,
685
+ :max_threads,
686
+ :requests_count,
687
+ :reactor_max,
688
+ ].freeze
689
+
690
+ # Returns a hash of stats about the running server for reporting purposes.
691
+ # @version 5.0.0
692
+ # @!attribute [r] stats
693
+ # @return [Hash] hash containing stat info from `Server` and `ThreadPool`
694
+ def stats
695
+ stats = @thread_pool&.stats || {}
696
+ stats[:max_threads] = @max_threads
697
+ stats[:requests_count] = @requests_count
698
+ stats[:reactor_max] = @reactor.reactor_max if @reactor
699
+ reset_max
700
+ stats
701
+ end
702
+
703
+ def reset_max
704
+ @reactor.reactor_max = 0 if @reactor
705
+ @thread_pool&.reset_max
706
+ end
707
+
708
+ # below are 'delegations' to binder
709
+ # remove in Puma 7?
710
+
711
+
712
+ def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
713
+ @binder.add_tcp_listener host, port, optimize_for_latency, backlog
714
+ end
715
+
716
+ def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
717
+ backlog = 1024)
718
+ @binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
719
+ end
720
+
721
+ def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
722
+ @binder.add_unix_listener path, umask, mode, backlog
723
+ end
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
+
768
+ # @!attribute [r] connected_ports
769
+ def connected_ports
770
+ @binder.connected_ports
771
+ end
772
+ end
773
+ end