puma 4.3.5-java → 5.0.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +1149 -518
- data/LICENSE +23 -20
- data/README.md +26 -13
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +4 -3
- data/ext/puma_http11/http11_parser.c +64 -64
- data/ext/puma_http11/mini_ssl.c +15 -2
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +7 -38
- data/lib/puma.rb +20 -0
- data/lib/puma/app/status.rb +18 -3
- data/lib/puma/binder.rb +90 -68
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +62 -13
- data/lib/puma/cluster.rb +193 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +3 -3
- data/lib/puma/control_cli.rb +29 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +144 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +37 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +57 -31
- data/lib/puma/minissl.rb +68 -18
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +10 -16
- data/lib/puma/runner.rb +13 -54
- data/lib/puma/server.rb +193 -241
- data/lib/puma/single.rb +8 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +116 -51
- data/lib/puma/util.rb +1 -0
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +17 -19
- 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/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/lib/puma/minissl.rb
CHANGED
|
@@ -5,8 +5,18 @@ begin
|
|
|
5
5
|
rescue LoadError
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
# need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
|
|
9
|
+
require 'puma/puma_http11'
|
|
10
|
+
|
|
8
11
|
module Puma
|
|
9
12
|
module MiniSSL
|
|
13
|
+
# Define constant at runtime, as it's easy to determine at built time,
|
|
14
|
+
# but Puma could (it shouldn't) be loaded with an older OpenSSL version
|
|
15
|
+
# @version 5.0.0
|
|
16
|
+
HAS_TLS1_3 = !IS_JRUBY &&
|
|
17
|
+
(OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
|
|
18
|
+
(OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
|
|
19
|
+
|
|
10
20
|
class Socket
|
|
11
21
|
def initialize(socket, engine)
|
|
12
22
|
@socket = socket
|
|
@@ -14,6 +24,7 @@ module Puma
|
|
|
14
24
|
@peercert = nil
|
|
15
25
|
end
|
|
16
26
|
|
|
27
|
+
# @!attribute [r] to_io
|
|
17
28
|
def to_io
|
|
18
29
|
@socket
|
|
19
30
|
end
|
|
@@ -22,6 +33,27 @@ module Puma
|
|
|
22
33
|
@socket.closed?
|
|
23
34
|
end
|
|
24
35
|
|
|
36
|
+
# Returns a two element array,
|
|
37
|
+
# first is protocol version (SSL_get_version),
|
|
38
|
+
# second is 'handshake' state (SSL_state_string)
|
|
39
|
+
#
|
|
40
|
+
# Used for dropping tcp connections to ssl.
|
|
41
|
+
# See OpenSSL ssl/ssl_stat.c SSL_state_string for info
|
|
42
|
+
# @!attribute [r] ssl_version_state
|
|
43
|
+
# @version 5.0.0
|
|
44
|
+
#
|
|
45
|
+
def ssl_version_state
|
|
46
|
+
IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Used to check the handshake status, in particular when a TCP connection
|
|
50
|
+
# is made with TLSv1.3 as an available protocol
|
|
51
|
+
# @version 5.0.0
|
|
52
|
+
def bad_tlsv1_3?
|
|
53
|
+
HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
|
|
54
|
+
end
|
|
55
|
+
private :bad_tlsv1_3?
|
|
56
|
+
|
|
25
57
|
def readpartial(size)
|
|
26
58
|
while true
|
|
27
59
|
output = @engine.read
|
|
@@ -41,6 +73,7 @@ module Puma
|
|
|
41
73
|
|
|
42
74
|
def engine_read_all
|
|
43
75
|
output = @engine.read
|
|
76
|
+
raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
|
|
44
77
|
while output and additional_output = @engine.read
|
|
45
78
|
output << additional_output
|
|
46
79
|
end
|
|
@@ -107,14 +140,18 @@ module Puma
|
|
|
107
140
|
alias_method :<<, :write
|
|
108
141
|
|
|
109
142
|
# This is a temporary fix to deal with websockets code using
|
|
110
|
-
# write_nonblock.
|
|
143
|
+
# write_nonblock.
|
|
144
|
+
|
|
145
|
+
# The problem with implementing it properly
|
|
111
146
|
# is that it means we'd have to have the ability to rewind
|
|
112
147
|
# an engine because after we write+extract, the socket
|
|
113
148
|
# write_nonblock call might raise an exception and later
|
|
114
149
|
# code would pass the same data in, but the engine would think
|
|
115
|
-
# it had already written the data in.
|
|
116
|
-
#
|
|
117
|
-
#
|
|
150
|
+
# it had already written the data in.
|
|
151
|
+
#
|
|
152
|
+
# So for the time being (and since write blocking is quite rare),
|
|
153
|
+
# go ahead and actually block in write_nonblock.
|
|
154
|
+
#
|
|
118
155
|
def write_nonblock(data, *_)
|
|
119
156
|
write data
|
|
120
157
|
end
|
|
@@ -125,11 +162,14 @@ module Puma
|
|
|
125
162
|
|
|
126
163
|
def read_and_drop(timeout = 1)
|
|
127
164
|
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
165
|
+
case @socket.read_nonblock(1024, exception: false)
|
|
166
|
+
when nil
|
|
167
|
+
:eof
|
|
168
|
+
when :wait_readable
|
|
169
|
+
:eagain
|
|
170
|
+
else
|
|
171
|
+
:drop
|
|
172
|
+
end
|
|
133
173
|
end
|
|
134
174
|
|
|
135
175
|
def should_drop_bytes?
|
|
@@ -141,9 +181,7 @@ module Puma
|
|
|
141
181
|
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
|
142
182
|
# Don't let this socket hold this loop forever.
|
|
143
183
|
# If it can't send more packets within 1s, then give up.
|
|
144
|
-
while should_drop_bytes?
|
|
145
|
-
return if [:timeout, :eof].include?(read_and_drop(1))
|
|
146
|
-
end
|
|
184
|
+
return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
|
|
147
185
|
rescue IOError, SystemCallError
|
|
148
186
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
149
187
|
# nothing
|
|
@@ -152,10 +190,12 @@ module Puma
|
|
|
152
190
|
end
|
|
153
191
|
end
|
|
154
192
|
|
|
193
|
+
# @!attribute [r] peeraddr
|
|
155
194
|
def peeraddr
|
|
156
195
|
@socket.peeraddr
|
|
157
196
|
end
|
|
158
197
|
|
|
198
|
+
# @!attribute [r] peercert
|
|
159
199
|
def peercert
|
|
160
200
|
return @peercert if @peercert
|
|
161
201
|
|
|
@@ -166,12 +206,13 @@ module Puma
|
|
|
166
206
|
end
|
|
167
207
|
end
|
|
168
208
|
|
|
169
|
-
if
|
|
209
|
+
if IS_JRUBY
|
|
210
|
+
OPENSSL_NO_SSL3 = false
|
|
211
|
+
OPENSSL_NO_TLS1 = false
|
|
212
|
+
|
|
170
213
|
class SSLError < StandardError
|
|
171
214
|
# Define this for jruby even though it isn't used.
|
|
172
215
|
end
|
|
173
|
-
|
|
174
|
-
def self.check; end
|
|
175
216
|
end
|
|
176
217
|
|
|
177
218
|
class Context
|
|
@@ -183,7 +224,7 @@ module Puma
|
|
|
183
224
|
@no_tlsv1_1 = false
|
|
184
225
|
end
|
|
185
226
|
|
|
186
|
-
if
|
|
227
|
+
if IS_JRUBY
|
|
187
228
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
|
188
229
|
attr_reader :keystore
|
|
189
230
|
attr_accessor :keystore_pass
|
|
@@ -227,14 +268,16 @@ module Puma
|
|
|
227
268
|
end
|
|
228
269
|
|
|
229
270
|
# disables TLSv1
|
|
271
|
+
# @!attribute [w] no_tlsv1=
|
|
230
272
|
def no_tlsv1=(tlsv1)
|
|
231
|
-
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
|
273
|
+
raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
|
|
232
274
|
@no_tlsv1 = tlsv1
|
|
233
275
|
end
|
|
234
276
|
|
|
235
277
|
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
|
278
|
+
# @!attribute [w] no_tlsv1_1=
|
|
236
279
|
def no_tlsv1_1=(tlsv1_1)
|
|
237
|
-
raise ArgumentError, "Invalid value of
|
|
280
|
+
raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
|
|
238
281
|
@no_tlsv1_1 = tlsv1_1
|
|
239
282
|
end
|
|
240
283
|
|
|
@@ -250,6 +293,7 @@ module Puma
|
|
|
250
293
|
@ctx = ctx
|
|
251
294
|
end
|
|
252
295
|
|
|
296
|
+
# @!attribute [r] to_io
|
|
253
297
|
def to_io
|
|
254
298
|
@socket
|
|
255
299
|
end
|
|
@@ -270,6 +314,12 @@ module Puma
|
|
|
270
314
|
Socket.new io, engine
|
|
271
315
|
end
|
|
272
316
|
|
|
317
|
+
# @!attribute [r] addr
|
|
318
|
+
# @version 5.0.0
|
|
319
|
+
def addr
|
|
320
|
+
@socket.addr
|
|
321
|
+
end
|
|
322
|
+
|
|
273
323
|
def close
|
|
274
324
|
@socket.close unless @socket.closed? # closed? call is for Windows
|
|
275
325
|
end
|
data/lib/puma/null_io.rb
CHANGED
data/lib/puma/plugin.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Puma
|
|
|
10
10
|
|
|
11
11
|
def create(name)
|
|
12
12
|
if cls = Plugins.find(name)
|
|
13
|
-
plugin = cls.new
|
|
13
|
+
plugin = cls.new
|
|
14
14
|
@instances << plugin
|
|
15
15
|
return plugin
|
|
16
16
|
end
|
|
@@ -104,17 +104,8 @@ module Puma
|
|
|
104
104
|
Plugins.register name, cls
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def initialize(loader)
|
|
108
|
-
@loader = loader
|
|
109
|
-
end
|
|
110
|
-
|
|
111
107
|
def in_background(&blk)
|
|
112
108
|
Plugins.add_background blk
|
|
113
109
|
end
|
|
114
|
-
|
|
115
|
-
def workers_supported?
|
|
116
|
-
return false if Puma.jruby? || Puma.windows?
|
|
117
|
-
true
|
|
118
|
-
end
|
|
119
110
|
end
|
|
120
111
|
end
|
data/lib/puma/puma_http11.jar
CHANGED
|
Binary file
|
data/lib/puma/rack/builder.rb
CHANGED
|
@@ -67,10 +67,6 @@ module Puma::Rack
|
|
|
67
67
|
options[:environment] = e
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
|
71
|
-
options[:daemonize] = d ? true : false
|
|
72
|
-
}
|
|
73
|
-
|
|
74
70
|
opts.on("-P", "--pid FILE", "file to store PID") { |f|
|
|
75
71
|
options[:pid] = ::File.expand_path(f)
|
|
76
72
|
}
|
data/lib/puma/reactor.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'puma/util'
|
|
4
|
-
require 'puma/minissl'
|
|
4
|
+
require 'puma/minissl' if ::Puma::HAS_SSL
|
|
5
5
|
|
|
6
6
|
require 'nio'
|
|
7
7
|
|
|
@@ -189,7 +189,12 @@ module Puma
|
|
|
189
189
|
if submon.value == @ready
|
|
190
190
|
false
|
|
191
191
|
else
|
|
192
|
-
submon.value.
|
|
192
|
+
if submon.value.can_close?
|
|
193
|
+
submon.value.close
|
|
194
|
+
else
|
|
195
|
+
# Pass remaining open client connections to the thread pool.
|
|
196
|
+
@app_pool << submon.value
|
|
197
|
+
end
|
|
193
198
|
begin
|
|
194
199
|
selector.deregister submon.value
|
|
195
200
|
rescue IOError
|
|
@@ -232,23 +237,12 @@ module Puma
|
|
|
232
237
|
|
|
233
238
|
# SSL handshake failure
|
|
234
239
|
rescue MiniSSL::SSLError => e
|
|
235
|
-
@server.lowlevel_error
|
|
236
|
-
|
|
237
|
-
ssl_socket = c.io
|
|
238
|
-
begin
|
|
239
|
-
addr = ssl_socket.peeraddr.last
|
|
240
|
-
# EINVAL can happen when browser closes socket w/security exception
|
|
241
|
-
rescue IOError, Errno::EINVAL
|
|
242
|
-
addr = "<unknown>"
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
cert = ssl_socket.peercert
|
|
240
|
+
@server.lowlevel_error e, c.env
|
|
241
|
+
@events.ssl_error e, c.io
|
|
246
242
|
|
|
247
243
|
c.close
|
|
248
244
|
clear_monitor mon
|
|
249
245
|
|
|
250
|
-
@events.ssl_error @server, addr, cert, e
|
|
251
|
-
|
|
252
246
|
# The client doesn't know HTTP well
|
|
253
247
|
rescue HttpParserError => e
|
|
254
248
|
@server.lowlevel_error(e, c.env)
|
|
@@ -258,7 +252,7 @@ module Puma
|
|
|
258
252
|
|
|
259
253
|
clear_monitor mon
|
|
260
254
|
|
|
261
|
-
@events.parse_error
|
|
255
|
+
@events.parse_error e, c
|
|
262
256
|
rescue StandardError => e
|
|
263
257
|
@server.lowlevel_error(e, c.env)
|
|
264
258
|
|
data/lib/puma/runner.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'puma/server'
|
|
4
4
|
require 'puma/const'
|
|
5
|
-
require 'puma/minissl/context_builder'
|
|
6
5
|
|
|
7
6
|
module Puma
|
|
8
7
|
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
|
@@ -18,10 +17,6 @@ module Puma
|
|
|
18
17
|
@started_at = Time.now
|
|
19
18
|
end
|
|
20
19
|
|
|
21
|
-
def daemon?
|
|
22
|
-
@options[:daemon]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
20
|
def development?
|
|
26
21
|
@options[:environment] == "development"
|
|
27
22
|
end
|
|
@@ -34,7 +29,8 @@ module Puma
|
|
|
34
29
|
@events.log str
|
|
35
30
|
end
|
|
36
31
|
|
|
37
|
-
|
|
32
|
+
# @version 5.0.0
|
|
33
|
+
def stop_control
|
|
38
34
|
@control.stop(true) if @control
|
|
39
35
|
end
|
|
40
36
|
|
|
@@ -52,42 +48,27 @@ module Puma
|
|
|
52
48
|
|
|
53
49
|
require 'puma/app/status'
|
|
54
50
|
|
|
55
|
-
uri = URI.parse str
|
|
56
|
-
|
|
57
51
|
if token = @options[:control_auth_token]
|
|
58
52
|
token = nil if token.empty? || token == 'none'
|
|
59
53
|
end
|
|
60
54
|
|
|
61
55
|
app = Puma::App::Status.new @launcher, token
|
|
62
56
|
|
|
63
|
-
control = Puma::Server.new app, @launcher.events
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
case uri.scheme
|
|
68
|
-
when "ssl"
|
|
69
|
-
log "* Starting control server on #{str}"
|
|
70
|
-
params = Util.parse_query uri.query
|
|
71
|
-
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
|
72
|
-
|
|
73
|
-
control.add_ssl_listener uri.host, uri.port, ctx
|
|
74
|
-
when "tcp"
|
|
75
|
-
log "* Starting control server on #{str}"
|
|
76
|
-
control.add_tcp_listener uri.host, uri.port
|
|
77
|
-
when "unix"
|
|
78
|
-
log "* Starting control server on #{str}"
|
|
79
|
-
path = "#{uri.host}#{uri.path}"
|
|
80
|
-
mask = @options[:control_url_umask]
|
|
81
|
-
|
|
82
|
-
control.add_unix_listener path, mask
|
|
83
|
-
else
|
|
84
|
-
error "Invalid control URI: #{str}"
|
|
85
|
-
end
|
|
57
|
+
control = Puma::Server.new app, @launcher.events,
|
|
58
|
+
{ min_threads: 0, max_threads: 1 }
|
|
59
|
+
|
|
60
|
+
control.binder.parse [str], self, 'Starting control server'
|
|
86
61
|
|
|
87
62
|
control.run
|
|
88
63
|
@control = control
|
|
89
64
|
end
|
|
90
65
|
|
|
66
|
+
# @version 5.0.0
|
|
67
|
+
def close_control_listeners
|
|
68
|
+
@control.binder.close_listeners if @control
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @!attribute [r] ruby_engine
|
|
91
72
|
def ruby_engine
|
|
92
73
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
|
93
74
|
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
|
@@ -108,10 +89,6 @@ module Puma
|
|
|
108
89
|
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
|
109
90
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
|
110
91
|
log "* Environment: #{ENV['RACK_ENV']}"
|
|
111
|
-
|
|
112
|
-
if @options[:mode] == :tcp
|
|
113
|
-
log "* Mode: Lopez Express (tcp)"
|
|
114
|
-
end
|
|
115
92
|
end
|
|
116
93
|
|
|
117
94
|
def redirected_io?
|
|
@@ -150,7 +127,6 @@ module Puma
|
|
|
150
127
|
exit 1
|
|
151
128
|
end
|
|
152
129
|
|
|
153
|
-
# Load the app before we daemonize.
|
|
154
130
|
begin
|
|
155
131
|
@app = @launcher.config.app
|
|
156
132
|
rescue Exception => e
|
|
@@ -161,31 +137,14 @@ module Puma
|
|
|
161
137
|
@launcher.binder.parse @options[:binds], self
|
|
162
138
|
end
|
|
163
139
|
|
|
140
|
+
# @!attribute [r] app
|
|
164
141
|
def app
|
|
165
142
|
@app ||= @launcher.config.app
|
|
166
143
|
end
|
|
167
144
|
|
|
168
145
|
def start_server
|
|
169
|
-
min_t = @options[:min_threads]
|
|
170
|
-
max_t = @options[:max_threads]
|
|
171
|
-
|
|
172
146
|
server = Puma::Server.new app, @launcher.events, @options
|
|
173
|
-
server.min_threads = min_t
|
|
174
|
-
server.max_threads = max_t
|
|
175
147
|
server.inherit_binder @launcher.binder
|
|
176
|
-
|
|
177
|
-
if @options[:mode] == :tcp
|
|
178
|
-
server.tcp_mode!
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
if @options[:early_hints]
|
|
182
|
-
server.early_hints = true
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
unless development? || test?
|
|
186
|
-
server.leak_stack_on_error = false
|
|
187
|
-
end
|
|
188
|
-
|
|
189
148
|
server
|
|
190
149
|
end
|
|
191
150
|
end
|
data/lib/puma/server.rb
CHANGED
|
@@ -9,10 +9,8 @@ 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/puma_http11'
|
|
13
|
+
require 'puma/io_buffer'
|
|
16
14
|
|
|
17
15
|
require 'socket'
|
|
18
16
|
require 'forwardable'
|
|
@@ -36,14 +34,21 @@ module Puma
|
|
|
36
34
|
|
|
37
35
|
attr_reader :thread
|
|
38
36
|
attr_reader :events
|
|
37
|
+
attr_reader :min_threads, :max_threads # for #stats
|
|
38
|
+
attr_reader :requests_count # @version 5.0.0
|
|
39
|
+
|
|
40
|
+
# the following may be deprecated in the future
|
|
41
|
+
attr_reader :auto_trim_time
|
|
42
|
+
attr_reader :first_data_timeout
|
|
43
|
+
attr_reader :persistent_timeout
|
|
44
|
+
attr_reader :reaping_time
|
|
45
|
+
|
|
39
46
|
attr_accessor :app
|
|
47
|
+
attr_accessor :binder
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
attr_accessor :auto_trim_time
|
|
45
|
-
attr_accessor :reaping_time
|
|
46
|
-
attr_accessor :first_data_timeout
|
|
49
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
|
|
50
|
+
|
|
51
|
+
ThreadLocalKey = :puma_server
|
|
47
52
|
|
|
48
53
|
# Create a server for the rack app +app+.
|
|
49
54
|
#
|
|
@@ -53,56 +58,80 @@ module Puma
|
|
|
53
58
|
# Server#run returns a thread that you can join on to wait for the server
|
|
54
59
|
# to do its work.
|
|
55
60
|
#
|
|
61
|
+
# @note Several instance variables exist so they are available for testing,
|
|
62
|
+
# and have default values set via +fetch+. Normally the values are set via
|
|
63
|
+
# `::Puma::Configuration.puma_default_options`.
|
|
64
|
+
#
|
|
56
65
|
def initialize(app, events=Events.stdio, options={})
|
|
57
66
|
@app = app
|
|
58
67
|
@events = events
|
|
59
68
|
|
|
60
|
-
@check, @notify =
|
|
61
|
-
|
|
69
|
+
@check, @notify = nil
|
|
62
70
|
@status = :stop
|
|
63
71
|
|
|
64
|
-
@min_threads = 0
|
|
65
|
-
@max_threads = 16
|
|
66
72
|
@auto_trim_time = 30
|
|
67
73
|
@reaping_time = 1
|
|
68
74
|
|
|
69
75
|
@thread = nil
|
|
70
76
|
@thread_pool = nil
|
|
71
|
-
@early_hints = nil
|
|
72
77
|
|
|
73
|
-
@
|
|
74
|
-
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
|
78
|
+
@options = options
|
|
75
79
|
|
|
76
|
-
@
|
|
80
|
+
@early_hints = options.fetch :early_hints, nil
|
|
81
|
+
@first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
|
|
82
|
+
@min_threads = options.fetch :min_threads, 0
|
|
83
|
+
@max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
|
|
84
|
+
@persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
|
|
85
|
+
@queue_requests = options.fetch :queue_requests, true
|
|
77
86
|
|
|
78
|
-
@leak_stack_on_error =
|
|
87
|
+
@leak_stack_on_error = !!(@options[:environment] =~ /\A(development|test)\z/)
|
|
79
88
|
|
|
80
|
-
@
|
|
81
|
-
@queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
|
|
89
|
+
@binder = Binder.new(events)
|
|
82
90
|
|
|
83
91
|
ENV['RACK_ENV'] ||= "development"
|
|
84
92
|
|
|
85
93
|
@mode = :http
|
|
86
94
|
|
|
87
95
|
@precheck_closing = true
|
|
88
|
-
end
|
|
89
96
|
|
|
90
|
-
|
|
97
|
+
@requests_count = 0
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
@shutdown_mutex = Mutex.new
|
|
100
|
+
end
|
|
93
101
|
|
|
94
102
|
def inherit_binder(bind)
|
|
95
103
|
@binder = bind
|
|
96
104
|
end
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
class << self
|
|
107
|
+
# @!attribute [r] current
|
|
108
|
+
def current
|
|
109
|
+
Thread.current[ThreadLocalKey]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# :nodoc:
|
|
113
|
+
# @version 5.0.0
|
|
114
|
+
def tcp_cork_supported?
|
|
115
|
+
RbConfig::CONFIG['host_os'] =~ /linux/ &&
|
|
116
|
+
Socket.const_defined?(:IPPROTO_TCP) &&
|
|
117
|
+
Socket.const_defined?(:TCP_CORK)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# :nodoc:
|
|
121
|
+
# @version 5.0.0
|
|
122
|
+
def closed_socket_supported?
|
|
123
|
+
RbConfig::CONFIG['host_os'] =~ /linux/ &&
|
|
124
|
+
Socket.const_defined?(:IPPROTO_TCP) &&
|
|
125
|
+
Socket.const_defined?(:TCP_INFO)
|
|
126
|
+
end
|
|
127
|
+
private :tcp_cork_supported?
|
|
128
|
+
private :closed_socket_supported?
|
|
100
129
|
end
|
|
101
130
|
|
|
102
131
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
|
103
132
|
# packetizes our stream. This improves both latency and throughput.
|
|
104
133
|
#
|
|
105
|
-
if
|
|
134
|
+
if tcp_cork_supported?
|
|
106
135
|
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
|
107
136
|
|
|
108
137
|
# 6 == Socket::IPPROTO_TCP
|
|
@@ -110,7 +139,7 @@ module Puma
|
|
|
110
139
|
# 1/0 == turn on/off
|
|
111
140
|
def cork_socket(socket)
|
|
112
141
|
begin
|
|
113
|
-
socket.setsockopt(
|
|
142
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
|
|
114
143
|
rescue IOError, SystemCallError
|
|
115
144
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
116
145
|
end
|
|
@@ -118,18 +147,26 @@ module Puma
|
|
|
118
147
|
|
|
119
148
|
def uncork_socket(socket)
|
|
120
149
|
begin
|
|
121
|
-
socket.setsockopt(
|
|
150
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
|
|
122
151
|
rescue IOError, SystemCallError
|
|
123
152
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
124
153
|
end
|
|
125
154
|
end
|
|
155
|
+
else
|
|
156
|
+
def cork_socket(socket)
|
|
157
|
+
end
|
|
126
158
|
|
|
159
|
+
def uncork_socket(socket)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if closed_socket_supported?
|
|
127
164
|
def closed_socket?(socket)
|
|
128
165
|
return false unless socket.kind_of? TCPSocket
|
|
129
166
|
return false unless @precheck_closing
|
|
130
167
|
|
|
131
168
|
begin
|
|
132
|
-
tcp_info = socket.getsockopt(Socket::
|
|
169
|
+
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
|
133
170
|
rescue IOError, SystemCallError
|
|
134
171
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
135
172
|
@precheck_closing = false
|
|
@@ -141,21 +178,17 @@ module Puma
|
|
|
141
178
|
end
|
|
142
179
|
end
|
|
143
180
|
else
|
|
144
|
-
def cork_socket(socket)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def uncork_socket(socket)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
181
|
def closed_socket?(socket)
|
|
151
182
|
false
|
|
152
183
|
end
|
|
153
184
|
end
|
|
154
185
|
|
|
186
|
+
# @!attribute [r] backlog
|
|
155
187
|
def backlog
|
|
156
188
|
@thread_pool and @thread_pool.backlog
|
|
157
189
|
end
|
|
158
190
|
|
|
191
|
+
# @!attribute [r] running
|
|
159
192
|
def running
|
|
160
193
|
@thread_pool and @thread_pool.spawned
|
|
161
194
|
end
|
|
@@ -168,111 +201,11 @@ module Puma
|
|
|
168
201
|
# there are 5 threads sitting idle ready to take
|
|
169
202
|
# a request. If one request comes in, then the
|
|
170
203
|
# value would be 4 until it finishes processing.
|
|
204
|
+
# @!attribute [r] pool_capacity
|
|
171
205
|
def pool_capacity
|
|
172
206
|
@thread_pool and @thread_pool.pool_capacity
|
|
173
207
|
end
|
|
174
208
|
|
|
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
209
|
# Runs the server.
|
|
277
210
|
#
|
|
278
211
|
# If +background+ is true (the default) then a thread is spun
|
|
@@ -286,15 +219,9 @@ module Puma
|
|
|
286
219
|
|
|
287
220
|
@status = :run
|
|
288
221
|
|
|
289
|
-
if @mode == :tcp
|
|
290
|
-
return run_lopez_mode(background)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
queue_requests = @queue_requests
|
|
294
|
-
|
|
295
222
|
@thread_pool = ThreadPool.new(@min_threads,
|
|
296
223
|
@max_threads,
|
|
297
|
-
IOBuffer) do |client, buffer|
|
|
224
|
+
::Puma::IOBuffer) do |client, buffer|
|
|
298
225
|
|
|
299
226
|
# Advertise this server into the thread
|
|
300
227
|
Thread.current[ThreadLocalKey] = self
|
|
@@ -302,40 +229,48 @@ module Puma
|
|
|
302
229
|
process_now = false
|
|
303
230
|
|
|
304
231
|
begin
|
|
305
|
-
if queue_requests
|
|
232
|
+
if @queue_requests
|
|
306
233
|
process_now = client.eagerly_finish
|
|
307
234
|
else
|
|
308
|
-
|
|
235
|
+
@thread_pool.with_force_shutdown do
|
|
236
|
+
client.finish(@first_data_timeout)
|
|
237
|
+
end
|
|
309
238
|
process_now = true
|
|
310
239
|
end
|
|
311
240
|
rescue MiniSSL::SSLError => e
|
|
312
|
-
|
|
313
|
-
addr = ssl_socket.peeraddr.last
|
|
314
|
-
cert = ssl_socket.peercert
|
|
315
|
-
|
|
241
|
+
@events.ssl_error e, client.io
|
|
316
242
|
client.close
|
|
317
243
|
|
|
318
|
-
@events.ssl_error self, addr, cert, e
|
|
319
244
|
rescue HttpParserError => e
|
|
320
245
|
client.write_error(400)
|
|
321
246
|
client.close
|
|
322
247
|
|
|
323
|
-
@events.parse_error
|
|
324
|
-
rescue
|
|
248
|
+
@events.parse_error e, client
|
|
249
|
+
rescue EOFError => e
|
|
325
250
|
client.close
|
|
251
|
+
|
|
252
|
+
# Swallow, do not log
|
|
253
|
+
rescue ConnectionError, ThreadPool::ForceShutdown => e
|
|
254
|
+
client.close
|
|
255
|
+
|
|
256
|
+
@events.connection_error e, client
|
|
326
257
|
else
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
258
|
+
process_now ||= @shutdown_mutex.synchronize do
|
|
259
|
+
next true unless @queue_requests
|
|
260
|
+
client.set_timeout @first_data_timeout
|
|
261
|
+
@reactor.add client
|
|
262
|
+
false
|
|
263
|
+
end
|
|
264
|
+
process_client client, buffer if process_now
|
|
333
265
|
end
|
|
266
|
+
|
|
267
|
+
process_now
|
|
334
268
|
end
|
|
335
269
|
|
|
270
|
+
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
|
336
271
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
|
337
272
|
|
|
338
|
-
if queue_requests
|
|
273
|
+
if @queue_requests
|
|
339
274
|
@reactor = Reactor.new self, @thread_pool
|
|
340
275
|
@reactor.run_in_thread
|
|
341
276
|
end
|
|
@@ -362,6 +297,7 @@ module Puma
|
|
|
362
297
|
end
|
|
363
298
|
|
|
364
299
|
def handle_servers
|
|
300
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
|
365
301
|
begin
|
|
366
302
|
check = @check
|
|
367
303
|
sockets = [check] + @binder.ios
|
|
@@ -385,51 +321,51 @@ module Puma
|
|
|
385
321
|
if sock == check
|
|
386
322
|
break if handle_check
|
|
387
323
|
else
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
324
|
+
pool.wait_until_not_full
|
|
325
|
+
pool.wait_for_less_busy_worker(
|
|
326
|
+
@options[:wait_for_less_busy_worker].to_f)
|
|
327
|
+
|
|
328
|
+
io = begin
|
|
329
|
+
sock.accept_nonblock
|
|
330
|
+
rescue IO::WaitReadable
|
|
331
|
+
next
|
|
412
332
|
end
|
|
333
|
+
client = Client.new io, @binder.env(sock)
|
|
334
|
+
if remote_addr_value
|
|
335
|
+
client.peerip = remote_addr_value
|
|
336
|
+
elsif remote_addr_header
|
|
337
|
+
client.remote_addr_header = remote_addr_header
|
|
338
|
+
end
|
|
339
|
+
pool << client
|
|
413
340
|
end
|
|
414
341
|
end
|
|
415
342
|
rescue Object => e
|
|
416
|
-
@events.unknown_error
|
|
343
|
+
@events.unknown_error e, nil, "Listen loop"
|
|
417
344
|
end
|
|
418
345
|
end
|
|
419
346
|
|
|
420
347
|
@events.fire :state, @status
|
|
421
348
|
|
|
422
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
|
423
349
|
if queue_requests
|
|
350
|
+
@shutdown_mutex.synchronize do
|
|
351
|
+
@queue_requests = false
|
|
352
|
+
end
|
|
424
353
|
@reactor.clear!
|
|
425
354
|
@reactor.shutdown
|
|
426
355
|
end
|
|
356
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
|
427
357
|
rescue Exception => e
|
|
428
|
-
|
|
429
|
-
STDERR.puts e.backtrace
|
|
358
|
+
@events.unknown_error e, nil, "Exception handling servers"
|
|
430
359
|
ensure
|
|
431
|
-
|
|
360
|
+
begin
|
|
361
|
+
@check.close unless @check.closed?
|
|
362
|
+
rescue Errno::EBADF, RuntimeError
|
|
363
|
+
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
|
364
|
+
# Errno::EBADF is infrequently raised
|
|
365
|
+
end
|
|
432
366
|
@notify.close
|
|
367
|
+
@notify = nil
|
|
368
|
+
@check = nil
|
|
433
369
|
end
|
|
434
370
|
|
|
435
371
|
@events.fire :state, :done
|
|
@@ -476,7 +412,6 @@ module Puma
|
|
|
476
412
|
close_socket = false
|
|
477
413
|
return
|
|
478
414
|
when true
|
|
479
|
-
return unless @queue_requests
|
|
480
415
|
buffer.reset
|
|
481
416
|
|
|
482
417
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
|
@@ -493,38 +428,39 @@ module Puma
|
|
|
493
428
|
check_for_more_data = false
|
|
494
429
|
end
|
|
495
430
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
431
|
+
next_request_ready = @thread_pool.with_force_shutdown do
|
|
432
|
+
client.reset(check_for_more_data)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
unless next_request_ready
|
|
436
|
+
@shutdown_mutex.synchronize do
|
|
437
|
+
return unless @queue_requests
|
|
438
|
+
close_socket = false
|
|
439
|
+
client.set_timeout @persistent_timeout
|
|
440
|
+
@reactor.add client
|
|
441
|
+
return
|
|
442
|
+
end
|
|
501
443
|
end
|
|
502
444
|
end
|
|
503
445
|
end
|
|
504
446
|
|
|
505
447
|
# The client disconnected while we were reading data
|
|
506
|
-
rescue ConnectionError
|
|
448
|
+
rescue ConnectionError, ThreadPool::ForceShutdown
|
|
507
449
|
# Swallow them. The ensure tries to close +client+ down
|
|
508
450
|
|
|
509
451
|
# SSL handshake error
|
|
510
452
|
rescue MiniSSL::SSLError => e
|
|
511
|
-
lowlevel_error
|
|
512
|
-
|
|
513
|
-
ssl_socket = client.io
|
|
514
|
-
addr = ssl_socket.peeraddr.last
|
|
515
|
-
cert = ssl_socket.peercert
|
|
516
|
-
|
|
453
|
+
lowlevel_error e, client.env
|
|
454
|
+
@events.ssl_error e, client.io
|
|
517
455
|
close_socket = true
|
|
518
456
|
|
|
519
|
-
@events.ssl_error self, addr, cert, e
|
|
520
|
-
|
|
521
457
|
# The client doesn't know HTTP well
|
|
522
458
|
rescue HttpParserError => e
|
|
523
459
|
lowlevel_error(e, client.env)
|
|
524
460
|
|
|
525
461
|
client.write_error(400)
|
|
526
462
|
|
|
527
|
-
@events.parse_error
|
|
463
|
+
@events.parse_error e, client
|
|
528
464
|
|
|
529
465
|
# Server error
|
|
530
466
|
rescue StandardError => e
|
|
@@ -532,8 +468,7 @@ module Puma
|
|
|
532
468
|
|
|
533
469
|
client.write_error(500)
|
|
534
470
|
|
|
535
|
-
@events.unknown_error
|
|
536
|
-
|
|
471
|
+
@events.unknown_error e, nil, "Read"
|
|
537
472
|
ensure
|
|
538
473
|
buffer.reset
|
|
539
474
|
|
|
@@ -543,7 +478,7 @@ module Puma
|
|
|
543
478
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
544
479
|
# Already closed
|
|
545
480
|
rescue StandardError => e
|
|
546
|
-
@events.unknown_error
|
|
481
|
+
@events.unknown_error e, nil, "Client"
|
|
547
482
|
end
|
|
548
483
|
end
|
|
549
484
|
end
|
|
@@ -579,7 +514,7 @@ module Puma
|
|
|
579
514
|
|
|
580
515
|
env[PATH_INFO] = env[REQUEST_PATH]
|
|
581
516
|
|
|
582
|
-
# From
|
|
517
|
+
# From https://www.ietf.org/rfc/rfc3875 :
|
|
583
518
|
# "Script authors should be aware that the REMOTE_ADDR and
|
|
584
519
|
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
585
520
|
# may not identify the ultimate source of the request.
|
|
@@ -626,6 +561,8 @@ module Puma
|
|
|
626
561
|
#
|
|
627
562
|
# Finally, it'll return +true+ on keep-alive connections.
|
|
628
563
|
def handle_request(req, lines)
|
|
564
|
+
@requests_count +=1
|
|
565
|
+
|
|
629
566
|
env = req.env
|
|
630
567
|
client = req.io
|
|
631
568
|
|
|
@@ -666,7 +603,8 @@ module Puma
|
|
|
666
603
|
end
|
|
667
604
|
|
|
668
605
|
fast_write client, "\r\n".freeze
|
|
669
|
-
rescue ConnectionError
|
|
606
|
+
rescue ConnectionError => e
|
|
607
|
+
@events.debug_error e
|
|
670
608
|
# noop, if we lost the socket we just won't send the early hints
|
|
671
609
|
end
|
|
672
610
|
}
|
|
@@ -694,7 +632,7 @@ module Puma
|
|
|
694
632
|
to_add = {}
|
|
695
633
|
end
|
|
696
634
|
|
|
697
|
-
to_add[k.
|
|
635
|
+
to_add[k.tr(",", "_")] = v
|
|
698
636
|
end
|
|
699
637
|
end
|
|
700
638
|
|
|
@@ -710,7 +648,9 @@ module Puma
|
|
|
710
648
|
|
|
711
649
|
begin
|
|
712
650
|
begin
|
|
713
|
-
status, headers, res_body = @
|
|
651
|
+
status, headers, res_body = @thread_pool.with_force_shutdown do
|
|
652
|
+
@app.call(env)
|
|
653
|
+
end
|
|
714
654
|
|
|
715
655
|
return :async if req.hijacked
|
|
716
656
|
|
|
@@ -724,17 +664,14 @@ module Puma
|
|
|
724
664
|
return :async
|
|
725
665
|
end
|
|
726
666
|
rescue ThreadPool::ForceShutdown => e
|
|
727
|
-
@events.
|
|
728
|
-
@events.
|
|
729
|
-
|
|
730
|
-
status = 503
|
|
731
|
-
headers = {}
|
|
732
|
-
res_body = ["Request was internally terminated early\n"]
|
|
667
|
+
@events.unknown_error e, req, "Rack app"
|
|
668
|
+
@events.log "Detected force shutdown of a thread"
|
|
733
669
|
|
|
670
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
|
734
671
|
rescue Exception => e
|
|
735
|
-
@events.unknown_error
|
|
672
|
+
@events.unknown_error e, req, "Rack app"
|
|
736
673
|
|
|
737
|
-
status, headers, res_body = lowlevel_error(e, env)
|
|
674
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
|
738
675
|
end
|
|
739
676
|
|
|
740
677
|
content_length = nil
|
|
@@ -749,10 +686,10 @@ module Puma
|
|
|
749
686
|
line_ending = LINE_END
|
|
750
687
|
colon = COLON
|
|
751
688
|
|
|
752
|
-
http_11 =
|
|
689
|
+
http_11 = env[HTTP_VERSION] == HTTP_11
|
|
690
|
+
if http_11
|
|
753
691
|
allow_chunked = true
|
|
754
692
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
|
755
|
-
include_keepalive_header = false
|
|
756
693
|
|
|
757
694
|
# An optimization. The most common response is 200, so we can
|
|
758
695
|
# reply with the proper 200 status without having to compute
|
|
@@ -766,11 +703,9 @@ module Puma
|
|
|
766
703
|
|
|
767
704
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
768
705
|
end
|
|
769
|
-
true
|
|
770
706
|
else
|
|
771
707
|
allow_chunked = false
|
|
772
708
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
|
773
|
-
include_keepalive_header = keep_alive
|
|
774
709
|
|
|
775
710
|
# Same optimization as above for HTTP/1.1
|
|
776
711
|
#
|
|
@@ -782,9 +717,12 @@ module Puma
|
|
|
782
717
|
|
|
783
718
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
784
719
|
end
|
|
785
|
-
false
|
|
786
720
|
end
|
|
787
721
|
|
|
722
|
+
# regardless of what the client wants, we always close the connection
|
|
723
|
+
# if running without request queueing
|
|
724
|
+
keep_alive &&= @queue_requests
|
|
725
|
+
|
|
788
726
|
response_hijack = nil
|
|
789
727
|
|
|
790
728
|
headers.each do |k, vs|
|
|
@@ -811,10 +749,15 @@ module Puma
|
|
|
811
749
|
end
|
|
812
750
|
end
|
|
813
751
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
752
|
+
# HTTP/1.1 & 1.0 assume different defaults:
|
|
753
|
+
# - HTTP 1.0 assumes the connection will be closed if not specified
|
|
754
|
+
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
|
755
|
+
# Only set the header if we're doing something which is not the default
|
|
756
|
+
# for this protocol version
|
|
757
|
+
if http_11
|
|
758
|
+
lines << CONNECTION_CLOSE if !keep_alive
|
|
759
|
+
else
|
|
760
|
+
lines << CONNECTION_KEEP_ALIVE if keep_alive
|
|
818
761
|
end
|
|
819
762
|
|
|
820
763
|
if no_body
|
|
@@ -941,19 +884,21 @@ module Puma
|
|
|
941
884
|
|
|
942
885
|
# A fallback rack response if +@app+ raises as exception.
|
|
943
886
|
#
|
|
944
|
-
def lowlevel_error(e, env)
|
|
887
|
+
def lowlevel_error(e, env, status=500)
|
|
945
888
|
if handler = @options[:lowlevel_error_handler]
|
|
946
889
|
if handler.arity == 1
|
|
947
890
|
return handler.call(e)
|
|
948
|
-
|
|
891
|
+
elsif handler.arity == 2
|
|
949
892
|
return handler.call(e, env)
|
|
893
|
+
else
|
|
894
|
+
return handler.call(e, env, status)
|
|
950
895
|
end
|
|
951
896
|
end
|
|
952
897
|
|
|
953
898
|
if @leak_stack_on_error
|
|
954
|
-
[
|
|
899
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
|
955
900
|
else
|
|
956
|
-
[
|
|
901
|
+
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
|
957
902
|
end
|
|
958
903
|
end
|
|
959
904
|
|
|
@@ -1003,7 +948,7 @@ module Puma
|
|
|
1003
948
|
|
|
1004
949
|
if @thread_pool
|
|
1005
950
|
if timeout = @options[:force_shutdown_after]
|
|
1006
|
-
@thread_pool.shutdown timeout.
|
|
951
|
+
@thread_pool.shutdown timeout.to_f
|
|
1007
952
|
else
|
|
1008
953
|
@thread_pool.shutdown
|
|
1009
954
|
end
|
|
@@ -1011,9 +956,10 @@ module Puma
|
|
|
1011
956
|
end
|
|
1012
957
|
|
|
1013
958
|
def notify_safely(message)
|
|
959
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
|
1014
960
|
begin
|
|
1015
961
|
@notify << message
|
|
1016
|
-
rescue IOError
|
|
962
|
+
rescue IOError, NoMethodError, Errno::EPIPE
|
|
1017
963
|
# The server, in another thread, is shutting down
|
|
1018
964
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
1019
965
|
rescue RuntimeError => e
|
|
@@ -1040,8 +986,9 @@ module Puma
|
|
|
1040
986
|
@thread.join if @thread && sync
|
|
1041
987
|
end
|
|
1042
988
|
|
|
1043
|
-
def begin_restart
|
|
989
|
+
def begin_restart(sync=false)
|
|
1044
990
|
notify_safely(RESTART_COMMAND)
|
|
991
|
+
@thread.join if @thread && sync
|
|
1045
992
|
end
|
|
1046
993
|
|
|
1047
994
|
def fast_write(io, str)
|
|
@@ -1065,12 +1012,6 @@ module Puma
|
|
|
1065
1012
|
end
|
|
1066
1013
|
private :fast_write
|
|
1067
1014
|
|
|
1068
|
-
ThreadLocalKey = :puma_server
|
|
1069
|
-
|
|
1070
|
-
def self.current
|
|
1071
|
-
Thread.current[ThreadLocalKey]
|
|
1072
|
-
end
|
|
1073
|
-
|
|
1074
1015
|
def shutting_down?
|
|
1075
1016
|
@status == :stop || @status == :restart
|
|
1076
1017
|
end
|
|
@@ -1079,5 +1020,16 @@ module Puma
|
|
|
1079
1020
|
HTTP_INJECTION_REGEX =~ header_value.to_s
|
|
1080
1021
|
end
|
|
1081
1022
|
private :possible_header_injection?
|
|
1023
|
+
|
|
1024
|
+
# List of methods invoked by #stats.
|
|
1025
|
+
# @version 5.0.0
|
|
1026
|
+
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
|
1027
|
+
|
|
1028
|
+
# Returns a hash of stats about the running server for reporting purposes.
|
|
1029
|
+
# @version 5.0.0
|
|
1030
|
+
# @!attribute [r] stats
|
|
1031
|
+
def stats
|
|
1032
|
+
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
|
1033
|
+
end
|
|
1082
1034
|
end
|
|
1083
1035
|
end
|