ed-precompiled_puma 7.0.4

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