puma 2.2.0 → 8.0.2
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 +7 -0
- data/History.md +3334 -0
- data/LICENSE +23 -20
- data/README.md +387 -100
- data/bin/puma-wild +25 -0
- data/bin/pumactl +1 -1
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/architecture.md +74 -0
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +137 -0
- data/docs/fork_worker.md +41 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- 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/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +9 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +73 -0
- data/docs/nginx.md +5 -5
- data/docs/plugins.md +42 -0
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +65 -0
- data/docs/signals.md +98 -0
- data/docs/stats.md +148 -0
- data/docs/systemd.md +253 -0
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +59 -2
- data/ext/puma_http11/http11_parser.c +319 -487
- data/ext/puma_http11/http11_parser.h +15 -13
- data/ext/puma_http11/http11_parser.java.rl +64 -94
- data/ext/puma_http11/http11_parser.rl +27 -24
- data/ext/puma_http11/http11_parser_common.rl +8 -8
- data/ext/puma_http11/mini_ssl.c +696 -38
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +241 -145
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +174 -221
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
- data/ext/puma_http11/puma_http11.c +183 -175
- data/lib/puma/app/status.rb +77 -29
- data/lib/puma/binder.rb +349 -119
- data/lib/puma/cli.rb +163 -801
- data/lib/puma/client.rb +627 -144
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +127 -0
- data/lib/puma/cluster.rb +634 -0
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +115 -0
- data/lib/puma/configuration.rb +420 -198
- data/lib/puma/const.rb +266 -115
- data/lib/puma/control_cli.rb +219 -120
- data/lib/puma/detect.rb +56 -2
- data/lib/puma/dsl.rb +1562 -0
- data/lib/puma/error_logger.rb +115 -0
- data/lib/puma/events.rb +44 -60
- data/lib/puma/io_buffer.rb +48 -5
- data/lib/puma/jruby_restart.rb +2 -51
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +102 -0
- data/lib/puma/launcher.rb +501 -0
- data/lib/puma/log_writer.rb +153 -0
- data/lib/puma/minissl/context_builder.rb +96 -0
- data/lib/puma/minissl.rb +355 -37
- data/lib/puma/null_io.rb +79 -12
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +102 -133
- data/lib/puma/response.rb +532 -0
- data/lib/puma/runner.rb +211 -0
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +582 -445
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +72 -0
- data/lib/puma/state_file.rb +69 -0
- data/lib/puma/thread_pool.rb +389 -57
- data/lib/puma/util.rb +125 -0
- data/lib/puma.rb +80 -6
- data/lib/rack/handler/puma.rb +125 -47
- data/tools/Dockerfile +26 -0
- data/tools/trickletest.rb +44 -0
- metadata +88 -148
- data/COPYING +0 -55
- data/Gemfile +0 -10
- data/History.txt +0 -302
- data/Manifest.txt +0 -60
- data/Rakefile +0 -165
- data/TODO +0 -5
- data/docs/config.md +0 -0
- data/ext/puma_http11/ext_help.h +0 -15
- data/ext/puma_http11/io_buffer.c +0 -154
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/capistrano.rb +0 -34
- data/lib/puma/compat.rb +0 -11
- data/lib/puma/daemon_ext.rb +0 -20
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack_patch.rb +0 -25
- data/puma.gemspec +0 -45
- data/test/test_app_status.rb +0 -88
- data/test/test_cli.rb +0 -171
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -126
- data/test/test_integration.rb +0 -154
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -25
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -224
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_thread_pool.rb +0 -146
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -332
- data/tools/jungle/init.d/run-puma +0 -3
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -52
data/lib/puma/server.rb
CHANGED
|
@@ -1,104 +1,195 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'stringio'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
require 'puma/puma_http11'
|
|
17
|
-
|
|
18
|
-
unless Puma.const_defined? "IOBuffer"
|
|
19
|
-
require 'puma/io_buffer'
|
|
20
|
-
end
|
|
5
|
+
require_relative 'thread_pool'
|
|
6
|
+
require_relative 'const'
|
|
7
|
+
require_relative 'log_writer'
|
|
8
|
+
require_relative 'events'
|
|
9
|
+
require_relative 'null_io'
|
|
10
|
+
require_relative 'reactor'
|
|
11
|
+
require_relative 'client'
|
|
12
|
+
require_relative 'binder'
|
|
13
|
+
require_relative 'util'
|
|
14
|
+
require_relative 'response'
|
|
15
|
+
require_relative 'configuration'
|
|
16
|
+
require_relative 'cluster_accept_loop_delay'
|
|
21
17
|
|
|
22
18
|
require 'socket'
|
|
19
|
+
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
|
23
20
|
|
|
24
21
|
module Puma
|
|
25
|
-
|
|
26
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
|
23
|
+
#
|
|
24
|
+
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
|
25
|
+
# to generate one or more `Puma::Server` instances capable of handling requests.
|
|
26
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
|
27
|
+
#
|
|
28
|
+
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
|
29
|
+
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
|
30
|
+
#
|
|
31
|
+
# Each `Puma::Server` will have one reactor and one thread pool.
|
|
27
32
|
class Server
|
|
33
|
+
module FiberPerRequest
|
|
34
|
+
def handle_request(processor, client, requests)
|
|
35
|
+
Fiber.new do
|
|
36
|
+
super
|
|
37
|
+
end.resume
|
|
38
|
+
end
|
|
39
|
+
end
|
|
28
40
|
|
|
29
|
-
include
|
|
30
|
-
|
|
41
|
+
include Const
|
|
42
|
+
include Response
|
|
31
43
|
|
|
44
|
+
attr_reader :options
|
|
32
45
|
attr_reader :thread
|
|
46
|
+
attr_reader :log_writer
|
|
33
47
|
attr_reader :events
|
|
34
|
-
|
|
48
|
+
attr_reader :min_threads, :max_threads # for #stats
|
|
49
|
+
attr_reader :requests_count # @version 5.0.0
|
|
35
50
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
# @todo the following may be deprecated in the future
|
|
52
|
+
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
|
|
53
|
+
:leak_stack_on_error,
|
|
54
|
+
:persistent_timeout, :reaping_time
|
|
55
|
+
|
|
56
|
+
attr_accessor :app
|
|
57
|
+
attr_accessor :binder
|
|
40
58
|
|
|
41
59
|
# Create a server for the rack app +app+.
|
|
42
60
|
#
|
|
43
|
-
# +
|
|
44
|
-
#
|
|
61
|
+
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
|
62
|
+
#
|
|
63
|
+
# +events+ is a Puma::Events object used to notify application status events.
|
|
45
64
|
#
|
|
46
65
|
# Server#run returns a thread that you can join on to wait for the server
|
|
47
|
-
# to do
|
|
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`.
|
|
48
71
|
#
|
|
49
|
-
|
|
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 = {})
|
|
50
77
|
@app = app
|
|
51
|
-
@events = events
|
|
52
|
-
|
|
53
|
-
@check, @notify = Puma::Util.pipe
|
|
78
|
+
@events = events || Events.new
|
|
54
79
|
|
|
80
|
+
@check, @notify = nil
|
|
55
81
|
@status = :stop
|
|
56
82
|
|
|
57
|
-
@min_threads = 0
|
|
58
|
-
@max_threads = 16
|
|
59
|
-
@auto_trim_time = 1
|
|
60
|
-
|
|
61
83
|
@thread = nil
|
|
62
84
|
@thread_pool = nil
|
|
85
|
+
@reactor = nil
|
|
86
|
+
|
|
87
|
+
@env_set_http_version = nil
|
|
88
|
+
|
|
89
|
+
@options = if options.is_a?(UserFileDefaultOptions)
|
|
90
|
+
options
|
|
91
|
+
else
|
|
92
|
+
UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
|
|
93
|
+
end
|
|
63
94
|
|
|
64
|
-
@
|
|
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
|
|
65
118
|
|
|
66
|
-
|
|
67
|
-
@
|
|
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
|
|
68
130
|
|
|
69
|
-
|
|
131
|
+
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
|
132
|
+
@leak_stack_on_error = @options[:environment] ? temp : true
|
|
70
133
|
|
|
71
|
-
@
|
|
134
|
+
@binder = Binder.new(log_writer, @options)
|
|
72
135
|
|
|
73
136
|
ENV['RACK_ENV'] ||= "development"
|
|
74
|
-
end
|
|
75
137
|
|
|
76
|
-
|
|
138
|
+
@mode = :http
|
|
139
|
+
|
|
140
|
+
@precheck_closing = true
|
|
141
|
+
|
|
142
|
+
@requests_count = 0
|
|
77
143
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
forward :add_unix_listener, :@binder
|
|
144
|
+
@idle_timeout_reached = false
|
|
145
|
+
end
|
|
81
146
|
|
|
82
147
|
def inherit_binder(bind)
|
|
83
148
|
@binder = bind
|
|
84
|
-
|
|
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?
|
|
85
170
|
end
|
|
86
171
|
|
|
87
172
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
|
88
173
|
# packetizes our stream. This improves both latency and throughput.
|
|
174
|
+
# socket parameter may be an MiniSSL::Socket, so use to_io
|
|
89
175
|
#
|
|
90
|
-
if
|
|
176
|
+
if tcp_cork_supported?
|
|
91
177
|
# 6 == Socket::IPPROTO_TCP
|
|
92
178
|
# 3 == TCP_CORK
|
|
93
179
|
# 1/0 == turn on/off
|
|
94
180
|
def cork_socket(socket)
|
|
95
|
-
|
|
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
|
|
96
186
|
end
|
|
97
187
|
|
|
98
188
|
def uncork_socket(socket)
|
|
189
|
+
skt = socket.to_io
|
|
99
190
|
begin
|
|
100
|
-
|
|
101
|
-
rescue IOError
|
|
191
|
+
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
|
192
|
+
rescue IOError, SystemCallError
|
|
102
193
|
end
|
|
103
194
|
end
|
|
104
195
|
else
|
|
@@ -109,12 +200,50 @@ module Puma
|
|
|
109
200
|
end
|
|
110
201
|
end
|
|
111
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
|
|
112
228
|
def backlog
|
|
113
|
-
@thread_pool
|
|
229
|
+
@thread_pool&.backlog
|
|
114
230
|
end
|
|
115
231
|
|
|
232
|
+
# @!attribute [r] running
|
|
116
233
|
def running
|
|
117
|
-
@thread_pool
|
|
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
|
|
118
247
|
end
|
|
119
248
|
|
|
120
249
|
# Runs the server.
|
|
@@ -123,101 +252,210 @@ module Puma
|
|
|
123
252
|
# up in the background to handle requests. Otherwise requests
|
|
124
253
|
# are handled synchronously.
|
|
125
254
|
#
|
|
126
|
-
def run(background=true)
|
|
255
|
+
def run(background=true, thread_name: 'srv')
|
|
127
256
|
BasicSocket.do_not_reverse_lookup = true
|
|
128
257
|
|
|
258
|
+
@events.fire :state, :booting
|
|
259
|
+
|
|
129
260
|
@status = :run
|
|
130
261
|
|
|
131
|
-
@thread_pool = ThreadPool.new(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
process_now = false
|
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) do |processor, client|
|
|
263
|
+
process_client(processor, client)
|
|
264
|
+
end
|
|
135
265
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
rescue ConnectionError
|
|
144
|
-
client.close
|
|
145
|
-
else
|
|
146
|
-
if process_now
|
|
147
|
-
process_client client, buffer
|
|
148
|
-
else
|
|
149
|
-
client.set_timeout @first_data_timeout
|
|
150
|
-
@reactor.add client
|
|
151
|
-
end
|
|
152
|
-
end
|
|
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
|
|
153
273
|
end
|
|
154
274
|
|
|
155
|
-
@
|
|
275
|
+
@thread_pool.auto_reap! if options[:reaping_time]
|
|
276
|
+
@thread_pool.auto_trim! if @min_threads != @max_threads && options[:auto_trim_time]
|
|
156
277
|
|
|
157
|
-
@
|
|
278
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
|
158
279
|
|
|
159
|
-
|
|
160
|
-
@thread_pool.auto_trim!(@auto_trim_time)
|
|
161
|
-
end
|
|
280
|
+
@events.fire :state, :running
|
|
162
281
|
|
|
163
282
|
if background
|
|
164
|
-
@thread = Thread.new
|
|
283
|
+
@thread = Thread.new do
|
|
284
|
+
Puma.set_thread_name thread_name
|
|
285
|
+
handle_servers
|
|
286
|
+
end
|
|
165
287
|
return @thread
|
|
166
288
|
else
|
|
167
289
|
handle_servers
|
|
168
290
|
end
|
|
169
291
|
end
|
|
170
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
|
+
|
|
171
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
|
+
|
|
172
341
|
begin
|
|
173
342
|
check = @check
|
|
174
343
|
sockets = [check] + @binder.ios
|
|
175
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
|
|
176
358
|
|
|
177
|
-
while @status == :run
|
|
359
|
+
while @status == :run || (drain && shutting_down?)
|
|
178
360
|
begin
|
|
179
|
-
ios = IO.select sockets
|
|
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
|
+
|
|
180
383
|
ios.first.each do |sock|
|
|
181
384
|
if sock == check
|
|
182
385
|
break if handle_check
|
|
183
386
|
else
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
190
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
|
|
191
410
|
end
|
|
192
411
|
end
|
|
193
|
-
rescue Errno::
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
rescue
|
|
197
|
-
@
|
|
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"
|
|
198
417
|
end
|
|
199
418
|
end
|
|
200
419
|
|
|
201
|
-
|
|
202
|
-
@
|
|
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
|
|
203
427
|
|
|
204
|
-
@
|
|
428
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
|
205
429
|
rescue Exception => e
|
|
206
|
-
|
|
207
|
-
STDERR.puts e.backtrace
|
|
430
|
+
@log_writer.unknown_error e, nil, "Exception handling servers"
|
|
208
431
|
ensure
|
|
209
|
-
|
|
210
|
-
@notify.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
214
438
|
end
|
|
439
|
+
@notify = nil
|
|
440
|
+
@check = nil
|
|
215
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
|
|
216
454
|
end
|
|
217
455
|
|
|
218
456
|
# :nodoc:
|
|
219
457
|
def handle_check
|
|
220
|
-
cmd = @check.read(1)
|
|
458
|
+
cmd = @check.read(1)
|
|
221
459
|
|
|
222
460
|
case cmd
|
|
223
461
|
when STOP_COMMAND
|
|
@@ -231,406 +469,305 @@ module Puma
|
|
|
231
469
|
return true
|
|
232
470
|
end
|
|
233
471
|
|
|
234
|
-
|
|
472
|
+
false
|
|
235
473
|
end
|
|
236
474
|
|
|
237
|
-
# Given a connection on +client+, handle the incoming requests
|
|
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.
|
|
238
479
|
#
|
|
239
|
-
# This method
|
|
480
|
+
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
|
240
481
|
# indicates that it supports keep alive, wait for another request before
|
|
241
482
|
# returning.
|
|
242
483
|
#
|
|
243
|
-
|
|
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
|
+
|
|
244
490
|
begin
|
|
245
|
-
|
|
491
|
+
if @queue_requests && !client.eagerly_finish
|
|
246
492
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
when false
|
|
250
|
-
return
|
|
251
|
-
when :async
|
|
493
|
+
client.set_timeout(@first_data_timeout)
|
|
494
|
+
if @reactor.add client
|
|
252
495
|
close_socket = false
|
|
253
|
-
return
|
|
254
|
-
when true
|
|
255
|
-
buffer.reset
|
|
256
|
-
|
|
257
|
-
unless client.reset(@status == :run)
|
|
258
|
-
close_socket = false
|
|
259
|
-
client.set_timeout @persistent_timeout
|
|
260
|
-
@reactor.add client
|
|
261
|
-
return
|
|
262
|
-
end
|
|
496
|
+
return false
|
|
263
497
|
end
|
|
264
498
|
end
|
|
265
499
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# The client doesn't know HTTP well
|
|
271
|
-
rescue HttpParserError => e
|
|
272
|
-
client.write_400
|
|
500
|
+
with_force_shutdown(client) do
|
|
501
|
+
client.finish(@first_data_timeout)
|
|
502
|
+
end
|
|
273
503
|
|
|
274
|
-
|
|
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
|
|
275
514
|
|
|
276
|
-
|
|
277
|
-
rescue StandardError => e
|
|
278
|
-
client.write_500
|
|
515
|
+
client.reset
|
|
279
516
|
|
|
280
|
-
|
|
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
|
|
281
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
|
|
282
547
|
ensure
|
|
283
|
-
|
|
548
|
+
client.io_buffer.reset
|
|
284
549
|
|
|
285
|
-
|
|
286
|
-
client.close if close_socket
|
|
287
|
-
rescue IOError, SystemCallError
|
|
288
|
-
# Already closed
|
|
289
|
-
rescue StandardError => e
|
|
290
|
-
@events.unknown_error self, e, "Client"
|
|
291
|
-
end
|
|
550
|
+
close_client_safely(client) if close_socket
|
|
292
551
|
end
|
|
293
552
|
end
|
|
294
553
|
|
|
295
|
-
#
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
306
|
-
end
|
|
307
|
-
else
|
|
308
|
-
env[SERVER_NAME] = LOCALHOST
|
|
309
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
unless env[REQUEST_PATH]
|
|
313
|
-
# it might be a dumbass full host request header
|
|
314
|
-
uri = URI.parse(env[REQUEST_URI])
|
|
315
|
-
env[REQUEST_PATH] = uri.path
|
|
316
|
-
|
|
317
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
|
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
|
|
321
564
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
# intermediary acting on behalf of the actual source client."
|
|
329
|
-
#
|
|
330
|
-
env[REMOTE_ADDR] = client.peeraddr.last
|
|
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!
|
|
331
571
|
end
|
|
332
572
|
|
|
333
|
-
|
|
334
|
-
|
|
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
|
|
335
594
|
end
|
|
336
595
|
|
|
337
|
-
#
|
|
338
|
-
# in +body+, finish reading the body if there is one and invoke
|
|
339
|
-
# the rack app. Then construct the response and write it back to
|
|
340
|
-
# +client+
|
|
341
|
-
#
|
|
342
|
-
# +cl+ is the previously fetched Content-Length header if there
|
|
343
|
-
# was one. This is an optimization to keep from having to look
|
|
344
|
-
# it up again.
|
|
596
|
+
# A fallback rack response if +@app+ raises as exception.
|
|
345
597
|
#
|
|
346
|
-
def
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
env[PUMA_SOCKET] = client
|
|
353
|
-
|
|
354
|
-
env[HIJACK_P] = true
|
|
355
|
-
env[HIJACK] = req
|
|
356
|
-
|
|
357
|
-
body = req.body
|
|
358
|
-
|
|
359
|
-
head = env[REQUEST_METHOD] == HEAD
|
|
360
|
-
|
|
361
|
-
env[RACK_INPUT] = body
|
|
362
|
-
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
|
363
|
-
|
|
364
|
-
# A rack extension. If the app writes #call'ables to this
|
|
365
|
-
# array, we will invoke them when the request is done.
|
|
366
|
-
#
|
|
367
|
-
after_reply = env[RACK_AFTER_REPLY] = []
|
|
368
|
-
|
|
369
|
-
begin
|
|
370
|
-
begin
|
|
371
|
-
status, headers, res_body = @app.call(env)
|
|
372
|
-
|
|
373
|
-
return :async if req.hijacked
|
|
374
|
-
|
|
375
|
-
status = status.to_i
|
|
376
|
-
|
|
377
|
-
if status == -1
|
|
378
|
-
unless headers.empty? and res_body == []
|
|
379
|
-
raise "async response must have empty headers and body"
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
return :async
|
|
383
|
-
end
|
|
384
|
-
rescue StandardError => e
|
|
385
|
-
@events.unknown_error self, e, "Rack app"
|
|
386
|
-
|
|
387
|
-
status, headers, res_body = lowlevel_error(e)
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
content_length = nil
|
|
391
|
-
no_body = head
|
|
392
|
-
|
|
393
|
-
if res_body.kind_of? Array and res_body.size == 1
|
|
394
|
-
content_length = res_body[0].bytesize
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
cork_socket client
|
|
398
|
-
|
|
399
|
-
line_ending = LINE_END
|
|
400
|
-
colon = COLON
|
|
401
|
-
|
|
402
|
-
if env[HTTP_VERSION] == HTTP_11
|
|
403
|
-
allow_chunked = true
|
|
404
|
-
keep_alive = env[HTTP_CONNECTION] != CLOSE
|
|
405
|
-
include_keepalive_header = false
|
|
406
|
-
|
|
407
|
-
# An optimization. The most common response is 200, so we can
|
|
408
|
-
# reply with the proper 200 status without having to compute
|
|
409
|
-
# the response header.
|
|
410
|
-
#
|
|
411
|
-
if status == 200
|
|
412
|
-
lines << HTTP_11_200
|
|
413
|
-
else
|
|
414
|
-
lines.append "HTTP/1.1 ", status.to_s, " ",
|
|
415
|
-
HTTP_STATUS_CODES[status], line_ending
|
|
416
|
-
|
|
417
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
418
|
-
end
|
|
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)
|
|
419
604
|
else
|
|
420
|
-
|
|
421
|
-
keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
|
|
422
|
-
include_keepalive_header = keep_alive
|
|
423
|
-
|
|
424
|
-
# Same optimization as above for HTTP/1.1
|
|
425
|
-
#
|
|
426
|
-
if status == 200
|
|
427
|
-
lines << HTTP_10_200
|
|
428
|
-
else
|
|
429
|
-
lines.append "HTTP/1.0 ", status.to_s, " ",
|
|
430
|
-
HTTP_STATUS_CODES[status], line_ending
|
|
431
|
-
|
|
432
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
433
|
-
end
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
response_hijack = nil
|
|
437
|
-
|
|
438
|
-
headers.each do |k, vs|
|
|
439
|
-
case k
|
|
440
|
-
when CONTENT_LENGTH2
|
|
441
|
-
content_length = vs
|
|
442
|
-
next
|
|
443
|
-
when TRANSFER_ENCODING
|
|
444
|
-
allow_chunked = false
|
|
445
|
-
content_length = nil
|
|
446
|
-
when CONTENT_TYPE
|
|
447
|
-
next if no_body
|
|
448
|
-
when HIJACK
|
|
449
|
-
response_hijack = vs
|
|
450
|
-
next
|
|
451
|
-
end
|
|
452
|
-
|
|
453
|
-
vs.split(NEWLINE).each do |v|
|
|
454
|
-
lines.append k, colon, v, line_ending
|
|
455
|
-
end
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
if no_body
|
|
459
|
-
lines << line_ending
|
|
460
|
-
fast_write client, lines.to_s
|
|
461
|
-
return keep_alive
|
|
462
|
-
end
|
|
463
|
-
|
|
464
|
-
if include_keepalive_header
|
|
465
|
-
lines << CONNECTION_KEEP_ALIVE
|
|
466
|
-
elsif !keep_alive
|
|
467
|
-
lines << CONNECTION_CLOSE
|
|
605
|
+
return handler.call(e, env, status)
|
|
468
606
|
end
|
|
607
|
+
end
|
|
469
608
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
lines << TRANSFER_ENCODING_CHUNKED
|
|
476
|
-
chunked = true
|
|
477
|
-
end
|
|
478
|
-
end
|
|
479
|
-
|
|
480
|
-
lines << line_ending
|
|
481
|
-
|
|
482
|
-
fast_write client, lines.to_s
|
|
483
|
-
|
|
484
|
-
if response_hijack
|
|
485
|
-
response_hijack.call client
|
|
486
|
-
return :async
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
res_body.each do |part|
|
|
490
|
-
if chunked
|
|
491
|
-
client.syswrite part.bytesize.to_s(16)
|
|
492
|
-
client.syswrite line_ending
|
|
493
|
-
fast_write client, part
|
|
494
|
-
client.syswrite line_ending
|
|
495
|
-
else
|
|
496
|
-
fast_write client, part
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
client.flush
|
|
500
|
-
end
|
|
501
|
-
|
|
502
|
-
if chunked
|
|
503
|
-
client.syswrite CLOSE_CHUNKED
|
|
504
|
-
client.flush
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
ensure
|
|
508
|
-
uncork_socket client
|
|
509
|
-
|
|
510
|
-
body.close
|
|
511
|
-
res_body.close if res_body.respond_to? :close
|
|
512
|
-
|
|
513
|
-
after_reply.each { |o| o.call }
|
|
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, {}, [""]]
|
|
514
614
|
end
|
|
615
|
+
end
|
|
515
616
|
|
|
516
|
-
|
|
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)
|
|
517
628
|
end
|
|
629
|
+
private :response_to_error
|
|
518
630
|
|
|
519
|
-
#
|
|
520
|
-
# plus a potential Content-Length value +cl+, finish reading
|
|
521
|
-
# the body and return it.
|
|
522
|
-
#
|
|
523
|
-
# If the body is larger than MAX_BODY, a Tempfile object is used
|
|
524
|
-
# for the body, otherwise a StringIO is used.
|
|
631
|
+
# Wait for all outstanding requests to finish.
|
|
525
632
|
#
|
|
526
|
-
def
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
633
|
+
def graceful_shutdown
|
|
634
|
+
if @status != :restart
|
|
635
|
+
@binder.close
|
|
636
|
+
end
|
|
530
637
|
|
|
531
|
-
|
|
638
|
+
@thread_pool.shutdown(options[:force_shutdown_after])
|
|
639
|
+
end
|
|
532
640
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
|
537
649
|
else
|
|
538
|
-
|
|
539
|
-
# encoding as body.
|
|
540
|
-
stream = StringIO.new body[0,0]
|
|
650
|
+
raise e
|
|
541
651
|
end
|
|
652
|
+
end
|
|
653
|
+
private :notify_safely
|
|
542
654
|
|
|
543
|
-
|
|
655
|
+
# Stops the acceptor thread and then causes the processor threads to finish
|
|
656
|
+
# off the request queue before finally exiting.
|
|
544
657
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
658
|
+
def stop(sync=false)
|
|
659
|
+
notify_safely(STOP_COMMAND)
|
|
660
|
+
@thread.join if @thread && sync
|
|
661
|
+
end
|
|
548
662
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
end
|
|
663
|
+
def halt(sync=false)
|
|
664
|
+
notify_safely(HALT_COMMAND)
|
|
665
|
+
@thread.join if @thread && sync
|
|
666
|
+
end
|
|
554
667
|
|
|
555
|
-
|
|
668
|
+
def begin_restart(sync=false)
|
|
669
|
+
notify_safely(RESTART_COMMAND)
|
|
670
|
+
@thread.join if @thread && sync
|
|
671
|
+
end
|
|
556
672
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
unless chunk
|
|
561
|
-
stream.close
|
|
562
|
-
return nil
|
|
563
|
-
end
|
|
673
|
+
def shutting_down?
|
|
674
|
+
@status == :stop || @status == :restart
|
|
675
|
+
end
|
|
564
676
|
|
|
565
|
-
|
|
566
|
-
|
|
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?
|
|
567
710
|
|
|
568
|
-
stream.rewind
|
|
569
711
|
|
|
570
|
-
|
|
712
|
+
def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
|
|
713
|
+
@binder.add_tcp_listener host, port, optimize_for_latency, backlog
|
|
571
714
|
end
|
|
572
715
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if @leak_stack_on_error
|
|
577
|
-
[500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
|
578
|
-
else
|
|
579
|
-
[500, {}, ["A really lowlevel plumbing error occured. Please contact your local Maytag(tm) repair man.\n"]]
|
|
580
|
-
end
|
|
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
|
|
581
719
|
end
|
|
582
720
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
def graceful_shutdown
|
|
586
|
-
@thread_pool.shutdown if @thread_pool
|
|
721
|
+
def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
|
|
722
|
+
@binder.add_unix_listener path, umask, mode, backlog
|
|
587
723
|
end
|
|
588
724
|
|
|
589
|
-
#
|
|
590
|
-
# off the request queue before finally exiting.
|
|
725
|
+
# Updates the minimum and maximum number of threads in the thread pool.
|
|
591
726
|
#
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
|
597
755
|
end
|
|
598
756
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
def halt(sync=false)
|
|
603
|
-
begin
|
|
604
|
-
@notify << HALT_COMMAND
|
|
605
|
-
rescue IOError
|
|
606
|
-
# The server, in another thread, is shutting down
|
|
757
|
+
if min < 0
|
|
758
|
+
@log_writer.log "`min' value cannot be less than 0"
|
|
759
|
+
return
|
|
607
760
|
end
|
|
608
761
|
|
|
609
|
-
@
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
def begin_restart
|
|
613
|
-
begin
|
|
614
|
-
@notify << RESTART_COMMAND
|
|
615
|
-
rescue IOError
|
|
616
|
-
# The server, in another thread, is shutting down
|
|
762
|
+
@thread_pool&.with_mutex do
|
|
763
|
+
@thread_pool.min, @thread_pool.max = min, max
|
|
764
|
+
@min_threads, @max_threads = min, max
|
|
617
765
|
end
|
|
618
766
|
end
|
|
619
767
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
begin
|
|
624
|
-
n = io.syswrite str
|
|
625
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
626
|
-
IO.select(nil, [io], nil, 1)
|
|
627
|
-
retry
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
return if n == str.bytesize
|
|
631
|
-
str = str.byteslice(n..-1)
|
|
632
|
-
end
|
|
768
|
+
# @!attribute [r] connected_ports
|
|
769
|
+
def connected_ports
|
|
770
|
+
@binder.connected_ports
|
|
633
771
|
end
|
|
634
|
-
private :fast_write
|
|
635
772
|
end
|
|
636
773
|
end
|