puma 3.12.0 → 5.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +1413 -439
- data/LICENSE +23 -20
- data/README.md +131 -60
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +24 -19
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +38 -13
- data/docs/fork_worker.md +33 -0
- data/docs/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +20 -10
- data/docs/rails_dev_mode.md +29 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +7 -6
- data/docs/stats.md +142 -0
- data/docs/systemd.md +48 -70
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +27 -0
- data/ext/puma_http11/http11_parser.c +84 -109
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +4 -2
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +262 -87
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
- data/ext/puma_http11/puma_http11.c +34 -50
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +197 -144
- data/lib/puma/cli.rb +17 -15
- data/lib/puma/client.rb +257 -226
- data/lib/puma/cluster/worker.rb +176 -0
- data/lib/puma/cluster/worker_handle.rb +90 -0
- data/lib/puma/cluster.rb +223 -212
- data/lib/puma/commonlogger.rb +4 -2
- data/lib/puma/configuration.rb +58 -51
- data/lib/puma/const.rb +41 -19
- data/lib/puma/control_cli.rb +117 -73
- data/lib/puma/detect.rb +26 -3
- data/lib/puma/dsl.rb +531 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +57 -31
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +182 -70
- data/lib/puma/minissl/context_builder.rb +79 -0
- data/lib/puma/minissl.rb +149 -48
- data/lib/puma/null_io.rb +15 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +8 -12
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +4 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +87 -316
- data/lib/puma/request.rb +456 -0
- data/lib/puma/runner.rb +33 -52
- data/lib/puma/server.rb +288 -679
- data/lib/puma/single.rb +13 -67
- data/lib/puma/state_file.rb +10 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +131 -81
- data/lib/puma/util.rb +14 -6
- data/lib/puma.rb +54 -0
- data/lib/rack/handler/puma.rb +8 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -29
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -39
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/server.rb
CHANGED
@@ -1,24 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'stringio'
|
2
4
|
|
3
5
|
require 'puma/thread_pool'
|
4
6
|
require 'puma/const'
|
5
7
|
require 'puma/events'
|
6
8
|
require 'puma/null_io'
|
7
|
-
require 'puma/compat'
|
8
9
|
require 'puma/reactor'
|
9
10
|
require 'puma/client'
|
10
11
|
require 'puma/binder'
|
11
|
-
require 'puma/delegation'
|
12
|
-
require 'puma/accept_nonblock'
|
13
12
|
require 'puma/util'
|
14
|
-
|
15
|
-
require 'puma/
|
16
|
-
|
17
|
-
unless Puma.const_defined? "IOBuffer"
|
18
|
-
require 'puma/io_buffer'
|
19
|
-
end
|
13
|
+
require 'puma/io_buffer'
|
14
|
+
require 'puma/request'
|
20
15
|
|
21
16
|
require 'socket'
|
17
|
+
require 'forwardable'
|
22
18
|
|
23
19
|
module Puma
|
24
20
|
|
@@ -26,7 +22,7 @@ module Puma
|
|
26
22
|
#
|
27
23
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
28
24
|
# to generate one or more `Puma::Server` instances capable of handling requests.
|
29
|
-
# Each Puma process will contain one `Puma::Server`
|
25
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
30
26
|
#
|
31
27
|
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
32
28
|
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
@@ -35,18 +31,31 @@ module Puma
|
|
35
31
|
class Server
|
36
32
|
|
37
33
|
include Puma::Const
|
38
|
-
|
34
|
+
include Request
|
35
|
+
extend Forwardable
|
39
36
|
|
40
37
|
attr_reader :thread
|
41
38
|
attr_reader :events
|
39
|
+
attr_reader :min_threads, :max_threads # for #stats
|
40
|
+
attr_reader :requests_count # @version 5.0.0
|
41
|
+
|
42
|
+
# @todo the following may be deprecated in the future
|
43
|
+
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
|
44
|
+
:leak_stack_on_error,
|
45
|
+
:persistent_timeout, :reaping_time
|
46
|
+
|
47
|
+
# @deprecated v6.0.0
|
48
|
+
attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
|
49
|
+
:leak_stack_on_error, :min_threads, :max_threads,
|
50
|
+
:persistent_timeout, :reaping_time
|
51
|
+
|
42
52
|
attr_accessor :app
|
53
|
+
attr_accessor :binder
|
43
54
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
attr_accessor :reaping_time
|
49
|
-
attr_accessor :first_data_timeout
|
55
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
|
56
|
+
:add_unix_listener, :connected_ports
|
57
|
+
|
58
|
+
ThreadLocalKey = :puma_server
|
50
59
|
|
51
60
|
# Create a server for the rack app +app+.
|
52
61
|
#
|
@@ -56,88 +65,115 @@ module Puma
|
|
56
65
|
# Server#run returns a thread that you can join on to wait for the server
|
57
66
|
# to do its work.
|
58
67
|
#
|
68
|
+
# @note Several instance variables exist so they are available for testing,
|
69
|
+
# and have default values set via +fetch+. Normally the values are set via
|
70
|
+
# `::Puma::Configuration.puma_default_options`.
|
71
|
+
#
|
59
72
|
def initialize(app, events=Events.stdio, options={})
|
60
73
|
@app = app
|
61
74
|
@events = events
|
62
75
|
|
63
|
-
@check, @notify =
|
64
|
-
|
76
|
+
@check, @notify = nil
|
65
77
|
@status = :stop
|
66
78
|
|
67
|
-
@min_threads = 0
|
68
|
-
@max_threads = 16
|
69
79
|
@auto_trim_time = 30
|
70
80
|
@reaping_time = 1
|
71
81
|
|
72
82
|
@thread = nil
|
73
83
|
@thread_pool = nil
|
74
|
-
@early_hints = nil
|
75
84
|
|
76
|
-
@
|
77
|
-
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
85
|
+
@options = options
|
78
86
|
|
79
|
-
@
|
80
|
-
@
|
87
|
+
@early_hints = options.fetch :early_hints, nil
|
88
|
+
@first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
|
89
|
+
@min_threads = options.fetch :min_threads, 0
|
90
|
+
@max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
|
91
|
+
@persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
|
92
|
+
@queue_requests = options.fetch :queue_requests, true
|
93
|
+
@max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
|
94
|
+
@io_selector_backend = options.fetch :io_selector_backend, :auto
|
81
95
|
|
82
|
-
|
96
|
+
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
97
|
+
@leak_stack_on_error = @options[:environment] ? temp : true
|
83
98
|
|
84
|
-
@
|
85
|
-
@queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
|
99
|
+
@binder = Binder.new(events)
|
86
100
|
|
87
101
|
ENV['RACK_ENV'] ||= "development"
|
88
102
|
|
89
103
|
@mode = :http
|
90
104
|
|
91
105
|
@precheck_closing = true
|
92
|
-
end
|
93
|
-
|
94
|
-
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
95
106
|
|
96
|
-
|
97
|
-
|
98
|
-
forward :add_unix_listener, :@binder
|
99
|
-
forward :connected_port, :@binder
|
107
|
+
@requests_count = 0
|
108
|
+
end
|
100
109
|
|
101
110
|
def inherit_binder(bind)
|
102
111
|
@binder = bind
|
103
|
-
@own_binder = false
|
104
112
|
end
|
105
113
|
|
106
|
-
|
107
|
-
|
114
|
+
class << self
|
115
|
+
# @!attribute [r] current
|
116
|
+
def current
|
117
|
+
Thread.current[ThreadLocalKey]
|
118
|
+
end
|
119
|
+
|
120
|
+
# :nodoc:
|
121
|
+
# @version 5.0.0
|
122
|
+
def tcp_cork_supported?
|
123
|
+
Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
|
124
|
+
end
|
125
|
+
|
126
|
+
# :nodoc:
|
127
|
+
# @version 5.0.0
|
128
|
+
def closed_socket_supported?
|
129
|
+
Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
|
130
|
+
end
|
131
|
+
private :tcp_cork_supported?
|
132
|
+
private :closed_socket_supported?
|
108
133
|
end
|
109
134
|
|
110
135
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
111
136
|
# packetizes our stream. This improves both latency and throughput.
|
137
|
+
# socket parameter may be an MiniSSL::Socket, so use to_io
|
112
138
|
#
|
113
|
-
if
|
114
|
-
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
115
|
-
|
139
|
+
if tcp_cork_supported?
|
116
140
|
# 6 == Socket::IPPROTO_TCP
|
117
141
|
# 3 == TCP_CORK
|
118
142
|
# 1/0 == turn on/off
|
119
143
|
def cork_socket(socket)
|
144
|
+
skt = socket.to_io
|
120
145
|
begin
|
121
|
-
|
146
|
+
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
|
122
147
|
rescue IOError, SystemCallError
|
123
148
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
124
149
|
end
|
125
150
|
end
|
126
151
|
|
127
152
|
def uncork_socket(socket)
|
153
|
+
skt = socket.to_io
|
128
154
|
begin
|
129
|
-
|
155
|
+
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
130
156
|
rescue IOError, SystemCallError
|
131
157
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
132
158
|
end
|
133
159
|
end
|
160
|
+
else
|
161
|
+
def cork_socket(socket)
|
162
|
+
end
|
163
|
+
|
164
|
+
def uncork_socket(socket)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
if closed_socket_supported?
|
169
|
+
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
134
170
|
|
135
171
|
def closed_socket?(socket)
|
136
|
-
|
137
|
-
return false unless @precheck_closing
|
172
|
+
skt = socket.to_io
|
173
|
+
return false unless skt.kind_of?(TCPSocket) && @precheck_closing
|
138
174
|
|
139
175
|
begin
|
140
|
-
tcp_info =
|
176
|
+
tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
141
177
|
rescue IOError, SystemCallError
|
142
178
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
143
179
|
@precheck_closing = false
|
@@ -149,21 +185,17 @@ module Puma
|
|
149
185
|
end
|
150
186
|
end
|
151
187
|
else
|
152
|
-
def cork_socket(socket)
|
153
|
-
end
|
154
|
-
|
155
|
-
def uncork_socket(socket)
|
156
|
-
end
|
157
|
-
|
158
188
|
def closed_socket?(socket)
|
159
189
|
false
|
160
190
|
end
|
161
191
|
end
|
162
192
|
|
193
|
+
# @!attribute [r] backlog
|
163
194
|
def backlog
|
164
195
|
@thread_pool and @thread_pool.backlog
|
165
196
|
end
|
166
197
|
|
198
|
+
# @!attribute [r] running
|
167
199
|
def running
|
168
200
|
@thread_pool and @thread_pool.spawned
|
169
201
|
end
|
@@ -176,172 +208,37 @@ module Puma
|
|
176
208
|
# there are 5 threads sitting idle ready to take
|
177
209
|
# a request. If one request comes in, then the
|
178
210
|
# value would be 4 until it finishes processing.
|
211
|
+
# @!attribute [r] pool_capacity
|
179
212
|
def pool_capacity
|
180
213
|
@thread_pool and @thread_pool.pool_capacity
|
181
214
|
end
|
182
215
|
|
183
|
-
# Lopez Mode == raw tcp apps
|
184
|
-
|
185
|
-
def run_lopez_mode(background=true)
|
186
|
-
@thread_pool = ThreadPool.new(@min_threads,
|
187
|
-
@max_threads,
|
188
|
-
Hash) do |client, tl|
|
189
|
-
|
190
|
-
io = client.to_io
|
191
|
-
addr = io.peeraddr.last
|
192
|
-
|
193
|
-
if addr.empty?
|
194
|
-
# Set unix socket addrs to localhost
|
195
|
-
addr = "127.0.0.1:0"
|
196
|
-
else
|
197
|
-
addr = "#{addr}:#{io.peeraddr[1]}"
|
198
|
-
end
|
199
|
-
|
200
|
-
env = { 'thread' => tl, REMOTE_ADDR => addr }
|
201
|
-
|
202
|
-
begin
|
203
|
-
@app.call env, client.to_io
|
204
|
-
rescue Object => e
|
205
|
-
STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
|
206
|
-
STDERR.puts e.backtrace
|
207
|
-
end
|
208
|
-
|
209
|
-
client.close unless env['detach']
|
210
|
-
end
|
211
|
-
|
212
|
-
@events.fire :state, :running
|
213
|
-
|
214
|
-
if background
|
215
|
-
@thread = Thread.new { handle_servers_lopez_mode }
|
216
|
-
return @thread
|
217
|
-
else
|
218
|
-
handle_servers_lopez_mode
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
def handle_servers_lopez_mode
|
223
|
-
begin
|
224
|
-
check = @check
|
225
|
-
sockets = [check] + @binder.ios
|
226
|
-
pool = @thread_pool
|
227
|
-
|
228
|
-
while @status == :run
|
229
|
-
begin
|
230
|
-
ios = IO.select sockets
|
231
|
-
ios.first.each do |sock|
|
232
|
-
if sock == check
|
233
|
-
break if handle_check
|
234
|
-
else
|
235
|
-
begin
|
236
|
-
if io = sock.accept_nonblock
|
237
|
-
client = Client.new io, nil
|
238
|
-
pool << client
|
239
|
-
end
|
240
|
-
rescue SystemCallError
|
241
|
-
# nothing
|
242
|
-
rescue Errno::ECONNABORTED
|
243
|
-
# client closed the socket even before accept
|
244
|
-
begin
|
245
|
-
io.close
|
246
|
-
rescue
|
247
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
rescue Object => e
|
253
|
-
@events.unknown_error self, e, "Listen loop"
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
@events.fire :state, @status
|
258
|
-
|
259
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
260
|
-
|
261
|
-
rescue Exception => e
|
262
|
-
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
263
|
-
STDERR.puts e.backtrace
|
264
|
-
ensure
|
265
|
-
begin
|
266
|
-
@check.close
|
267
|
-
rescue
|
268
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
269
|
-
end
|
270
|
-
|
271
|
-
@notify.close
|
272
|
-
|
273
|
-
if @status != :restart and @own_binder
|
274
|
-
@binder.close
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
@events.fire :state, :done
|
279
|
-
end
|
280
216
|
# Runs the server.
|
281
217
|
#
|
282
218
|
# If +background+ is true (the default) then a thread is spun
|
283
219
|
# up in the background to handle requests. Otherwise requests
|
284
220
|
# are handled synchronously.
|
285
221
|
#
|
286
|
-
def run(background=true)
|
222
|
+
def run(background=true, thread_name: 'server')
|
287
223
|
BasicSocket.do_not_reverse_lookup = true
|
288
224
|
|
289
225
|
@events.fire :state, :booting
|
290
226
|
|
291
227
|
@status = :run
|
292
228
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
@thread_pool = ThreadPool.new(@min_threads,
|
300
|
-
@max_threads,
|
301
|
-
IOBuffer) do |client, buffer|
|
302
|
-
|
303
|
-
# Advertise this server into the thread
|
304
|
-
Thread.current[ThreadLocalKey] = self
|
305
|
-
|
306
|
-
process_now = false
|
307
|
-
|
308
|
-
begin
|
309
|
-
if queue_requests
|
310
|
-
process_now = client.eagerly_finish
|
311
|
-
else
|
312
|
-
client.finish
|
313
|
-
process_now = true
|
314
|
-
end
|
315
|
-
rescue MiniSSL::SSLError => e
|
316
|
-
ssl_socket = client.io
|
317
|
-
addr = ssl_socket.peeraddr.last
|
318
|
-
cert = ssl_socket.peercert
|
319
|
-
|
320
|
-
client.close
|
321
|
-
|
322
|
-
@events.ssl_error self, addr, cert, e
|
323
|
-
rescue HttpParserError => e
|
324
|
-
client.write_400
|
325
|
-
client.close
|
326
|
-
|
327
|
-
@events.parse_error self, client.env, e
|
328
|
-
rescue ConnectionError, EOFError
|
329
|
-
client.close
|
330
|
-
else
|
331
|
-
if process_now
|
332
|
-
process_client client, buffer
|
333
|
-
else
|
334
|
-
client.set_timeout @first_data_timeout
|
335
|
-
@reactor.add client
|
336
|
-
end
|
337
|
-
end
|
338
|
-
end
|
229
|
+
@thread_pool = ThreadPool.new(
|
230
|
+
@min_threads,
|
231
|
+
@max_threads,
|
232
|
+
::Puma::IOBuffer,
|
233
|
+
&method(:process_client)
|
234
|
+
)
|
339
235
|
|
236
|
+
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
340
237
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
341
238
|
|
342
|
-
if queue_requests
|
343
|
-
@reactor = Reactor.new
|
344
|
-
@reactor.
|
239
|
+
if @queue_requests
|
240
|
+
@reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
|
241
|
+
@reactor.run
|
345
242
|
end
|
346
243
|
|
347
244
|
if @reaping_time
|
@@ -352,22 +249,69 @@ module Puma
|
|
352
249
|
@thread_pool.auto_trim!(@auto_trim_time)
|
353
250
|
end
|
354
251
|
|
252
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
253
|
+
|
355
254
|
@events.fire :state, :running
|
356
255
|
|
357
256
|
if background
|
358
|
-
@thread = Thread.new
|
257
|
+
@thread = Thread.new do
|
258
|
+
Puma.set_thread_name thread_name
|
259
|
+
handle_servers
|
260
|
+
end
|
359
261
|
return @thread
|
360
262
|
else
|
361
263
|
handle_servers
|
362
264
|
end
|
363
265
|
end
|
364
266
|
|
267
|
+
# This method is called from the Reactor thread when a queued Client receives data,
|
268
|
+
# times out, or when the Reactor is shutting down.
|
269
|
+
#
|
270
|
+
# It is responsible for ensuring that a request has been completely received
|
271
|
+
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
272
|
+
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
273
|
+
# such as nginx) then the application would be subject to a slow client attack.
|
274
|
+
#
|
275
|
+
# 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).
|
276
|
+
#
|
277
|
+
# The method checks to see if it has the full header and body with
|
278
|
+
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
279
|
+
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
280
|
+
# so that a "worker thread" can pick up the request and begin to execute application logic.
|
281
|
+
# The Client is then removed from the reactor (return `true`).
|
282
|
+
#
|
283
|
+
# If a client object times out, a 408 response is written, its connection is closed,
|
284
|
+
# and the object is removed from the reactor (return `true`).
|
285
|
+
#
|
286
|
+
# If the Reactor is shutting down, all Clients are either timed out or passed to the
|
287
|
+
# ThreadPool, depending on their current state (#can_close?).
|
288
|
+
#
|
289
|
+
# Otherwise, if the full request is not ready then the client will remain in the reactor
|
290
|
+
# (return `false`). When the client sends more data to the socket the `Puma::Client` object
|
291
|
+
# will wake up and again be checked to see if it's ready to be passed to the thread pool.
|
292
|
+
def reactor_wakeup(client)
|
293
|
+
shutdown = !@queue_requests
|
294
|
+
if client.try_to_finish || (shutdown && !client.can_close?)
|
295
|
+
@thread_pool << client
|
296
|
+
elsif shutdown || client.timeout == 0
|
297
|
+
client.timeout!
|
298
|
+
else
|
299
|
+
client.set_timeout(@first_data_timeout)
|
300
|
+
false
|
301
|
+
end
|
302
|
+
rescue StandardError => e
|
303
|
+
client_error(e, client)
|
304
|
+
client.close
|
305
|
+
true
|
306
|
+
end
|
307
|
+
|
365
308
|
def handle_servers
|
366
309
|
begin
|
367
310
|
check = @check
|
368
311
|
sockets = [check] + @binder.ios
|
369
312
|
pool = @thread_pool
|
370
313
|
queue_requests = @queue_requests
|
314
|
+
drain = @options[:drain_on_shutdown] ? 0 : nil
|
371
315
|
|
372
316
|
remote_addr_value = nil
|
373
317
|
remote_addr_header = nil
|
@@ -379,59 +323,57 @@ module Puma
|
|
379
323
|
remote_addr_header = @options[:remote_address_header]
|
380
324
|
end
|
381
325
|
|
382
|
-
while @status == :run
|
326
|
+
while @status == :run || (drain && shutting_down?)
|
383
327
|
begin
|
384
|
-
ios = IO.select sockets
|
328
|
+
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
|
329
|
+
break unless ios
|
385
330
|
ios.first.each do |sock|
|
386
331
|
if sock == check
|
387
332
|
break if handle_check
|
388
333
|
else
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
end
|
397
|
-
|
398
|
-
pool << client
|
399
|
-
pool.wait_until_not_full
|
400
|
-
end
|
401
|
-
rescue SystemCallError
|
402
|
-
# nothing
|
403
|
-
rescue Errno::ECONNABORTED
|
404
|
-
# client closed the socket even before accept
|
405
|
-
begin
|
406
|
-
io.close
|
407
|
-
rescue
|
408
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
409
|
-
end
|
334
|
+
pool.wait_until_not_full
|
335
|
+
pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
|
336
|
+
|
337
|
+
io = begin
|
338
|
+
sock.accept_nonblock
|
339
|
+
rescue IO::WaitReadable
|
340
|
+
next
|
410
341
|
end
|
342
|
+
drain += 1 if shutting_down?
|
343
|
+
client = Client.new io, @binder.env(sock)
|
344
|
+
if remote_addr_value
|
345
|
+
client.peerip = remote_addr_value
|
346
|
+
elsif remote_addr_header
|
347
|
+
client.remote_addr_header = remote_addr_header
|
348
|
+
end
|
349
|
+
pool << client
|
411
350
|
end
|
412
351
|
end
|
413
352
|
rescue Object => e
|
414
|
-
@events.unknown_error
|
353
|
+
@events.unknown_error e, nil, "Listen loop"
|
415
354
|
end
|
416
355
|
end
|
417
356
|
|
357
|
+
@events.debug "Drained #{drain} additional connections." if drain
|
418
358
|
@events.fire :state, @status
|
419
359
|
|
420
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
421
360
|
if queue_requests
|
422
|
-
@
|
361
|
+
@queue_requests = false
|
423
362
|
@reactor.shutdown
|
424
363
|
end
|
364
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
425
365
|
rescue Exception => e
|
426
|
-
|
427
|
-
STDERR.puts e.backtrace
|
366
|
+
@events.unknown_error e, nil, "Exception handling servers"
|
428
367
|
ensure
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
368
|
+
begin
|
369
|
+
@check.close unless @check.closed?
|
370
|
+
rescue Errno::EBADF, RuntimeError
|
371
|
+
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
372
|
+
# Errno::EBADF is infrequently raised
|
434
373
|
end
|
374
|
+
@notify.close
|
375
|
+
@notify = nil
|
376
|
+
@check = nil
|
435
377
|
end
|
436
378
|
|
437
379
|
@events.fire :state, :done
|
@@ -456,72 +398,89 @@ module Puma
|
|
456
398
|
return false
|
457
399
|
end
|
458
400
|
|
459
|
-
# Given a connection on +client+, handle the incoming requests
|
401
|
+
# Given a connection on +client+, handle the incoming requests,
|
402
|
+
# or queue the connection in the Reactor if no request is available.
|
403
|
+
#
|
404
|
+
# This method is called from a ThreadPool worker thread.
|
460
405
|
#
|
461
|
-
# This method
|
406
|
+
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
462
407
|
# indicates that it supports keep alive, wait for another request before
|
463
408
|
# returning.
|
464
409
|
#
|
410
|
+
# Return true if one or more requests were processed.
|
465
411
|
def process_client(client, buffer)
|
412
|
+
# Advertise this server into the thread
|
413
|
+
Thread.current[ThreadLocalKey] = self
|
414
|
+
|
415
|
+
clean_thread_locals = @options[:clean_thread_locals]
|
416
|
+
close_socket = true
|
417
|
+
|
418
|
+
requests = 0
|
419
|
+
|
466
420
|
begin
|
421
|
+
if @queue_requests &&
|
422
|
+
!client.eagerly_finish
|
467
423
|
|
468
|
-
|
469
|
-
|
424
|
+
client.set_timeout(@first_data_timeout)
|
425
|
+
if @reactor.add client
|
426
|
+
close_socket = false
|
427
|
+
return false
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
with_force_shutdown(client) do
|
432
|
+
client.finish(@first_data_timeout)
|
433
|
+
end
|
470
434
|
|
471
435
|
while true
|
436
|
+
@requests_count += 1
|
472
437
|
case handle_request(client, buffer)
|
473
438
|
when false
|
474
|
-
|
439
|
+
break
|
475
440
|
when :async
|
476
441
|
close_socket = false
|
477
|
-
|
442
|
+
break
|
478
443
|
when true
|
479
|
-
return unless @queue_requests
|
480
444
|
buffer.reset
|
481
445
|
|
482
446
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
483
447
|
|
484
|
-
|
485
|
-
|
448
|
+
requests += 1
|
449
|
+
|
450
|
+
# Closing keepalive sockets after they've made a reasonable
|
451
|
+
# number of requests allows Puma to service many connections
|
452
|
+
# fairly, even when the number of concurrent connections exceeds
|
453
|
+
# the size of the threadpool. It also allows cluster mode Pumas
|
454
|
+
# to keep load evenly distributed across workers, because clients
|
455
|
+
# are randomly assigned a new worker when opening a new connection.
|
456
|
+
#
|
457
|
+
# Previously, Puma would kick connections in this conditional back
|
458
|
+
# to the reactor. However, because this causes the todo set to increase
|
459
|
+
# in size, the wait_until_full mutex would never unlock, leaving
|
460
|
+
# any additional connections unserviced.
|
461
|
+
break if requests >= @max_fast_inline
|
462
|
+
|
463
|
+
check_for_more_data = @status == :run
|
464
|
+
|
465
|
+
next_request_ready = with_force_shutdown(client) do
|
466
|
+
client.reset(check_for_more_data)
|
467
|
+
end
|
468
|
+
|
469
|
+
unless next_request_ready
|
470
|
+
break unless @queue_requests
|
486
471
|
client.set_timeout @persistent_timeout
|
487
|
-
@reactor.add client
|
488
|
-
|
472
|
+
if @reactor.add client
|
473
|
+
close_socket = false
|
474
|
+
break
|
475
|
+
end
|
489
476
|
end
|
490
477
|
end
|
491
478
|
end
|
492
|
-
|
493
|
-
# The client disconnected while we were reading data
|
494
|
-
rescue ConnectionError
|
495
|
-
# Swallow them. The ensure tries to close +client+ down
|
496
|
-
|
497
|
-
# SSL handshake error
|
498
|
-
rescue MiniSSL::SSLError => e
|
499
|
-
lowlevel_error(e, client.env)
|
500
|
-
|
501
|
-
ssl_socket = client.io
|
502
|
-
addr = ssl_socket.peeraddr.last
|
503
|
-
cert = ssl_socket.peercert
|
504
|
-
|
505
|
-
close_socket = true
|
506
|
-
|
507
|
-
@events.ssl_error self, addr, cert, e
|
508
|
-
|
509
|
-
# The client doesn't know HTTP well
|
510
|
-
rescue HttpParserError => e
|
511
|
-
lowlevel_error(e, client.env)
|
512
|
-
|
513
|
-
client.write_400
|
514
|
-
|
515
|
-
@events.parse_error self, client.env, e
|
516
|
-
|
517
|
-
# Server error
|
479
|
+
true
|
518
480
|
rescue StandardError => e
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
@events.unknown_error self, e, "Read"
|
524
|
-
|
481
|
+
client_error(e, client)
|
482
|
+
# The ensure tries to close +client+ down
|
483
|
+
requests > 0
|
525
484
|
ensure
|
526
485
|
buffer.reset
|
527
486
|
|
@@ -531,372 +490,57 @@ module Puma
|
|
531
490
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
532
491
|
# Already closed
|
533
492
|
rescue StandardError => e
|
534
|
-
@events.unknown_error
|
535
|
-
end
|
536
|
-
end
|
537
|
-
end
|
538
|
-
|
539
|
-
# Given a Hash +env+ for the request read from +client+, add
|
540
|
-
# and fixup keys to comply with Rack's env guidelines.
|
541
|
-
#
|
542
|
-
def normalize_env(env, client)
|
543
|
-
if host = env[HTTP_HOST]
|
544
|
-
if colon = host.index(":")
|
545
|
-
env[SERVER_NAME] = host[0, colon]
|
546
|
-
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
547
|
-
else
|
548
|
-
env[SERVER_NAME] = host
|
549
|
-
env[SERVER_PORT] = default_server_port(env)
|
550
|
-
end
|
551
|
-
else
|
552
|
-
env[SERVER_NAME] = LOCALHOST
|
553
|
-
env[SERVER_PORT] = default_server_port(env)
|
554
|
-
end
|
555
|
-
|
556
|
-
unless env[REQUEST_PATH]
|
557
|
-
# it might be a dumbass full host request header
|
558
|
-
uri = URI.parse(env[REQUEST_URI])
|
559
|
-
env[REQUEST_PATH] = uri.path
|
560
|
-
|
561
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
562
|
-
|
563
|
-
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
564
|
-
# so only set the env value if there actually is a value.
|
565
|
-
env[QUERY_STRING] = uri.query if uri.query
|
566
|
-
end
|
567
|
-
|
568
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
569
|
-
|
570
|
-
# From http://www.ietf.org/rfc/rfc3875 :
|
571
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
572
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
573
|
-
# may not identify the ultimate source of the request.
|
574
|
-
# They identify the client for the immediate request to the
|
575
|
-
# server; that client may be a proxy, gateway, or other
|
576
|
-
# intermediary acting on behalf of the actual source client."
|
577
|
-
#
|
578
|
-
|
579
|
-
unless env.key?(REMOTE_ADDR)
|
580
|
-
begin
|
581
|
-
addr = client.peerip
|
582
|
-
rescue Errno::ENOTCONN
|
583
|
-
# Client disconnects can result in an inability to get the
|
584
|
-
# peeraddr from the socket; default to localhost.
|
585
|
-
addr = LOCALHOST_IP
|
493
|
+
@events.unknown_error e, nil, "Client"
|
586
494
|
end
|
587
|
-
|
588
|
-
# Set unix socket addrs to localhost
|
589
|
-
addr = LOCALHOST_IP if addr.empty?
|
590
|
-
|
591
|
-
env[REMOTE_ADDR] = addr
|
592
495
|
end
|
593
496
|
end
|
594
497
|
|
595
|
-
|
596
|
-
|
597
|
-
|
498
|
+
# Triggers a client timeout if the thread-pool shuts down
|
499
|
+
# during execution of the provided block.
|
500
|
+
def with_force_shutdown(client, &block)
|
501
|
+
@thread_pool.with_force_shutdown(&block)
|
502
|
+
rescue ThreadPool::ForceShutdown
|
503
|
+
client.timeout!
|
598
504
|
end
|
599
505
|
|
600
|
-
#
|
601
|
-
# in +body+, finish reading the body if there is one and invoke
|
602
|
-
# the rack app. Then construct the response and write it back to
|
603
|
-
# +client+
|
604
|
-
#
|
605
|
-
# +cl+ is the previously fetched Content-Length header if there
|
606
|
-
# was one. This is an optimization to keep from having to look
|
607
|
-
# it up again.
|
608
|
-
#
|
609
|
-
def handle_request(req, lines)
|
610
|
-
env = req.env
|
611
|
-
client = req.io
|
612
|
-
|
613
|
-
return false if closed_socket?(client)
|
506
|
+
# :nocov:
|
614
507
|
|
615
|
-
|
508
|
+
# Handle various error types thrown by Client I/O operations.
|
509
|
+
def client_error(e, client)
|
510
|
+
# Swallow, do not log
|
511
|
+
return if [ConnectionError, EOFError].include?(e.class)
|
616
512
|
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
env[HIJACK] = req
|
625
|
-
|
626
|
-
body = req.body
|
627
|
-
|
628
|
-
head = env[REQUEST_METHOD] == HEAD
|
629
|
-
|
630
|
-
env[RACK_INPUT] = body
|
631
|
-
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
632
|
-
|
633
|
-
if @early_hints
|
634
|
-
env[EARLY_HINTS] = lambda { |headers|
|
635
|
-
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
636
|
-
|
637
|
-
headers.each_pair do |k, vs|
|
638
|
-
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
639
|
-
vs.to_s.split(NEWLINE).each do |v|
|
640
|
-
fast_write client, "#{k}: #{v}\r\n"
|
641
|
-
end
|
642
|
-
else
|
643
|
-
fast_write client, "#{k}: #{vs}\r\n"
|
644
|
-
end
|
645
|
-
end
|
646
|
-
|
647
|
-
fast_write client, "\r\n".freeze
|
648
|
-
}
|
649
|
-
end
|
650
|
-
|
651
|
-
# A rack extension. If the app writes #call'ables to this
|
652
|
-
# array, we will invoke them when the request is done.
|
653
|
-
#
|
654
|
-
after_reply = env[RACK_AFTER_REPLY] = []
|
655
|
-
|
656
|
-
begin
|
657
|
-
begin
|
658
|
-
status, headers, res_body = @app.call(env)
|
659
|
-
|
660
|
-
return :async if req.hijacked
|
661
|
-
|
662
|
-
status = status.to_i
|
663
|
-
|
664
|
-
if status == -1
|
665
|
-
unless headers.empty? and res_body == []
|
666
|
-
raise "async response must have empty headers and body"
|
667
|
-
end
|
668
|
-
|
669
|
-
return :async
|
670
|
-
end
|
671
|
-
rescue ThreadPool::ForceShutdown => e
|
672
|
-
@events.log "Detected force shutdown of a thread, returning 503"
|
673
|
-
@events.unknown_error self, e, "Rack app"
|
674
|
-
|
675
|
-
status = 503
|
676
|
-
headers = {}
|
677
|
-
res_body = ["Request was internally terminated early\n"]
|
678
|
-
|
679
|
-
rescue Exception => e
|
680
|
-
@events.unknown_error self, e, "Rack app", env
|
681
|
-
|
682
|
-
status, headers, res_body = lowlevel_error(e, env)
|
683
|
-
end
|
684
|
-
|
685
|
-
content_length = nil
|
686
|
-
no_body = head
|
687
|
-
|
688
|
-
if res_body.kind_of? Array and res_body.size == 1
|
689
|
-
content_length = res_body[0].bytesize
|
690
|
-
end
|
691
|
-
|
692
|
-
cork_socket client
|
693
|
-
|
694
|
-
line_ending = LINE_END
|
695
|
-
colon = COLON
|
696
|
-
|
697
|
-
http_11 = if env[HTTP_VERSION] == HTTP_11
|
698
|
-
allow_chunked = true
|
699
|
-
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
700
|
-
include_keepalive_header = false
|
701
|
-
|
702
|
-
# An optimization. The most common response is 200, so we can
|
703
|
-
# reply with the proper 200 status without having to compute
|
704
|
-
# the response header.
|
705
|
-
#
|
706
|
-
if status == 200
|
707
|
-
lines << HTTP_11_200
|
708
|
-
else
|
709
|
-
lines.append "HTTP/1.1 ", status.to_s, " ",
|
710
|
-
fetch_status_code(status), line_ending
|
711
|
-
|
712
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
713
|
-
end
|
714
|
-
true
|
715
|
-
else
|
716
|
-
allow_chunked = false
|
717
|
-
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
718
|
-
include_keepalive_header = keep_alive
|
719
|
-
|
720
|
-
# Same optimization as above for HTTP/1.1
|
721
|
-
#
|
722
|
-
if status == 200
|
723
|
-
lines << HTTP_10_200
|
724
|
-
else
|
725
|
-
lines.append "HTTP/1.0 ", status.to_s, " ",
|
726
|
-
fetch_status_code(status), line_ending
|
727
|
-
|
728
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
729
|
-
end
|
730
|
-
false
|
731
|
-
end
|
732
|
-
|
733
|
-
response_hijack = nil
|
734
|
-
|
735
|
-
headers.each do |k, vs|
|
736
|
-
case k.downcase
|
737
|
-
when CONTENT_LENGTH2
|
738
|
-
content_length = vs
|
739
|
-
next
|
740
|
-
when TRANSFER_ENCODING
|
741
|
-
allow_chunked = false
|
742
|
-
content_length = nil
|
743
|
-
when HIJACK
|
744
|
-
response_hijack = vs
|
745
|
-
next
|
746
|
-
end
|
747
|
-
|
748
|
-
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
749
|
-
vs.to_s.split(NEWLINE).each do |v|
|
750
|
-
lines.append k, colon, v, line_ending
|
751
|
-
end
|
752
|
-
else
|
753
|
-
lines.append k, colon, line_ending
|
754
|
-
end
|
755
|
-
end
|
756
|
-
|
757
|
-
if include_keepalive_header
|
758
|
-
lines << CONNECTION_KEEP_ALIVE
|
759
|
-
elsif http_11 && !keep_alive
|
760
|
-
lines << CONNECTION_CLOSE
|
761
|
-
end
|
762
|
-
|
763
|
-
if no_body
|
764
|
-
if content_length and status != 204
|
765
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
766
|
-
end
|
767
|
-
|
768
|
-
lines << line_ending
|
769
|
-
fast_write client, lines.to_s
|
770
|
-
return keep_alive
|
771
|
-
end
|
772
|
-
|
773
|
-
if content_length
|
774
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
775
|
-
chunked = false
|
776
|
-
elsif !response_hijack and allow_chunked
|
777
|
-
lines << TRANSFER_ENCODING_CHUNKED
|
778
|
-
chunked = true
|
779
|
-
end
|
780
|
-
|
781
|
-
lines << line_ending
|
782
|
-
|
783
|
-
fast_write client, lines.to_s
|
784
|
-
|
785
|
-
if response_hijack
|
786
|
-
response_hijack.call client
|
787
|
-
return :async
|
788
|
-
end
|
789
|
-
|
790
|
-
begin
|
791
|
-
res_body.each do |part|
|
792
|
-
next if part.bytesize.zero?
|
793
|
-
if chunked
|
794
|
-
fast_write client, part.bytesize.to_s(16)
|
795
|
-
fast_write client, line_ending
|
796
|
-
fast_write client, part
|
797
|
-
fast_write client, line_ending
|
798
|
-
else
|
799
|
-
fast_write client, part
|
800
|
-
end
|
801
|
-
|
802
|
-
client.flush
|
803
|
-
end
|
804
|
-
|
805
|
-
if chunked
|
806
|
-
fast_write client, CLOSE_CHUNKED
|
807
|
-
client.flush
|
808
|
-
end
|
809
|
-
rescue SystemCallError, IOError
|
810
|
-
raise ConnectionError, "Connection error detected during write"
|
811
|
-
end
|
812
|
-
|
813
|
-
ensure
|
814
|
-
uncork_socket client
|
815
|
-
|
816
|
-
body.close
|
817
|
-
req.tempfile.unlink if req.tempfile
|
818
|
-
res_body.close if res_body.respond_to? :close
|
819
|
-
|
820
|
-
after_reply.each { |o| o.call }
|
821
|
-
end
|
822
|
-
|
823
|
-
return keep_alive
|
824
|
-
end
|
825
|
-
|
826
|
-
def fetch_status_code(status)
|
827
|
-
HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
|
828
|
-
end
|
829
|
-
private :fetch_status_code
|
830
|
-
|
831
|
-
# Given the request +env+ from +client+ and the partial body +body+
|
832
|
-
# plus a potential Content-Length value +cl+, finish reading
|
833
|
-
# the body and return it.
|
834
|
-
#
|
835
|
-
# If the body is larger than MAX_BODY, a Tempfile object is used
|
836
|
-
# for the body, otherwise a StringIO is used.
|
837
|
-
#
|
838
|
-
def read_body(env, client, body, cl)
|
839
|
-
content_length = cl.to_i
|
840
|
-
|
841
|
-
remain = content_length - body.bytesize
|
842
|
-
|
843
|
-
return StringIO.new(body) if remain <= 0
|
844
|
-
|
845
|
-
# Use a Tempfile if there is a lot of data left
|
846
|
-
if remain > MAX_BODY
|
847
|
-
stream = Tempfile.new(Const::PUMA_TMP_BASE)
|
848
|
-
stream.binmode
|
513
|
+
lowlevel_error(e, client.env)
|
514
|
+
case e
|
515
|
+
when MiniSSL::SSLError
|
516
|
+
@events.ssl_error e, client.io
|
517
|
+
when HttpParserError
|
518
|
+
client.write_error(400)
|
519
|
+
@events.parse_error e, client
|
849
520
|
else
|
850
|
-
|
851
|
-
|
852
|
-
stream = StringIO.new body[0,0]
|
853
|
-
end
|
854
|
-
|
855
|
-
stream.write body
|
856
|
-
|
857
|
-
# Read an odd sized chunk so we can read even sized ones
|
858
|
-
# after this
|
859
|
-
chunk = client.readpartial(remain % CHUNK_SIZE)
|
860
|
-
|
861
|
-
# No chunk means a closed socket
|
862
|
-
unless chunk
|
863
|
-
stream.close
|
864
|
-
return nil
|
521
|
+
client.write_error(500)
|
522
|
+
@events.unknown_error e, nil, "Read"
|
865
523
|
end
|
866
|
-
|
867
|
-
remain -= stream.write(chunk)
|
868
|
-
|
869
|
-
# Raed the rest of the chunks
|
870
|
-
while remain > 0
|
871
|
-
chunk = client.readpartial(CHUNK_SIZE)
|
872
|
-
unless chunk
|
873
|
-
stream.close
|
874
|
-
return nil
|
875
|
-
end
|
876
|
-
|
877
|
-
remain -= stream.write(chunk)
|
878
|
-
end
|
879
|
-
|
880
|
-
stream.rewind
|
881
|
-
|
882
|
-
return stream
|
883
524
|
end
|
884
525
|
|
885
526
|
# A fallback rack response if +@app+ raises as exception.
|
886
527
|
#
|
887
|
-
def lowlevel_error(e, env)
|
528
|
+
def lowlevel_error(e, env, status=500)
|
888
529
|
if handler = @options[:lowlevel_error_handler]
|
889
530
|
if handler.arity == 1
|
890
531
|
return handler.call(e)
|
891
|
-
|
532
|
+
elsif handler.arity == 2
|
892
533
|
return handler.call(e, env)
|
534
|
+
else
|
535
|
+
return handler.call(e, env, status)
|
893
536
|
end
|
894
537
|
end
|
895
538
|
|
896
539
|
if @leak_stack_on_error
|
897
|
-
|
540
|
+
backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
|
541
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
|
898
542
|
else
|
899
|
-
[
|
543
|
+
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
900
544
|
end
|
901
545
|
end
|
902
546
|
|
@@ -918,31 +562,13 @@ module Puma
|
|
918
562
|
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
919
563
|
end
|
920
564
|
|
921
|
-
if @
|
922
|
-
|
923
|
-
|
924
|
-
while true
|
925
|
-
ios = IO.select @binder.ios, nil, nil, 0
|
926
|
-
break unless ios
|
927
|
-
|
928
|
-
ios.first.each do |sock|
|
929
|
-
begin
|
930
|
-
if io = sock.accept_nonblock
|
931
|
-
count += 1
|
932
|
-
client = Client.new io, @binder.env(sock)
|
933
|
-
@thread_pool << client
|
934
|
-
end
|
935
|
-
rescue SystemCallError
|
936
|
-
end
|
937
|
-
end
|
938
|
-
end
|
939
|
-
|
940
|
-
@events.debug "Drained #{count} additional connections."
|
565
|
+
if @status != :restart
|
566
|
+
@binder.close
|
941
567
|
end
|
942
568
|
|
943
569
|
if @thread_pool
|
944
570
|
if timeout = @options[:force_shutdown_after]
|
945
|
-
@thread_pool.shutdown timeout.
|
571
|
+
@thread_pool.shutdown timeout.to_f
|
946
572
|
else
|
947
573
|
@thread_pool.shutdown
|
948
574
|
end
|
@@ -950,18 +576,16 @@ module Puma
|
|
950
576
|
end
|
951
577
|
|
952
578
|
def notify_safely(message)
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
579
|
+
@notify << message
|
580
|
+
rescue IOError, NoMethodError, Errno::EPIPE
|
581
|
+
# The server, in another thread, is shutting down
|
582
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
583
|
+
rescue RuntimeError => e
|
584
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
585
|
+
if e.message.include?('IOError')
|
957
586
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
958
|
-
|
959
|
-
|
960
|
-
if e.message.include?('IOError')
|
961
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
962
|
-
else
|
963
|
-
raise e
|
964
|
-
end
|
587
|
+
else
|
588
|
+
raise e
|
965
589
|
end
|
966
590
|
end
|
967
591
|
private :notify_safely
|
@@ -979,39 +603,24 @@ module Puma
|
|
979
603
|
@thread.join if @thread && sync
|
980
604
|
end
|
981
605
|
|
982
|
-
def begin_restart
|
606
|
+
def begin_restart(sync=false)
|
983
607
|
notify_safely(RESTART_COMMAND)
|
608
|
+
@thread.join if @thread && sync
|
984
609
|
end
|
985
610
|
|
986
|
-
def
|
987
|
-
|
988
|
-
while true
|
989
|
-
begin
|
990
|
-
n = io.syswrite str
|
991
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
992
|
-
if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
|
993
|
-
raise ConnectionError, "Socket timeout writing data"
|
994
|
-
end
|
995
|
-
|
996
|
-
retry
|
997
|
-
rescue Errno::EPIPE, SystemCallError, IOError
|
998
|
-
raise ConnectionError, "Socket timeout writing data"
|
999
|
-
end
|
1000
|
-
|
1001
|
-
return if n == str.bytesize
|
1002
|
-
str = str.byteslice(n..-1)
|
1003
|
-
end
|
611
|
+
def shutting_down?
|
612
|
+
@status == :stop || @status == :restart
|
1004
613
|
end
|
1005
|
-
private :fast_write
|
1006
614
|
|
1007
|
-
|
615
|
+
# List of methods invoked by #stats.
|
616
|
+
# @version 5.0.0
|
617
|
+
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
1008
618
|
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
@status == :stop || @status == :restart
|
619
|
+
# Returns a hash of stats about the running server for reporting purposes.
|
620
|
+
# @version 5.0.0
|
621
|
+
# @!attribute [r] stats
|
622
|
+
def stats
|
623
|
+
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
1015
624
|
end
|
1016
625
|
end
|
1017
626
|
end
|