puma 4.3.8 → 5.0.0.beta1
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 +58 -21
- data/LICENSE +23 -20
- data/README.md +17 -11
- data/docs/deployment.md +3 -1
- 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 +1 -0
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +3 -2
- data/ext/puma_http11/http11_parser.c +1 -3
- data/ext/puma_http11/http11_parser.rl +1 -3
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/puma_http11.c +2 -38
- data/lib/puma.rb +4 -0
- data/lib/puma/app/status.rb +16 -5
- data/lib/puma/binder.rb +62 -60
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +35 -32
- data/lib/puma/cluster.rb +179 -74
- data/lib/puma/configuration.rb +30 -42
- data/lib/puma/const.rb +2 -3
- data/lib/puma/control_cli.rb +27 -17
- data/lib/puma/detect.rb +8 -0
- data/lib/puma/dsl.rb +70 -34
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +41 -29
- data/lib/puma/minissl.rb +13 -8
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +6 -1
- data/lib/puma/runner.rb +5 -34
- data/lib/puma/server.rb +70 -190
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +5 -2
- data/lib/puma/thread_pool.rb +85 -47
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +20 -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/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/launcher.rb
CHANGED
@@ -48,7 +48,8 @@ module Puma
|
|
48
48
|
@config = conf
|
49
49
|
|
50
50
|
@binder = Binder.new(@events)
|
51
|
-
@binder.
|
51
|
+
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
52
|
+
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
52
53
|
|
53
54
|
@environment = conf.environment
|
54
55
|
|
@@ -69,10 +70,6 @@ module Puma
|
|
69
70
|
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
70
71
|
end
|
71
72
|
|
72
|
-
if @options[:daemon] && Puma.windows?
|
73
|
-
unsupported 'daemon mode not supported on Windows'
|
74
|
-
end
|
75
|
-
|
76
73
|
Dir.chdir(@restart_dir)
|
77
74
|
|
78
75
|
prune_bundler if prune_bundler?
|
@@ -105,6 +102,7 @@ module Puma
|
|
105
102
|
write_pid
|
106
103
|
|
107
104
|
path = @options[:state]
|
105
|
+
permission = @options[:state_permission]
|
108
106
|
return unless path
|
109
107
|
|
110
108
|
require 'puma/state_file'
|
@@ -114,7 +112,7 @@ module Puma
|
|
114
112
|
sf.control_url = @options[:control_url]
|
115
113
|
sf.control_auth_token = @options[:control_auth_token]
|
116
114
|
|
117
|
-
sf.save path
|
115
|
+
sf.save path, permission
|
118
116
|
end
|
119
117
|
|
120
118
|
# Delete the configured pidfile
|
@@ -184,12 +182,12 @@ module Puma
|
|
184
182
|
when :exit
|
185
183
|
# nothing
|
186
184
|
end
|
187
|
-
@
|
185
|
+
close_binder_listeners unless @status == :restart
|
188
186
|
end
|
189
187
|
|
190
|
-
# Return
|
191
|
-
def
|
192
|
-
@binder.
|
188
|
+
# Return all tcp ports the launcher may be using, TCP or SSL
|
189
|
+
def connected_ports
|
190
|
+
@binder.connected_ports
|
193
191
|
end
|
194
192
|
|
195
193
|
def restart_args
|
@@ -202,9 +200,21 @@ module Puma
|
|
202
200
|
end
|
203
201
|
|
204
202
|
def close_binder_listeners
|
203
|
+
@runner.close_control_listeners
|
205
204
|
@binder.close_listeners
|
206
205
|
end
|
207
206
|
|
207
|
+
def thread_status
|
208
|
+
Thread.list.each do |thread|
|
209
|
+
name = "Thread: TID-#{thread.object_id.to_s(36)}"
|
210
|
+
name += " #{thread['label']}" if thread['label']
|
211
|
+
name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
|
212
|
+
backtrace = thread.backtrace || ["<no backtrace available>"]
|
213
|
+
|
214
|
+
yield name, backtrace
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
208
218
|
private
|
209
219
|
|
210
220
|
# If configured, write the pid of the current process out
|
@@ -225,7 +235,7 @@ module Puma
|
|
225
235
|
end
|
226
236
|
|
227
237
|
def restart!
|
228
|
-
@config.run_hooks :on_restart, self
|
238
|
+
@config.run_hooks :on_restart, self, @events
|
229
239
|
|
230
240
|
if Puma.jruby?
|
231
241
|
close_binder_listeners
|
@@ -241,6 +251,7 @@ module Puma
|
|
241
251
|
else
|
242
252
|
argv = restart_args
|
243
253
|
Dir.chdir(@restart_dir)
|
254
|
+
ENV.update(@binder.redirects_for_restart_env)
|
244
255
|
argv += [@binder.redirects_for_restart]
|
245
256
|
Kernel.exec(*argv)
|
246
257
|
end
|
@@ -286,8 +297,10 @@ module Puma
|
|
286
297
|
|
287
298
|
log '* Pruning Bundler environment'
|
288
299
|
home = ENV['GEM_HOME']
|
289
|
-
Bundler.
|
300
|
+
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
301
|
+
with_unbundled_env do
|
290
302
|
ENV['GEM_HOME'] = home
|
303
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
291
304
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
292
305
|
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
293
306
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
@@ -323,21 +336,6 @@ module Puma
|
|
323
336
|
log "- Goodbye!"
|
324
337
|
end
|
325
338
|
|
326
|
-
def log_thread_status
|
327
|
-
Thread.list.each do |thread|
|
328
|
-
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
329
|
-
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
|
330
|
-
logstr += " #{thread.name}" if thread.respond_to?(:name)
|
331
|
-
log logstr
|
332
|
-
|
333
|
-
if thread.backtrace
|
334
|
-
log thread.backtrace.join("\n")
|
335
|
-
else
|
336
|
-
log "<no backtrace available>"
|
337
|
-
end
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
339
|
def set_process_title
|
342
340
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
343
341
|
end
|
@@ -456,8 +454,13 @@ module Puma
|
|
456
454
|
end
|
457
455
|
|
458
456
|
begin
|
459
|
-
|
460
|
-
|
457
|
+
unless Puma.jruby? # INFO in use by JVM already
|
458
|
+
Signal.trap "SIGINFO" do
|
459
|
+
thread_status do |name, backtrace|
|
460
|
+
@events.log name
|
461
|
+
@events.log backtrace.map { |bt| " #{bt}" }
|
462
|
+
end
|
463
|
+
end
|
461
464
|
end
|
462
465
|
rescue Exception
|
463
466
|
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
@@ -471,5 +474,14 @@ module Puma
|
|
471
474
|
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
475
|
"You must have RubyGems #{min_version}+ to use this feature."
|
473
476
|
end
|
477
|
+
|
478
|
+
def with_unbundled_env
|
479
|
+
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
480
|
+
if bundler_ver < Gem::Version.new('2.1.0')
|
481
|
+
Bundler.with_clean_env { yield }
|
482
|
+
else
|
483
|
+
Bundler.with_unbundled_env { yield }
|
484
|
+
end
|
485
|
+
end
|
474
486
|
end
|
475
487
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -125,11 +125,14 @@ module Puma
|
|
125
125
|
|
126
126
|
def read_and_drop(timeout = 1)
|
127
127
|
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
128
|
+
case @socket.read_nonblock(1024, exception: false)
|
129
|
+
when nil
|
130
|
+
:eof
|
131
|
+
when :wait_readable
|
132
|
+
:eagain
|
133
|
+
else
|
134
|
+
:drop
|
135
|
+
end
|
133
136
|
end
|
134
137
|
|
135
138
|
def should_drop_bytes?
|
@@ -141,9 +144,7 @@ module Puma
|
|
141
144
|
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
142
145
|
# Don't let this socket hold this loop forever.
|
143
146
|
# 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
|
147
|
+
return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
|
147
148
|
rescue IOError, SystemCallError
|
148
149
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
149
150
|
# nothing
|
@@ -270,6 +271,10 @@ module Puma
|
|
270
271
|
Socket.new io, engine
|
271
272
|
end
|
272
273
|
|
274
|
+
def addr
|
275
|
+
@socket.addr
|
276
|
+
end
|
277
|
+
|
273
278
|
def close
|
274
279
|
@socket.close unless @socket.closed? # closed? call is for Windows
|
275
280
|
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/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
@@ -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
|
data/lib/puma/runner.rb
CHANGED
@@ -18,10 +18,6 @@ module Puma
|
|
18
18
|
@started_at = Time.now
|
19
19
|
end
|
20
20
|
|
21
|
-
def daemon?
|
22
|
-
@options[:daemon]
|
23
|
-
end
|
24
|
-
|
25
21
|
def development?
|
26
22
|
@options[:environment] == "development"
|
27
23
|
end
|
@@ -52,8 +48,6 @@ 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
|
@@ -64,30 +58,16 @@ module Puma
|
|
64
58
|
control.min_threads = 0
|
65
59
|
control.max_threads = 1
|
66
60
|
|
67
|
-
|
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
|
61
|
+
control.binder.parse [str], self, 'Starting control server'
|
86
62
|
|
87
63
|
control.run
|
88
64
|
@control = control
|
89
65
|
end
|
90
66
|
|
67
|
+
def close_control_listeners
|
68
|
+
@control.binder.close_listeners if @control
|
69
|
+
end
|
70
|
+
|
91
71
|
def ruby_engine
|
92
72
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
93
73
|
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
@@ -108,10 +88,6 @@ module Puma
|
|
108
88
|
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
109
89
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
110
90
|
log "* Environment: #{ENV['RACK_ENV']}"
|
111
|
-
|
112
|
-
if @options[:mode] == :tcp
|
113
|
-
log "* Mode: Lopez Express (tcp)"
|
114
|
-
end
|
115
91
|
end
|
116
92
|
|
117
93
|
def redirected_io?
|
@@ -150,7 +126,6 @@ module Puma
|
|
150
126
|
exit 1
|
151
127
|
end
|
152
128
|
|
153
|
-
# Load the app before we daemonize.
|
154
129
|
begin
|
155
130
|
@app = @launcher.config.app
|
156
131
|
rescue Exception => e
|
@@ -174,10 +149,6 @@ module Puma
|
|
174
149
|
server.max_threads = max_t
|
175
150
|
server.inherit_binder @launcher.binder
|
176
151
|
|
177
|
-
if @options[:mode] == :tcp
|
178
|
-
server.tcp_mode!
|
179
|
-
end
|
180
|
-
|
181
152
|
if @options[:early_hints]
|
182
153
|
server.early_hints = true
|
183
154
|
end
|
data/lib/puma/server.rb
CHANGED
@@ -11,6 +11,7 @@ require 'puma/client'
|
|
11
11
|
require 'puma/binder'
|
12
12
|
require 'puma/accept_nonblock'
|
13
13
|
require 'puma/util'
|
14
|
+
require 'puma/io_buffer'
|
14
15
|
|
15
16
|
require 'puma/puma_http11'
|
16
17
|
|
@@ -36,6 +37,7 @@ module Puma
|
|
36
37
|
|
37
38
|
attr_reader :thread
|
38
39
|
attr_reader :events
|
40
|
+
attr_reader :requests_count
|
39
41
|
attr_accessor :app
|
40
42
|
|
41
43
|
attr_accessor :min_threads
|
@@ -57,8 +59,7 @@ module Puma
|
|
57
59
|
@app = app
|
58
60
|
@events = events
|
59
61
|
|
60
|
-
@check, @notify =
|
61
|
-
|
62
|
+
@check, @notify = nil
|
62
63
|
@status = :stop
|
63
64
|
|
64
65
|
@min_threads = 0
|
@@ -85,20 +86,18 @@ module Puma
|
|
85
86
|
@mode = :http
|
86
87
|
|
87
88
|
@precheck_closing = true
|
89
|
+
|
90
|
+
@requests_count = 0
|
88
91
|
end
|
89
92
|
|
90
93
|
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
91
94
|
|
92
|
-
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :
|
95
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
|
93
96
|
|
94
97
|
def inherit_binder(bind)
|
95
98
|
@binder = bind
|
96
99
|
end
|
97
100
|
|
98
|
-
def tcp_mode!
|
99
|
-
@mode = :tcp
|
100
|
-
end
|
101
|
-
|
102
101
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
103
102
|
# packetizes our stream. This improves both latency and throughput.
|
104
103
|
#
|
@@ -172,107 +171,6 @@ module Puma
|
|
172
171
|
@thread_pool and @thread_pool.pool_capacity
|
173
172
|
end
|
174
173
|
|
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
174
|
# Runs the server.
|
277
175
|
#
|
278
176
|
# If +background+ is true (the default) then a thread is spun
|
@@ -286,15 +184,9 @@ module Puma
|
|
286
184
|
|
287
185
|
@status = :run
|
288
186
|
|
289
|
-
if @mode == :tcp
|
290
|
-
return run_lopez_mode(background)
|
291
|
-
end
|
292
|
-
|
293
|
-
queue_requests = @queue_requests
|
294
|
-
|
295
187
|
@thread_pool = ThreadPool.new(@min_threads,
|
296
188
|
@max_threads,
|
297
|
-
IOBuffer) do |client, buffer|
|
189
|
+
::Puma::IOBuffer) do |client, buffer|
|
298
190
|
|
299
191
|
# Advertise this server into the thread
|
300
192
|
Thread.current[ThreadLocalKey] = self
|
@@ -302,10 +194,10 @@ module Puma
|
|
302
194
|
process_now = false
|
303
195
|
|
304
196
|
begin
|
305
|
-
if queue_requests
|
197
|
+
if @queue_requests
|
306
198
|
process_now = client.eagerly_finish
|
307
199
|
else
|
308
|
-
client.finish
|
200
|
+
client.finish(@first_data_timeout)
|
309
201
|
process_now = true
|
310
202
|
end
|
311
203
|
rescue MiniSSL::SSLError => e
|
@@ -331,11 +223,14 @@ module Puma
|
|
331
223
|
@reactor.add client
|
332
224
|
end
|
333
225
|
end
|
226
|
+
|
227
|
+
process_now
|
334
228
|
end
|
335
229
|
|
230
|
+
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
336
231
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
337
232
|
|
338
|
-
if queue_requests
|
233
|
+
if @queue_requests
|
339
234
|
@reactor = Reactor.new self, @thread_pool
|
340
235
|
@reactor.run_in_thread
|
341
236
|
end
|
@@ -362,6 +257,7 @@ module Puma
|
|
362
257
|
end
|
363
258
|
|
364
259
|
def handle_servers
|
260
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
365
261
|
begin
|
366
262
|
check = @check
|
367
263
|
sockets = [check] + @binder.ios
|
@@ -386,6 +282,10 @@ module Puma
|
|
386
282
|
break if handle_check
|
387
283
|
else
|
388
284
|
begin
|
285
|
+
pool.wait_until_not_full
|
286
|
+
pool.wait_for_less_busy_worker(
|
287
|
+
@options[:wait_for_less_busy_worker].to_f)
|
288
|
+
|
389
289
|
if io = sock.accept_nonblock
|
390
290
|
client = Client.new io, @binder.env(sock)
|
391
291
|
if remote_addr_value
|
@@ -395,10 +295,6 @@ module Puma
|
|
395
295
|
end
|
396
296
|
|
397
297
|
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
298
|
end
|
403
299
|
rescue SystemCallError
|
404
300
|
# nothing
|
@@ -419,17 +315,20 @@ module Puma
|
|
419
315
|
|
420
316
|
@events.fire :state, @status
|
421
317
|
|
422
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
423
318
|
if queue_requests
|
319
|
+
@queue_requests = false
|
424
320
|
@reactor.clear!
|
425
321
|
@reactor.shutdown
|
426
322
|
end
|
323
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
427
324
|
rescue Exception => e
|
428
325
|
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
429
326
|
STDERR.puts e.backtrace
|
430
327
|
ensure
|
431
|
-
@check.close
|
328
|
+
@check.close unless @check.closed? # Ruby 2.2 issue
|
432
329
|
@notify.close
|
330
|
+
@notify = nil
|
331
|
+
@check = nil
|
433
332
|
end
|
434
333
|
|
435
334
|
@events.fire :state, :done
|
@@ -476,29 +375,24 @@ module Puma
|
|
476
375
|
close_socket = false
|
477
376
|
return
|
478
377
|
when true
|
479
|
-
return unless @queue_requests
|
480
378
|
buffer.reset
|
481
379
|
|
482
380
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
483
381
|
|
484
382
|
requests += 1
|
485
383
|
|
486
|
-
# Closing keepalive sockets after they've made a reasonable
|
487
|
-
# number of requests allows Puma to service many connections
|
488
|
-
# fairly, even when the number of concurrent connections exceeds
|
489
|
-
# the size of the threadpool. It also allows cluster mode Pumas
|
490
|
-
# to keep load evenly distributed across workers, because clients
|
491
|
-
# are randomly assigned a new worker when opening a new connection.
|
492
|
-
#
|
493
|
-
# Previously, Puma would kick connections in this conditional back
|
494
|
-
# to the reactor. However, because this causes the todo set to increase
|
495
|
-
# in size, the wait_until_full mutex would never unlock, leaving
|
496
|
-
# any additional connections unserviced.
|
497
|
-
break if requests >= MAX_FAST_INLINE
|
498
|
-
|
499
384
|
check_for_more_data = @status == :run
|
500
385
|
|
386
|
+
if requests >= MAX_FAST_INLINE
|
387
|
+
# This will mean that reset will only try to use the data it already
|
388
|
+
# has buffered and won't try to read more data. What this means is that
|
389
|
+
# every client, independent of their request speed, gets treated like a slow
|
390
|
+
# one once every MAX_FAST_INLINE requests.
|
391
|
+
check_for_more_data = false
|
392
|
+
end
|
393
|
+
|
501
394
|
unless client.reset(check_for_more_data)
|
395
|
+
return unless @queue_requests
|
502
396
|
close_socket = false
|
503
397
|
client.set_timeout @persistent_timeout
|
504
398
|
@reactor.add client
|
@@ -631,6 +525,8 @@ module Puma
|
|
631
525
|
#
|
632
526
|
# Finally, it'll return +true+ on keep-alive connections.
|
633
527
|
def handle_request(req, lines)
|
528
|
+
@requests_count +=1
|
529
|
+
|
634
530
|
env = req.env
|
635
531
|
client = req.io
|
636
532
|
|
@@ -677,37 +573,6 @@ module Puma
|
|
677
573
|
}
|
678
574
|
end
|
679
575
|
|
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
576
|
# A rack extension. If the app writes #call'ables to this
|
712
577
|
# array, we will invoke them when the request is done.
|
713
578
|
#
|
@@ -729,17 +594,14 @@ module Puma
|
|
729
594
|
return :async
|
730
595
|
end
|
731
596
|
rescue ThreadPool::ForceShutdown => e
|
732
|
-
@events.
|
733
|
-
@events.
|
734
|
-
|
735
|
-
status = 503
|
736
|
-
headers = {}
|
737
|
-
res_body = ["Request was internally terminated early\n"]
|
597
|
+
@events.unknown_error self, e, "Rack app", env
|
598
|
+
@events.log "Detected force shutdown of a thread"
|
738
599
|
|
600
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
739
601
|
rescue Exception => e
|
740
602
|
@events.unknown_error self, e, "Rack app", env
|
741
603
|
|
742
|
-
status, headers, res_body = lowlevel_error(e, env)
|
604
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
743
605
|
end
|
744
606
|
|
745
607
|
content_length = nil
|
@@ -754,10 +616,10 @@ module Puma
|
|
754
616
|
line_ending = LINE_END
|
755
617
|
colon = COLON
|
756
618
|
|
757
|
-
http_11 =
|
619
|
+
http_11 = env[HTTP_VERSION] == HTTP_11
|
620
|
+
if http_11
|
758
621
|
allow_chunked = true
|
759
622
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
760
|
-
include_keepalive_header = false
|
761
623
|
|
762
624
|
# An optimization. The most common response is 200, so we can
|
763
625
|
# reply with the proper 200 status without having to compute
|
@@ -771,11 +633,9 @@ module Puma
|
|
771
633
|
|
772
634
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
773
635
|
end
|
774
|
-
true
|
775
636
|
else
|
776
637
|
allow_chunked = false
|
777
638
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
778
|
-
include_keepalive_header = keep_alive
|
779
639
|
|
780
640
|
# Same optimization as above for HTTP/1.1
|
781
641
|
#
|
@@ -787,9 +647,12 @@ module Puma
|
|
787
647
|
|
788
648
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
789
649
|
end
|
790
|
-
false
|
791
650
|
end
|
792
651
|
|
652
|
+
# regardless of what the client wants, we always close the connection
|
653
|
+
# if running without request queueing
|
654
|
+
keep_alive &&= @queue_requests
|
655
|
+
|
793
656
|
response_hijack = nil
|
794
657
|
|
795
658
|
headers.each do |k, vs|
|
@@ -816,10 +679,15 @@ module Puma
|
|
816
679
|
end
|
817
680
|
end
|
818
681
|
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
682
|
+
# HTTP/1.1 & 1.0 assume different defaults:
|
683
|
+
# - HTTP 1.0 assumes the connection will be closed if not specified
|
684
|
+
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
685
|
+
# Only set the header if we're doing something which is not the default
|
686
|
+
# for this protocol version
|
687
|
+
if http_11
|
688
|
+
lines << CONNECTION_CLOSE if !keep_alive
|
689
|
+
else
|
690
|
+
lines << CONNECTION_KEEP_ALIVE if keep_alive
|
823
691
|
end
|
824
692
|
|
825
693
|
if no_body
|
@@ -946,19 +814,21 @@ module Puma
|
|
946
814
|
|
947
815
|
# A fallback rack response if +@app+ raises as exception.
|
948
816
|
#
|
949
|
-
def lowlevel_error(e, env)
|
817
|
+
def lowlevel_error(e, env, status=500)
|
950
818
|
if handler = @options[:lowlevel_error_handler]
|
951
819
|
if handler.arity == 1
|
952
820
|
return handler.call(e)
|
953
|
-
|
821
|
+
elsif handler.arity == 2
|
954
822
|
return handler.call(e, env)
|
823
|
+
else
|
824
|
+
return handler.call(e, env, status)
|
955
825
|
end
|
956
826
|
end
|
957
827
|
|
958
828
|
if @leak_stack_on_error
|
959
|
-
[
|
829
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
960
830
|
else
|
961
|
-
[
|
831
|
+
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
962
832
|
end
|
963
833
|
end
|
964
834
|
|
@@ -1016,6 +886,7 @@ module Puma
|
|
1016
886
|
end
|
1017
887
|
|
1018
888
|
def notify_safely(message)
|
889
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
1019
890
|
begin
|
1020
891
|
@notify << message
|
1021
892
|
rescue IOError
|
@@ -1045,8 +916,9 @@ module Puma
|
|
1045
916
|
@thread.join if @thread && sync
|
1046
917
|
end
|
1047
918
|
|
1048
|
-
def begin_restart
|
919
|
+
def begin_restart(sync=false)
|
1049
920
|
notify_safely(RESTART_COMMAND)
|
921
|
+
@thread.join if @thread && sync
|
1050
922
|
end
|
1051
923
|
|
1052
924
|
def fast_write(io, str)
|
@@ -1084,5 +956,13 @@ module Puma
|
|
1084
956
|
HTTP_INJECTION_REGEX =~ header_value.to_s
|
1085
957
|
end
|
1086
958
|
private :possible_header_injection?
|
959
|
+
|
960
|
+
# List of methods invoked by #stats.
|
961
|
+
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
962
|
+
|
963
|
+
# Returns a hash of stats about the running server for reporting purposes.
|
964
|
+
def stats
|
965
|
+
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
966
|
+
end
|
1087
967
|
end
|
1088
968
|
end
|