jun-puma 1.0.0-java

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