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