puma 6.0.0 → 6.6.0
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.
- checksums.yaml +4 -4
- data/History.md +392 -13
- data/LICENSE +0 -0
- data/README.md +135 -29
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +0 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +11 -1
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +4 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +2 -2
- data/docs/stats.md +8 -3
- data/docs/systemd.md +13 -7
- data/docs/testing_benchmarks_local_files.md +0 -0
- data/docs/testing_test_rackup_ci_files.md +0 -0
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +21 -14
- data/ext/puma_http11/http11_parser.c +0 -0
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +0 -0
- data/ext/puma_http11/http11_parser.rl +0 -0
- data/ext/puma_http11/http11_parser_common.rl +0 -0
- data/ext/puma_http11/mini_ssl.c +107 -10
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +30 -7
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
- data/ext/puma_http11/puma_http11.c +4 -1
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +26 -15
- data/lib/puma/cli.rb +13 -5
- data/lib/puma/client.rb +113 -26
- data/lib/puma/cluster/worker.rb +14 -6
- data/lib/puma/cluster/worker_handle.rb +4 -5
- data/lib/puma/cluster.rb +93 -22
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +42 -22
- data/lib/puma/const.rb +149 -89
- data/lib/puma/control_cli.rb +16 -9
- data/lib/puma/detect.rb +5 -4
- data/lib/puma/dsl.rb +432 -40
- data/lib/puma/error_logger.rb +6 -5
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +10 -0
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +0 -0
- data/lib/puma/launcher.rb +29 -29
- data/lib/puma/log_writer.rb +23 -13
- data/lib/puma/minissl/context_builder.rb +4 -0
- data/lib/puma/minissl.rb +23 -0
- data/lib/puma/null_io.rb +42 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +2 -2
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +18 -3
- data/lib/puma/reactor.rb +17 -8
- data/lib/puma/request.rb +207 -126
- data/lib/puma/runner.rb +26 -4
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +121 -49
- data/lib/puma/single.rb +3 -1
- data/lib/puma/state_file.rb +2 -2
- data/lib/puma/thread_pool.rb +56 -9
- data/lib/puma/util.rb +1 -1
- data/lib/puma.rb +1 -3
- data/lib/rack/handler/puma.rb +116 -86
- data/tools/Dockerfile +2 -2
- data/tools/trickletest.rb +0 -0
- metadata +12 -13
- data/lib/puma/systemd.rb +0 -47
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
# The MIT License
|
7
|
+
#
|
8
|
+
# Copyright (c) 2017-2022 Agis Anastasopoulos
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
11
|
+
# this software and associated documentation files (the "Software"), to deal in
|
12
|
+
# the Software without restriction, including without limitation the rights to
|
13
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
14
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
15
|
+
# subject to the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be included in all
|
18
|
+
# copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
22
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
23
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
24
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
25
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
|
+
#
|
27
|
+
# This is a copy of https://github.com/agis/ruby-sdnotify as of commit cca575c
|
28
|
+
# The only changes made was "rehoming" it within the Puma module to avoid
|
29
|
+
# namespace collisions and applying standard's code formatting style.
|
30
|
+
#
|
31
|
+
# SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
|
32
|
+
# notify systemd about state changes. Methods of this package are no-op on
|
33
|
+
# non-systemd systems (eg. Darwin).
|
34
|
+
#
|
35
|
+
# The API maps closely to the original implementation of sd_notify(3),
|
36
|
+
# therefore be sure to check the official man pages prior to using SdNotify.
|
37
|
+
#
|
38
|
+
# @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
39
|
+
module SdNotify
|
40
|
+
# Exception raised when there's an error writing to the notification socket
|
41
|
+
class NotifyError < RuntimeError; end
|
42
|
+
|
43
|
+
READY = "READY=1"
|
44
|
+
RELOADING = "RELOADING=1"
|
45
|
+
STOPPING = "STOPPING=1"
|
46
|
+
STATUS = "STATUS="
|
47
|
+
ERRNO = "ERRNO="
|
48
|
+
MAINPID = "MAINPID="
|
49
|
+
WATCHDOG = "WATCHDOG=1"
|
50
|
+
FDSTORE = "FDSTORE=1"
|
51
|
+
|
52
|
+
def self.ready(unset_env=false)
|
53
|
+
notify(READY, unset_env)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.reloading(unset_env=false)
|
57
|
+
notify(RELOADING, unset_env)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.stopping(unset_env=false)
|
61
|
+
notify(STOPPING, unset_env)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param status [String] a custom status string that describes the current
|
65
|
+
# state of the service
|
66
|
+
def self.status(status, unset_env=false)
|
67
|
+
notify("#{STATUS}#{status}", unset_env)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param errno [Integer]
|
71
|
+
def self.errno(errno, unset_env=false)
|
72
|
+
notify("#{ERRNO}#{errno}", unset_env)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param pid [Integer]
|
76
|
+
def self.mainpid(pid, unset_env=false)
|
77
|
+
notify("#{MAINPID}#{pid}", unset_env)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.watchdog(unset_env=false)
|
81
|
+
notify(WATCHDOG, unset_env)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.fdstore(unset_env=false)
|
85
|
+
notify(FDSTORE, unset_env)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param [Boolean] true if the service manager expects watchdog keep-alive
|
89
|
+
# notification messages to be sent from this process.
|
90
|
+
#
|
91
|
+
# If the $WATCHDOG_USEC environment variable is set,
|
92
|
+
# and the $WATCHDOG_PID variable is unset or set to the PID of the current
|
93
|
+
# process
|
94
|
+
#
|
95
|
+
# @note Unlike sd_watchdog_enabled(3), this method does not mutate the
|
96
|
+
# environment.
|
97
|
+
def self.watchdog?
|
98
|
+
wd_usec = ENV["WATCHDOG_USEC"]
|
99
|
+
wd_pid = ENV["WATCHDOG_PID"]
|
100
|
+
|
101
|
+
return false if !wd_usec
|
102
|
+
|
103
|
+
begin
|
104
|
+
wd_usec = Integer(wd_usec)
|
105
|
+
rescue
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
return false if wd_usec <= 0
|
110
|
+
return true if !wd_pid || wd_pid == $$.to_s
|
111
|
+
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
# Notify systemd with the provided state, via the notification socket, if
|
116
|
+
# any.
|
117
|
+
#
|
118
|
+
# Generally this method will be used indirectly through the other methods
|
119
|
+
# of the library.
|
120
|
+
#
|
121
|
+
# @param state [String]
|
122
|
+
# @param unset_env [Boolean]
|
123
|
+
#
|
124
|
+
# @return [Fixnum, nil] the number of bytes written to the notification
|
125
|
+
# socket or nil if there was no socket to report to (eg. the program wasn't
|
126
|
+
# started by systemd)
|
127
|
+
#
|
128
|
+
# @raise [NotifyError] if there was an error communicating with the systemd
|
129
|
+
# socket
|
130
|
+
#
|
131
|
+
# @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
132
|
+
def self.notify(state, unset_env=false)
|
133
|
+
sock = ENV["NOTIFY_SOCKET"]
|
134
|
+
|
135
|
+
return nil if !sock
|
136
|
+
|
137
|
+
ENV.delete("NOTIFY_SOCKET") if unset_env
|
138
|
+
|
139
|
+
begin
|
140
|
+
Addrinfo.unix(sock, :DGRAM).connect { |s| s.write state }
|
141
|
+
rescue StandardError => e
|
142
|
+
raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/puma/server.rb
CHANGED
@@ -11,15 +11,16 @@ require_relative 'reactor'
|
|
11
11
|
require_relative 'client'
|
12
12
|
require_relative 'binder'
|
13
13
|
require_relative 'util'
|
14
|
-
require_relative 'io_buffer'
|
15
14
|
require_relative 'request'
|
16
15
|
|
17
16
|
require 'socket'
|
18
17
|
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
19
|
-
require 'forwardable'
|
20
18
|
|
21
19
|
module Puma
|
22
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
|
+
|
23
24
|
# The HTTP Server itself. Serves out a single Rack app.
|
24
25
|
#
|
25
26
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
@@ -33,8 +34,8 @@ module Puma
|
|
33
34
|
class Server
|
34
35
|
include Puma::Const
|
35
36
|
include Request
|
36
|
-
extend Forwardable
|
37
37
|
|
38
|
+
attr_reader :options
|
38
39
|
attr_reader :thread
|
39
40
|
attr_reader :log_writer
|
40
41
|
attr_reader :events
|
@@ -49,10 +50,6 @@ module Puma
|
|
49
50
|
attr_accessor :app
|
50
51
|
attr_accessor :binder
|
51
52
|
|
52
|
-
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
|
53
|
-
:add_unix_listener, :connected_ports
|
54
|
-
|
55
|
-
ThreadLocalKey = :puma_server
|
56
53
|
|
57
54
|
# Create a server for the rack app +app+.
|
58
55
|
#
|
@@ -87,15 +84,32 @@ module Puma
|
|
87
84
|
UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
|
88
85
|
end
|
89
86
|
|
90
|
-
@
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
95
|
-
@persistent_timeout
|
96
|
-
@
|
97
|
-
@
|
98
|
-
@
|
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
|
99
113
|
|
100
114
|
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
101
115
|
@leak_stack_on_error = @options[:environment] ? temp : true
|
@@ -109,6 +123,8 @@ module Puma
|
|
109
123
|
@precheck_closing = true
|
110
124
|
|
111
125
|
@requests_count = 0
|
126
|
+
|
127
|
+
@idle_timeout_reached = false
|
112
128
|
end
|
113
129
|
|
114
130
|
def inherit_binder(bind)
|
@@ -118,7 +134,7 @@ module Puma
|
|
118
134
|
class << self
|
119
135
|
# @!attribute [r] current
|
120
136
|
def current
|
121
|
-
Thread.current
|
137
|
+
Thread.current.puma_server
|
122
138
|
end
|
123
139
|
|
124
140
|
# :nodoc:
|
@@ -217,6 +233,11 @@ module Puma
|
|
217
233
|
@thread_pool&.pool_capacity
|
218
234
|
end
|
219
235
|
|
236
|
+
# @!attribute [r] busy_threads
|
237
|
+
def busy_threads
|
238
|
+
@thread_pool&.busy_threads
|
239
|
+
end
|
240
|
+
|
220
241
|
# Runs the server.
|
221
242
|
#
|
222
243
|
# If +background+ is true (the default) then a thread is spun
|
@@ -230,16 +251,15 @@ module Puma
|
|
230
251
|
|
231
252
|
@status = :run
|
232
253
|
|
233
|
-
@thread_pool = ThreadPool.new(thread_name,
|
254
|
+
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
|
234
255
|
|
235
256
|
if @queue_requests
|
236
257
|
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
|
237
258
|
@reactor.run
|
238
259
|
end
|
239
260
|
|
240
|
-
|
241
|
-
@thread_pool.
|
242
|
-
@thread_pool.auto_trim! if @options[:auto_trim_time]
|
261
|
+
@thread_pool.auto_reap! if options[:reaping_time]
|
262
|
+
@thread_pool.auto_trim! if @min_threads != @max_threads && options[:auto_trim_time]
|
243
263
|
|
244
264
|
@check, @notify = Puma::Util.pipe unless @notify
|
245
265
|
|
@@ -303,29 +323,49 @@ module Puma
|
|
303
323
|
sockets = [check] + @binder.ios
|
304
324
|
pool = @thread_pool
|
305
325
|
queue_requests = @queue_requests
|
306
|
-
drain =
|
326
|
+
drain = options[:drain_on_shutdown] ? 0 : nil
|
307
327
|
|
308
|
-
addr_send_name, addr_value = case
|
328
|
+
addr_send_name, addr_value = case options[:remote_address]
|
309
329
|
when :value
|
310
|
-
[:peerip=,
|
330
|
+
[:peerip=, options[:remote_address_value]]
|
311
331
|
when :header
|
312
|
-
[:remote_addr_header=,
|
332
|
+
[:remote_addr_header=, options[:remote_address_header]]
|
313
333
|
when :proxy_protocol
|
314
|
-
[:expect_proxy_proto=,
|
334
|
+
[:expect_proxy_proto=, options[:remote_address_proxy_protocol]]
|
315
335
|
else
|
316
336
|
[nil, nil]
|
317
337
|
end
|
318
338
|
|
319
339
|
while @status == :run || (drain && shutting_down?)
|
320
340
|
begin
|
321
|
-
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 :
|
322
|
-
|
341
|
+
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
|
342
|
+
unless ios
|
343
|
+
unless shutting_down?
|
344
|
+
@idle_timeout_reached = true
|
345
|
+
|
346
|
+
if @clustered
|
347
|
+
@worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
|
348
|
+
next
|
349
|
+
else
|
350
|
+
@log_writer.log "- Idle timeout reached"
|
351
|
+
@status = :stop
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
break
|
356
|
+
end
|
357
|
+
|
358
|
+
if @idle_timeout_reached && @clustered
|
359
|
+
@idle_timeout_reached = false
|
360
|
+
@worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
|
361
|
+
end
|
362
|
+
|
323
363
|
ios.first.each do |sock|
|
324
364
|
if sock == check
|
325
365
|
break if handle_check
|
326
366
|
else
|
327
367
|
pool.wait_until_not_full
|
328
|
-
pool.wait_for_less_busy_worker(
|
368
|
+
pool.wait_for_less_busy_worker(options[:wait_for_less_busy_worker]) if @clustered
|
329
369
|
|
330
370
|
io = begin
|
331
371
|
sock.accept_nonblock
|
@@ -335,6 +375,7 @@ module Puma
|
|
335
375
|
drain += 1 if shutting_down?
|
336
376
|
pool << Client.new(io, @binder.env(sock)).tap { |c|
|
337
377
|
c.listener = sock
|
378
|
+
c.http_content_length_limit = @http_content_length_limit
|
338
379
|
c.send(addr_send_name, addr_value) if addr_value
|
339
380
|
}
|
340
381
|
end
|
@@ -354,6 +395,7 @@ module Puma
|
|
354
395
|
@queue_requests = false
|
355
396
|
@reactor.shutdown
|
356
397
|
end
|
398
|
+
|
357
399
|
graceful_shutdown if @status == :stop || @status == :restart
|
358
400
|
rescue Exception => e
|
359
401
|
@log_writer.unknown_error e, nil, "Exception handling servers"
|
@@ -401,11 +443,11 @@ module Puma
|
|
401
443
|
# returning.
|
402
444
|
#
|
403
445
|
# Return true if one or more requests were processed.
|
404
|
-
def process_client(client
|
446
|
+
def process_client(client)
|
405
447
|
# Advertise this server into the thread
|
406
|
-
Thread.current
|
448
|
+
Thread.current.puma_server = self
|
407
449
|
|
408
|
-
clean_thread_locals =
|
450
|
+
clean_thread_locals = options[:clean_thread_locals]
|
409
451
|
close_socket = true
|
410
452
|
|
411
453
|
requests = 0
|
@@ -427,15 +469,13 @@ module Puma
|
|
427
469
|
|
428
470
|
while true
|
429
471
|
@requests_count += 1
|
430
|
-
case handle_request(client,
|
472
|
+
case handle_request(client, requests + 1)
|
431
473
|
when false
|
432
474
|
break
|
433
475
|
when :async
|
434
476
|
close_socket = false
|
435
477
|
break
|
436
478
|
when true
|
437
|
-
buffer.reset
|
438
|
-
|
439
479
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
440
480
|
|
441
481
|
requests += 1
|
@@ -465,11 +505,11 @@ module Puma
|
|
465
505
|
end
|
466
506
|
true
|
467
507
|
rescue StandardError => e
|
468
|
-
client_error(e, client)
|
508
|
+
client_error(e, client, requests)
|
469
509
|
# The ensure tries to close +client+ down
|
470
510
|
requests > 0
|
471
511
|
ensure
|
472
|
-
|
512
|
+
client.io_buffer.reset
|
473
513
|
|
474
514
|
begin
|
475
515
|
client.close if close_socket
|
@@ -493,22 +533,22 @@ module Puma
|
|
493
533
|
# :nocov:
|
494
534
|
|
495
535
|
# Handle various error types thrown by Client I/O operations.
|
496
|
-
def client_error(e, client)
|
536
|
+
def client_error(e, client, requests = 1)
|
497
537
|
# Swallow, do not log
|
498
538
|
return if [ConnectionError, EOFError].include?(e.class)
|
499
539
|
|
500
|
-
lowlevel_error(e, client.env)
|
501
540
|
case e
|
502
541
|
when MiniSSL::SSLError
|
542
|
+
lowlevel_error(e, client.env)
|
503
543
|
@log_writer.ssl_error e, client.io
|
504
544
|
when HttpParserError
|
505
|
-
client
|
545
|
+
response_to_error(client, requests, e, 400)
|
506
546
|
@log_writer.parse_error e, client
|
507
547
|
when HttpParserError501
|
508
|
-
client
|
548
|
+
response_to_error(client, requests, e, 501)
|
509
549
|
@log_writer.parse_error e, client
|
510
550
|
else
|
511
|
-
client
|
551
|
+
response_to_error(client, requests, e, 500)
|
512
552
|
@log_writer.unknown_error e, nil, "Read"
|
513
553
|
end
|
514
554
|
end
|
@@ -516,7 +556,7 @@ module Puma
|
|
516
556
|
# A fallback rack response if +@app+ raises as exception.
|
517
557
|
#
|
518
558
|
def lowlevel_error(e, env, status=500)
|
519
|
-
if handler =
|
559
|
+
if handler = options[:lowlevel_error_handler]
|
520
560
|
if handler.arity == 1
|
521
561
|
return handler.call(e)
|
522
562
|
elsif handler.arity == 2
|
@@ -530,14 +570,20 @@ module Puma
|
|
530
570
|
backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
|
531
571
|
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
|
532
572
|
else
|
533
|
-
[status, {}, ["
|
573
|
+
[status, {}, [""]]
|
534
574
|
end
|
535
575
|
end
|
536
576
|
|
577
|
+
def response_to_error(client, requests, err, status_code)
|
578
|
+
status, headers, res_body = lowlevel_error(err, client.env, status_code)
|
579
|
+
prepare_response(status, headers, res_body, requests, client)
|
580
|
+
end
|
581
|
+
private :response_to_error
|
582
|
+
|
537
583
|
# Wait for all outstanding requests to finish.
|
538
584
|
#
|
539
585
|
def graceful_shutdown
|
540
|
-
if
|
586
|
+
if options[:shutdown_debug]
|
541
587
|
threads = Thread.list
|
542
588
|
total = threads.size
|
543
589
|
|
@@ -557,7 +603,7 @@ module Puma
|
|
557
603
|
end
|
558
604
|
|
559
605
|
if @thread_pool
|
560
|
-
if timeout =
|
606
|
+
if timeout = options[:force_shutdown_after]
|
561
607
|
@thread_pool.shutdown timeout.to_f
|
562
608
|
else
|
563
609
|
@thread_pool.shutdown
|
@@ -567,7 +613,7 @@ module Puma
|
|
567
613
|
|
568
614
|
def notify_safely(message)
|
569
615
|
@notify << message
|
570
|
-
rescue IOError, NoMethodError, Errno::EPIPE
|
616
|
+
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
571
617
|
# The server, in another thread, is shutting down
|
572
618
|
Puma::Util.purge_interrupt_queue
|
573
619
|
rescue RuntimeError => e
|
@@ -604,13 +650,39 @@ module Puma
|
|
604
650
|
|
605
651
|
# List of methods invoked by #stats.
|
606
652
|
# @version 5.0.0
|
607
|
-
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
653
|
+
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count, :busy_threads].freeze
|
608
654
|
|
609
655
|
# Returns a hash of stats about the running server for reporting purposes.
|
610
656
|
# @version 5.0.0
|
611
657
|
# @!attribute [r] stats
|
658
|
+
# @return [Hash] hash containing stat info from `Server` and `ThreadPool`
|
612
659
|
def stats
|
613
|
-
|
660
|
+
stats = @thread_pool&.stats || {}
|
661
|
+
stats[:max_threads] = @max_threads
|
662
|
+
stats[:requests_count] = @requests_count
|
663
|
+
stats
|
664
|
+
end
|
665
|
+
|
666
|
+
# below are 'delegations' to binder
|
667
|
+
# remove in Puma 7?
|
668
|
+
|
669
|
+
|
670
|
+
def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
|
671
|
+
@binder.add_tcp_listener host, port, optimize_for_latency, backlog
|
672
|
+
end
|
673
|
+
|
674
|
+
def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
|
675
|
+
backlog = 1024)
|
676
|
+
@binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
|
677
|
+
end
|
678
|
+
|
679
|
+
def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
|
680
|
+
@binder.add_unix_listener path, umask, mode, backlog
|
681
|
+
end
|
682
|
+
|
683
|
+
# @!attribute [r] connected_ports
|
684
|
+
def connected_ports
|
685
|
+
@binder.connected_ports
|
614
686
|
end
|
615
687
|
end
|
616
688
|
end
|
data/lib/puma/single.rb
CHANGED
@@ -16,7 +16,7 @@ module Puma
|
|
16
16
|
# @!attribute [r] stats
|
17
17
|
def stats
|
18
18
|
{
|
19
|
-
started_at: @started_at
|
19
|
+
started_at: utc_iso8601(@started_at)
|
20
20
|
}.merge(@server.stats).merge(super)
|
21
21
|
end
|
22
22
|
|
@@ -57,6 +57,8 @@ module Puma
|
|
57
57
|
|
58
58
|
@events.fire_on_booted!
|
59
59
|
|
60
|
+
debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
|
61
|
+
|
60
62
|
begin
|
61
63
|
server_thread.join
|
62
64
|
rescue Interrupt
|
data/lib/puma/state_file.rb
CHANGED
data/lib/puma/thread_pool.rb
CHANGED
@@ -44,10 +44,15 @@ module Puma
|
|
44
44
|
@name = name
|
45
45
|
@min = Integer(options[:min_threads])
|
46
46
|
@max = Integer(options[:max_threads])
|
47
|
+
# Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
|
48
|
+
# to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
|
49
|
+
# makes stubbing constants difficult.
|
50
|
+
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
47
51
|
@block = block
|
48
|
-
@extra = [::Puma::IOBuffer]
|
49
52
|
@out_of_band = options[:out_of_band]
|
50
53
|
@clean_thread_locals = options[:clean_thread_locals]
|
54
|
+
@before_thread_start = options[:before_thread_start]
|
55
|
+
@before_thread_exit = options[:before_thread_exit]
|
51
56
|
@reaping_time = options[:reaping_time]
|
52
57
|
@auto_trim_time = options[:auto_trim_time]
|
53
58
|
|
@@ -80,6 +85,18 @@ module Puma
|
|
80
85
|
end
|
81
86
|
end
|
82
87
|
|
88
|
+
# generate stats hash so as not to perform multiple locks
|
89
|
+
# @return [Hash] hash containing stat info from ThreadPool
|
90
|
+
def stats
|
91
|
+
with_mutex do
|
92
|
+
{ backlog: @todo.size,
|
93
|
+
running: @spawned,
|
94
|
+
pool_capacity: @waiting + (@max - @spawned),
|
95
|
+
busy_threads: @spawned - @waiting + @todo.size
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
83
100
|
# How many objects have yet to be processed by the pool?
|
84
101
|
#
|
85
102
|
def backlog
|
@@ -104,6 +121,7 @@ module Puma
|
|
104
121
|
def spawn_thread
|
105
122
|
@spawned += 1
|
106
123
|
|
124
|
+
trigger_before_thread_start_hooks
|
107
125
|
th = Thread.new(@spawned) do |spawned|
|
108
126
|
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
109
127
|
todo = @todo
|
@@ -112,8 +130,6 @@ module Puma
|
|
112
130
|
not_empty = @not_empty
|
113
131
|
not_full = @not_full
|
114
132
|
|
115
|
-
extra = @extra.map { |i| i.new }
|
116
|
-
|
117
133
|
while true
|
118
134
|
work = nil
|
119
135
|
|
@@ -124,6 +140,7 @@ module Puma
|
|
124
140
|
@spawned -= 1
|
125
141
|
@workers.delete th
|
126
142
|
not_full.signal
|
143
|
+
trigger_before_thread_exit_hooks
|
127
144
|
Thread.exit
|
128
145
|
end
|
129
146
|
|
@@ -147,7 +164,7 @@ module Puma
|
|
147
164
|
end
|
148
165
|
|
149
166
|
begin
|
150
|
-
@out_of_band_pending = true if block.call(work
|
167
|
+
@out_of_band_pending = true if block.call(work)
|
151
168
|
rescue Exception => e
|
152
169
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
153
170
|
end
|
@@ -161,6 +178,36 @@ module Puma
|
|
161
178
|
|
162
179
|
private :spawn_thread
|
163
180
|
|
181
|
+
def trigger_before_thread_start_hooks
|
182
|
+
return unless @before_thread_start&.any?
|
183
|
+
|
184
|
+
@before_thread_start.each do |b|
|
185
|
+
begin
|
186
|
+
b.call
|
187
|
+
rescue Exception => e
|
188
|
+
STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
|
194
|
+
private :trigger_before_thread_start_hooks
|
195
|
+
|
196
|
+
def trigger_before_thread_exit_hooks
|
197
|
+
return unless @before_thread_exit&.any?
|
198
|
+
|
199
|
+
@before_thread_exit.each do |b|
|
200
|
+
begin
|
201
|
+
b.call
|
202
|
+
rescue Exception => e
|
203
|
+
STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
|
209
|
+
private :trigger_before_thread_exit_hooks
|
210
|
+
|
164
211
|
# @version 5.0.0
|
165
212
|
def trigger_out_of_band_hook
|
166
213
|
return false unless @out_of_band&.any?
|
@@ -323,12 +370,12 @@ module Puma
|
|
323
370
|
end
|
324
371
|
|
325
372
|
def auto_trim!(timeout=@auto_trim_time)
|
326
|
-
@auto_trim = Automaton.new(self, timeout, "#{@name}
|
373
|
+
@auto_trim = Automaton.new(self, timeout, "#{@name} tp trim", :trim)
|
327
374
|
@auto_trim.start!
|
328
375
|
end
|
329
376
|
|
330
377
|
def auto_reap!(timeout=@reaping_time)
|
331
|
-
@reaper = Automaton.new(self, timeout, "#{@name}
|
378
|
+
@reaper = Automaton.new(self, timeout, "#{@name} tp reap", :reap)
|
332
379
|
@reaper.start!
|
333
380
|
end
|
334
381
|
|
@@ -347,8 +394,8 @@ module Puma
|
|
347
394
|
|
348
395
|
# Tell all threads in the pool to exit and wait for them to finish.
|
349
396
|
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
350
|
-
# Next, wait an extra +
|
351
|
-
# Finally, wait
|
397
|
+
# Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
|
398
|
+
# threads. Finally, wait 1 second for remaining threads to exit.
|
352
399
|
#
|
353
400
|
def shutdown(timeout=-1)
|
354
401
|
threads = with_mutex do
|
@@ -385,7 +432,7 @@ module Puma
|
|
385
432
|
t.raise ForceShutdown if t[:with_force_shutdown]
|
386
433
|
end
|
387
434
|
end
|
388
|
-
join.call(
|
435
|
+
join.call(@shutdown_grace_time)
|
389
436
|
|
390
437
|
# If threads are _still_ running, forcefully kill them and wait to finish.
|
391
438
|
threads.each(&:kill)
|
data/lib/puma/util.rb
CHANGED
@@ -11,7 +11,7 @@ module Puma
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# An instance method on Thread has been provided to address https://bugs.ruby-lang.org/issues/13632,
|
14
|
-
# which currently
|
14
|
+
# which currently affects some older versions of Ruby: 2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1
|
15
15
|
# Additional context: https://github.com/puma/puma/pull/1345
|
16
16
|
def purge_interrupt_queue
|
17
17
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|