puma 3.0.0.rc1 → 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 +5 -5
- data/{History.txt → History.md} +703 -70
- data/LICENSE +23 -20
- data/README.md +173 -163
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
- data/docs/fork_worker.md +31 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +13 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/{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 +1 -1
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +57 -3
- data/docs/systemd.md +228 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +16 -0
- data/ext/puma_http11/http11_parser.c +287 -468
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +159 -10
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/ext/puma_http11/puma_http11.c +6 -38
- data/lib/puma.rb +25 -5
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +53 -26
- data/lib/puma/binder.rb +150 -119
- data/lib/puma/cli.rb +56 -38
- data/lib/puma/client.rb +277 -80
- data/lib/puma/cluster.rb +326 -130
- data/lib/puma/commonlogger.rb +21 -20
- data/lib/puma/configuration.rb +160 -161
- data/lib/puma/const.rb +50 -47
- data/lib/puma/control_cli.rb +104 -63
- data/lib/puma/detect.rb +13 -1
- data/lib/puma/dsl.rb +463 -114
- data/lib/puma/events.rb +22 -13
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/launcher.rb +195 -105
- data/lib/puma/minissl.rb +110 -4
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +9 -14
- data/lib/puma/plugin.rb +32 -12
- data/lib/puma/plugin/tmp_restart.rb +19 -6
- data/lib/puma/rack/builder.rb +7 -5
- data/lib/puma/rack/urlmap.rb +11 -8
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +242 -32
- data/lib/puma/runner.rb +41 -30
- data/lib/puma/server.rb +265 -183
- data/lib/puma/single.rb +22 -63
- data/lib/puma/state_file.rb +9 -2
- data/lib/puma/thread_pool.rb +179 -68
- data/lib/puma/util.rb +3 -11
- data/lib/rack/handler/puma.rb +60 -11
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +1 -2
- metadata +35 -99
- data/COPYING +0 -55
- data/Gemfile +0 -13
- data/Manifest.txt +0 -79
- data/Rakefile +0 -158
- data/docs/config.md +0 -0
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/capistrano.rb +0 -94
- data/lib/puma/compat.rb +0 -18
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_18.rb +0 -56
- data/lib/puma/rack/backports/uri/common_192.rb +0 -52
- data/lib/puma/rack/backports/uri/common_193.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -32
- data/puma.gemspec +0 -52
- data/tools/jungle/README.md +0 -9
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -394
- data/tools/jungle/init.d/run-puma +0 -3
data/lib/puma/runner.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'puma/server'
|
4
|
+
require 'puma/const'
|
5
|
+
require 'puma/minissl/context_builder'
|
6
|
+
|
1
7
|
module Puma
|
8
|
+
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
9
|
+
# serve requests. This class spawns a new instance of `Puma::Server` via
|
10
|
+
# a call to `start_server`.
|
2
11
|
class Runner
|
3
12
|
def initialize(cli, events)
|
4
13
|
@launcher = cli
|
@@ -6,16 +15,17 @@ module Puma
|
|
6
15
|
@options = cli.options
|
7
16
|
@app = nil
|
8
17
|
@control = nil
|
9
|
-
|
10
|
-
|
11
|
-
def daemon?
|
12
|
-
@options[:daemon]
|
18
|
+
@started_at = Time.now
|
13
19
|
end
|
14
20
|
|
15
21
|
def development?
|
16
22
|
@options[:environment] == "development"
|
17
23
|
end
|
18
24
|
|
25
|
+
def test?
|
26
|
+
@options[:environment] == "test"
|
27
|
+
end
|
28
|
+
|
19
29
|
def log(str)
|
20
30
|
@events.log str
|
21
31
|
end
|
@@ -38,41 +48,35 @@ module Puma
|
|
38
48
|
|
39
49
|
require 'puma/app/status'
|
40
50
|
|
41
|
-
uri = URI.parse str
|
42
|
-
|
43
|
-
app = Puma::App::Status.new @launcher
|
44
|
-
|
45
51
|
if token = @options[:control_auth_token]
|
46
|
-
|
52
|
+
token = nil if token.empty? || token == 'none'
|
47
53
|
end
|
48
54
|
|
55
|
+
app = Puma::App::Status.new @launcher, token
|
56
|
+
|
49
57
|
control = Puma::Server.new app, @launcher.events
|
50
58
|
control.min_threads = 0
|
51
59
|
control.max_threads = 1
|
52
60
|
|
53
|
-
|
54
|
-
when "tcp"
|
55
|
-
log "* Starting control server on #{str}"
|
56
|
-
control.add_tcp_listener uri.host, uri.port
|
57
|
-
when "unix"
|
58
|
-
log "* Starting control server on #{str}"
|
59
|
-
path = "#{uri.host}#{uri.path}"
|
60
|
-
mask = @options[:control_url_umask]
|
61
|
-
|
62
|
-
control.add_unix_listener path, mask
|
63
|
-
else
|
64
|
-
error "Invalid control URI: #{str}"
|
65
|
-
end
|
61
|
+
control.binder.parse [str], self, 'Starting control server'
|
66
62
|
|
67
63
|
control.run
|
68
64
|
@control = control
|
69
65
|
end
|
70
66
|
|
67
|
+
def close_control_listeners
|
68
|
+
@control.binder.close_listeners if @control
|
69
|
+
end
|
70
|
+
|
71
71
|
def ruby_engine
|
72
72
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
73
73
|
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
74
74
|
else
|
75
|
-
|
75
|
+
if defined?(RUBY_ENGINE_VERSION)
|
76
|
+
"#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
|
77
|
+
else
|
78
|
+
"#{RUBY_ENGINE} #{RUBY_VERSION}"
|
79
|
+
end
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
@@ -84,10 +88,10 @@ module Puma
|
|
84
88
|
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
85
89
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
86
90
|
log "* Environment: #{ENV['RACK_ENV']}"
|
91
|
+
end
|
87
92
|
|
88
|
-
|
89
|
-
|
90
|
-
end
|
93
|
+
def redirected_io?
|
94
|
+
@options[:redirect_stdout] || @options[:redirect_stderr]
|
91
95
|
end
|
92
96
|
|
93
97
|
def redirect_io
|
@@ -96,12 +100,20 @@ module Puma
|
|
96
100
|
append = @options[:redirect_append]
|
97
101
|
|
98
102
|
if stdout
|
103
|
+
unless Dir.exist?(File.dirname(stdout))
|
104
|
+
raise "Cannot redirect STDOUT to #{stdout}"
|
105
|
+
end
|
106
|
+
|
99
107
|
STDOUT.reopen stdout, (append ? "a" : "w")
|
100
108
|
STDOUT.sync = true
|
101
109
|
STDOUT.puts "=== puma startup: #{Time.now} ==="
|
102
110
|
end
|
103
111
|
|
104
112
|
if stderr
|
113
|
+
unless Dir.exist?(File.dirname(stderr))
|
114
|
+
raise "Cannot redirect STDERR to #{stderr}"
|
115
|
+
end
|
116
|
+
|
105
117
|
STDERR.reopen stderr, (append ? "a" : "w")
|
106
118
|
STDERR.sync = true
|
107
119
|
STDERR.puts "=== puma startup: #{Time.now} ==="
|
@@ -114,7 +126,6 @@ module Puma
|
|
114
126
|
exit 1
|
115
127
|
end
|
116
128
|
|
117
|
-
# Load the app before we daemonize.
|
118
129
|
begin
|
119
130
|
@app = @launcher.config.app
|
120
131
|
rescue Exception => e
|
@@ -138,11 +149,11 @@ module Puma
|
|
138
149
|
server.max_threads = max_t
|
139
150
|
server.inherit_binder @launcher.binder
|
140
151
|
|
141
|
-
if @options[:
|
142
|
-
server.
|
152
|
+
if @options[:early_hints]
|
153
|
+
server.early_hints = true
|
143
154
|
end
|
144
155
|
|
145
|
-
unless development?
|
156
|
+
unless development? || test?
|
146
157
|
server.leak_stack_on_error = false
|
147
158
|
end
|
148
159
|
|
data/lib/puma/server.rb
CHANGED
@@ -1,35 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'stringio'
|
2
4
|
|
3
5
|
require 'puma/thread_pool'
|
4
6
|
require 'puma/const'
|
5
7
|
require 'puma/events'
|
6
8
|
require 'puma/null_io'
|
7
|
-
require 'puma/compat'
|
8
9
|
require 'puma/reactor'
|
9
10
|
require 'puma/client'
|
10
11
|
require 'puma/binder'
|
11
|
-
require 'puma/delegation'
|
12
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
|
|
17
|
-
unless Puma.const_defined? "IOBuffer"
|
18
|
-
require 'puma/io_buffer'
|
19
|
-
end
|
20
|
-
|
21
18
|
require 'socket'
|
19
|
+
require 'forwardable'
|
22
20
|
|
23
21
|
module Puma
|
24
22
|
|
25
23
|
# The HTTP Server itself. Serves out a single Rack app.
|
24
|
+
#
|
25
|
+
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
26
|
+
# to generate one or more `Puma::Server` instances capable of handling requests.
|
27
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
28
|
+
#
|
29
|
+
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
30
|
+
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
31
|
+
#
|
32
|
+
# Each `Puma::Server` will have one reactor and one thread pool.
|
26
33
|
class Server
|
27
34
|
|
28
35
|
include Puma::Const
|
29
|
-
extend
|
36
|
+
extend Forwardable
|
30
37
|
|
31
38
|
attr_reader :thread
|
32
39
|
attr_reader :events
|
40
|
+
attr_reader :requests_count
|
33
41
|
attr_accessor :app
|
34
42
|
|
35
43
|
attr_accessor :min_threads
|
@@ -51,24 +59,22 @@ module Puma
|
|
51
59
|
@app = app
|
52
60
|
@events = events
|
53
61
|
|
54
|
-
@check, @notify =
|
55
|
-
|
62
|
+
@check, @notify = nil
|
56
63
|
@status = :stop
|
57
64
|
|
58
65
|
@min_threads = 0
|
59
66
|
@max_threads = 16
|
60
|
-
@auto_trim_time =
|
67
|
+
@auto_trim_time = 30
|
61
68
|
@reaping_time = 1
|
62
69
|
|
63
70
|
@thread = nil
|
64
71
|
@thread_pool = nil
|
72
|
+
@early_hints = nil
|
65
73
|
|
66
|
-
@persistent_timeout = PERSISTENT_TIMEOUT
|
74
|
+
@persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
|
75
|
+
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
67
76
|
|
68
77
|
@binder = Binder.new(events)
|
69
|
-
@own_binder = true
|
70
|
-
|
71
|
-
@first_data_timeout = FIRST_DATA_TIMEOUT
|
72
78
|
|
73
79
|
@leak_stack_on_error = true
|
74
80
|
|
@@ -78,28 +84,26 @@ module Puma
|
|
78
84
|
ENV['RACK_ENV'] ||= "development"
|
79
85
|
|
80
86
|
@mode = :http
|
87
|
+
|
88
|
+
@precheck_closing = true
|
89
|
+
|
90
|
+
@requests_count = 0
|
81
91
|
end
|
82
92
|
|
83
|
-
attr_accessor :binder, :leak_stack_on_error
|
93
|
+
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
84
94
|
|
85
|
-
|
86
|
-
forward :add_ssl_listener, :@binder
|
87
|
-
forward :add_unix_listener, :@binder
|
88
|
-
forward :connected_port, :@binder
|
95
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
|
89
96
|
|
90
97
|
def inherit_binder(bind)
|
91
98
|
@binder = bind
|
92
|
-
@own_binder = false
|
93
|
-
end
|
94
|
-
|
95
|
-
def tcp_mode!
|
96
|
-
@mode = :tcp
|
97
99
|
end
|
98
100
|
|
99
101
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
100
102
|
# packetizes our stream. This improves both latency and throughput.
|
101
103
|
#
|
102
104
|
if RUBY_PLATFORM =~ /linux/
|
105
|
+
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
106
|
+
|
103
107
|
# 6 == Socket::IPPROTO_TCP
|
104
108
|
# 3 == TCP_CORK
|
105
109
|
# 1/0 == turn on/off
|
@@ -107,6 +111,7 @@ module Puma
|
|
107
111
|
begin
|
108
112
|
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
109
113
|
rescue IOError, SystemCallError
|
114
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
110
115
|
end
|
111
116
|
end
|
112
117
|
|
@@ -114,6 +119,24 @@ module Puma
|
|
114
119
|
begin
|
115
120
|
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
116
121
|
rescue IOError, SystemCallError
|
122
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def closed_socket?(socket)
|
127
|
+
return false unless socket.kind_of? TCPSocket
|
128
|
+
return false unless @precheck_closing
|
129
|
+
|
130
|
+
begin
|
131
|
+
tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
|
132
|
+
rescue IOError, SystemCallError
|
133
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
134
|
+
@precheck_closing = false
|
135
|
+
false
|
136
|
+
else
|
137
|
+
state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
|
138
|
+
# TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
|
139
|
+
(state >= 6 && state <= 9) || state == 11
|
117
140
|
end
|
118
141
|
end
|
119
142
|
else
|
@@ -122,6 +145,10 @@ module Puma
|
|
122
145
|
|
123
146
|
def uncork_socket(socket)
|
124
147
|
end
|
148
|
+
|
149
|
+
def closed_socket?(socket)
|
150
|
+
false
|
151
|
+
end
|
125
152
|
end
|
126
153
|
|
127
154
|
def backlog
|
@@ -132,94 +159,18 @@ module Puma
|
|
132
159
|
@thread_pool and @thread_pool.spawned
|
133
160
|
end
|
134
161
|
|
135
|
-
# Lopez Mode == raw tcp apps
|
136
162
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# Set unix socket addrs to localhost
|
147
|
-
addr = "127.0.0.1:0"
|
148
|
-
else
|
149
|
-
addr = "#{addr}:#{io.peeraddr[1]}"
|
150
|
-
end
|
151
|
-
|
152
|
-
env = { 'thread' => tl, REMOTE_ADDR => addr }
|
153
|
-
|
154
|
-
begin
|
155
|
-
@app.call env, client.to_io
|
156
|
-
rescue Object => e
|
157
|
-
STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
|
158
|
-
STDERR.puts e.backtrace
|
159
|
-
end
|
160
|
-
|
161
|
-
client.close unless env['detach']
|
162
|
-
end
|
163
|
-
|
164
|
-
@events.fire :state, :running
|
165
|
-
|
166
|
-
if background
|
167
|
-
@thread = Thread.new { handle_servers_lopez_mode }
|
168
|
-
return @thread
|
169
|
-
else
|
170
|
-
handle_servers_lopez_mode
|
171
|
-
end
|
163
|
+
# This number represents the number of requests that
|
164
|
+
# the server is capable of taking right now.
|
165
|
+
#
|
166
|
+
# For example if the number is 5 then it means
|
167
|
+
# there are 5 threads sitting idle ready to take
|
168
|
+
# a request. If one request comes in, then the
|
169
|
+
# value would be 4 until it finishes processing.
|
170
|
+
def pool_capacity
|
171
|
+
@thread_pool and @thread_pool.pool_capacity
|
172
172
|
end
|
173
173
|
|
174
|
-
def handle_servers_lopez_mode
|
175
|
-
begin
|
176
|
-
check = @check
|
177
|
-
sockets = [check] + @binder.ios
|
178
|
-
pool = @thread_pool
|
179
|
-
|
180
|
-
while @status == :run
|
181
|
-
begin
|
182
|
-
ios = IO.select sockets
|
183
|
-
ios.first.each do |sock|
|
184
|
-
if sock == check
|
185
|
-
break if handle_check
|
186
|
-
else
|
187
|
-
begin
|
188
|
-
if io = sock.accept_nonblock
|
189
|
-
client = Client.new io, nil
|
190
|
-
pool << client
|
191
|
-
end
|
192
|
-
rescue SystemCallError
|
193
|
-
# nothing
|
194
|
-
rescue Errno::ECONNABORTED
|
195
|
-
# client closed the socket even before accept
|
196
|
-
io.close rescue nil
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
rescue Object => e
|
201
|
-
@events.unknown_error self, e, "Listen loop"
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
@events.fire :state, @status
|
206
|
-
|
207
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
208
|
-
|
209
|
-
rescue Exception => e
|
210
|
-
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
211
|
-
STDERR.puts e.backtrace
|
212
|
-
ensure
|
213
|
-
@check.close
|
214
|
-
@notify.close
|
215
|
-
|
216
|
-
if @status != :restart and @own_binder
|
217
|
-
@binder.close
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
@events.fire :state, :done
|
222
|
-
end
|
223
174
|
# Runs the server.
|
224
175
|
#
|
225
176
|
# If +background+ is true (the default) then a thread is spun
|
@@ -233,22 +184,20 @@ module Puma
|
|
233
184
|
|
234
185
|
@status = :run
|
235
186
|
|
236
|
-
if @mode == :tcp
|
237
|
-
return run_lopez_mode(background)
|
238
|
-
end
|
239
|
-
|
240
|
-
queue_requests = @queue_requests
|
241
|
-
|
242
187
|
@thread_pool = ThreadPool.new(@min_threads,
|
243
188
|
@max_threads,
|
244
|
-
IOBuffer) do |client, buffer|
|
189
|
+
::Puma::IOBuffer) do |client, buffer|
|
190
|
+
|
191
|
+
# Advertise this server into the thread
|
192
|
+
Thread.current[ThreadLocalKey] = self
|
193
|
+
|
245
194
|
process_now = false
|
246
195
|
|
247
196
|
begin
|
248
|
-
if queue_requests
|
197
|
+
if @queue_requests
|
249
198
|
process_now = client.eagerly_finish
|
250
199
|
else
|
251
|
-
client.finish
|
200
|
+
client.finish(@first_data_timeout)
|
252
201
|
process_now = true
|
253
202
|
end
|
254
203
|
rescue MiniSSL::SSLError => e
|
@@ -260,11 +209,11 @@ module Puma
|
|
260
209
|
|
261
210
|
@events.ssl_error self, addr, cert, e
|
262
211
|
rescue HttpParserError => e
|
263
|
-
client.
|
212
|
+
client.write_error(400)
|
264
213
|
client.close
|
265
214
|
|
266
215
|
@events.parse_error self, client.env, e
|
267
|
-
rescue ConnectionError
|
216
|
+
rescue ConnectionError, EOFError
|
268
217
|
client.close
|
269
218
|
else
|
270
219
|
if process_now
|
@@ -274,11 +223,14 @@ module Puma
|
|
274
223
|
@reactor.add client
|
275
224
|
end
|
276
225
|
end
|
226
|
+
|
227
|
+
process_now
|
277
228
|
end
|
278
229
|
|
230
|
+
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
279
231
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
280
232
|
|
281
|
-
if queue_requests
|
233
|
+
if @queue_requests
|
282
234
|
@reactor = Reactor.new self, @thread_pool
|
283
235
|
@reactor.run_in_thread
|
284
236
|
end
|
@@ -294,7 +246,10 @@ module Puma
|
|
294
246
|
@events.fire :state, :running
|
295
247
|
|
296
248
|
if background
|
297
|
-
@thread = Thread.new
|
249
|
+
@thread = Thread.new do
|
250
|
+
Puma.set_thread_name "server"
|
251
|
+
handle_servers
|
252
|
+
end
|
298
253
|
return @thread
|
299
254
|
else
|
300
255
|
handle_servers
|
@@ -302,6 +257,7 @@ module Puma
|
|
302
257
|
end
|
303
258
|
|
304
259
|
def handle_servers
|
260
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
305
261
|
begin
|
306
262
|
check = @check
|
307
263
|
sockets = [check] + @binder.ios
|
@@ -326,6 +282,10 @@ module Puma
|
|
326
282
|
break if handle_check
|
327
283
|
else
|
328
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
|
+
|
329
289
|
if io = sock.accept_nonblock
|
330
290
|
client = Client.new io, @binder.env(sock)
|
331
291
|
if remote_addr_value
|
@@ -335,13 +295,16 @@ module Puma
|
|
335
295
|
end
|
336
296
|
|
337
297
|
pool << client
|
338
|
-
pool.wait_until_not_full unless queue_requests
|
339
298
|
end
|
340
299
|
rescue SystemCallError
|
341
300
|
# nothing
|
342
301
|
rescue Errno::ECONNABORTED
|
343
302
|
# client closed the socket even before accept
|
344
|
-
|
303
|
+
begin
|
304
|
+
io.close
|
305
|
+
rescue
|
306
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
307
|
+
end
|
345
308
|
end
|
346
309
|
end
|
347
310
|
end
|
@@ -352,21 +315,20 @@ module Puma
|
|
352
315
|
|
353
316
|
@events.fire :state, @status
|
354
317
|
|
355
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
356
318
|
if queue_requests
|
357
|
-
@
|
319
|
+
@queue_requests = false
|
320
|
+
@reactor.clear!
|
358
321
|
@reactor.shutdown
|
359
322
|
end
|
323
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
360
324
|
rescue Exception => e
|
361
325
|
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
362
326
|
STDERR.puts e.backtrace
|
363
327
|
ensure
|
364
|
-
@check.close
|
328
|
+
@check.close unless @check.closed? # Ruby 2.2 issue
|
365
329
|
@notify.close
|
366
|
-
|
367
|
-
|
368
|
-
@binder.close
|
369
|
-
end
|
330
|
+
@notify = nil
|
331
|
+
@check = nil
|
370
332
|
end
|
371
333
|
|
372
334
|
@events.fire :state, :done
|
@@ -399,8 +361,12 @@ module Puma
|
|
399
361
|
#
|
400
362
|
def process_client(client, buffer)
|
401
363
|
begin
|
364
|
+
|
365
|
+
clean_thread_locals = @options[:clean_thread_locals]
|
402
366
|
close_socket = true
|
403
367
|
|
368
|
+
requests = 0
|
369
|
+
|
404
370
|
while true
|
405
371
|
case handle_request(client, buffer)
|
406
372
|
when false
|
@@ -409,10 +375,24 @@ module Puma
|
|
409
375
|
close_socket = false
|
410
376
|
return
|
411
377
|
when true
|
412
|
-
return unless @queue_requests
|
413
378
|
buffer.reset
|
414
379
|
|
415
|
-
|
380
|
+
ThreadPool.clean_thread_locals if clean_thread_locals
|
381
|
+
|
382
|
+
requests += 1
|
383
|
+
|
384
|
+
check_for_more_data = @status == :run
|
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
|
+
|
394
|
+
unless client.reset(check_for_more_data)
|
395
|
+
return unless @queue_requests
|
416
396
|
close_socket = false
|
417
397
|
client.set_timeout @persistent_timeout
|
418
398
|
@reactor.add client
|
@@ -427,6 +407,8 @@ module Puma
|
|
427
407
|
|
428
408
|
# SSL handshake error
|
429
409
|
rescue MiniSSL::SSLError => e
|
410
|
+
lowlevel_error(e, client.env)
|
411
|
+
|
430
412
|
ssl_socket = client.io
|
431
413
|
addr = ssl_socket.peeraddr.last
|
432
414
|
cert = ssl_socket.peercert
|
@@ -437,13 +419,17 @@ module Puma
|
|
437
419
|
|
438
420
|
# The client doesn't know HTTP well
|
439
421
|
rescue HttpParserError => e
|
440
|
-
client.
|
422
|
+
lowlevel_error(e, client.env)
|
423
|
+
|
424
|
+
client.write_error(400)
|
441
425
|
|
442
426
|
@events.parse_error self, client.env, e
|
443
427
|
|
444
428
|
# Server error
|
445
429
|
rescue StandardError => e
|
446
|
-
client.
|
430
|
+
lowlevel_error(e, client.env)
|
431
|
+
|
432
|
+
client.write_error(500)
|
447
433
|
|
448
434
|
@events.unknown_error self, e, "Read"
|
449
435
|
|
@@ -453,6 +439,7 @@ module Puma
|
|
453
439
|
begin
|
454
440
|
client.close if close_socket
|
455
441
|
rescue IOError, SystemCallError
|
442
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
456
443
|
# Already closed
|
457
444
|
rescue StandardError => e
|
458
445
|
@events.unknown_error self, e, "Client"
|
@@ -483,6 +470,10 @@ module Puma
|
|
483
470
|
env[REQUEST_PATH] = uri.path
|
484
471
|
|
485
472
|
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
473
|
+
|
474
|
+
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
475
|
+
# so only set the env value if there actually is a value.
|
476
|
+
env[QUERY_STRING] = uri.query if uri.query
|
486
477
|
end
|
487
478
|
|
488
479
|
env[PATH_INFO] = env[REQUEST_PATH]
|
@@ -513,23 +504,34 @@ module Puma
|
|
513
504
|
end
|
514
505
|
|
515
506
|
def default_server_port(env)
|
516
|
-
|
517
|
-
|
507
|
+
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"
|
508
|
+
PORT_443
|
509
|
+
else
|
510
|
+
PORT_80
|
511
|
+
end
|
518
512
|
end
|
519
513
|
|
520
|
-
#
|
521
|
-
#
|
522
|
-
#
|
523
|
-
# +
|
514
|
+
# Takes the request +req+, invokes the Rack application to construct
|
515
|
+
# the response and writes it back to +req.io+.
|
516
|
+
#
|
517
|
+
# The second parameter +lines+ is a IO-like object unique to this thread.
|
518
|
+
# This is normally an instance of Puma::IOBuffer.
|
519
|
+
#
|
520
|
+
# It'll return +false+ when the connection is closed, this doesn't mean
|
521
|
+
# that the response wasn't successful.
|
524
522
|
#
|
525
|
-
# +
|
526
|
-
#
|
527
|
-
# it up again.
|
523
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
524
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
528
525
|
#
|
526
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
529
527
|
def handle_request(req, lines)
|
528
|
+
@requests_count +=1
|
529
|
+
|
530
530
|
env = req.env
|
531
531
|
client = req.io
|
532
532
|
|
533
|
+
return false if closed_socket?(client)
|
534
|
+
|
533
535
|
normalize_env env, req
|
534
536
|
|
535
537
|
env[PUMA_SOCKET] = client
|
@@ -546,7 +548,30 @@ module Puma
|
|
546
548
|
head = env[REQUEST_METHOD] == HEAD
|
547
549
|
|
548
550
|
env[RACK_INPUT] = body
|
549
|
-
env[RACK_URL_SCHEME] =
|
551
|
+
env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
552
|
+
|
553
|
+
if @early_hints
|
554
|
+
env[EARLY_HINTS] = lambda { |headers|
|
555
|
+
begin
|
556
|
+
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
557
|
+
|
558
|
+
headers.each_pair do |k, vs|
|
559
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
560
|
+
vs.to_s.split(NEWLINE).each do |v|
|
561
|
+
next if possible_header_injection?(v)
|
562
|
+
fast_write client, "#{k}: #{v}\r\n"
|
563
|
+
end
|
564
|
+
else
|
565
|
+
fast_write client, "#{k}: #{vs}\r\n"
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
fast_write client, "\r\n".freeze
|
570
|
+
rescue ConnectionError
|
571
|
+
# noop, if we lost the socket we just won't send the early hints
|
572
|
+
end
|
573
|
+
}
|
574
|
+
end
|
550
575
|
|
551
576
|
# A rack extension. If the app writes #call'ables to this
|
552
577
|
# array, we will invoke them when the request is done.
|
@@ -568,10 +593,15 @@ module Puma
|
|
568
593
|
|
569
594
|
return :async
|
570
595
|
end
|
571
|
-
rescue
|
572
|
-
@events.unknown_error self, e, "Rack app"
|
596
|
+
rescue ThreadPool::ForceShutdown => e
|
597
|
+
@events.unknown_error self, e, "Rack app", env
|
598
|
+
@events.log "Detected force shutdown of a thread"
|
599
|
+
|
600
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
601
|
+
rescue Exception => e
|
602
|
+
@events.unknown_error self, e, "Rack app", env
|
573
603
|
|
574
|
-
status, headers, res_body = lowlevel_error(e)
|
604
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
575
605
|
end
|
576
606
|
|
577
607
|
content_length = nil
|
@@ -586,10 +616,10 @@ module Puma
|
|
586
616
|
line_ending = LINE_END
|
587
617
|
colon = COLON
|
588
618
|
|
589
|
-
http_11 =
|
619
|
+
http_11 = env[HTTP_VERSION] == HTTP_11
|
620
|
+
if http_11
|
590
621
|
allow_chunked = true
|
591
622
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
592
|
-
include_keepalive_header = false
|
593
623
|
|
594
624
|
# An optimization. The most common response is 200, so we can
|
595
625
|
# reply with the proper 200 status without having to compute
|
@@ -603,11 +633,9 @@ module Puma
|
|
603
633
|
|
604
634
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
605
635
|
end
|
606
|
-
true
|
607
636
|
else
|
608
637
|
allow_chunked = false
|
609
638
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
610
|
-
include_keepalive_header = keep_alive
|
611
639
|
|
612
640
|
# Same optimization as above for HTTP/1.1
|
613
641
|
#
|
@@ -619,14 +647,18 @@ module Puma
|
|
619
647
|
|
620
648
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
621
649
|
end
|
622
|
-
false
|
623
650
|
end
|
624
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
|
+
|
625
656
|
response_hijack = nil
|
626
657
|
|
627
658
|
headers.each do |k, vs|
|
628
659
|
case k.downcase
|
629
660
|
when CONTENT_LENGTH2
|
661
|
+
next if possible_header_injection?(vs)
|
630
662
|
content_length = vs
|
631
663
|
next
|
632
664
|
when TRANSFER_ENCODING
|
@@ -637,8 +669,9 @@ module Puma
|
|
637
669
|
next
|
638
670
|
end
|
639
671
|
|
640
|
-
if vs.respond_to?(:to_s)
|
672
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
641
673
|
vs.to_s.split(NEWLINE).each do |v|
|
674
|
+
next if possible_header_injection?(v)
|
642
675
|
lines.append k, colon, v, line_ending
|
643
676
|
end
|
644
677
|
else
|
@@ -646,10 +679,15 @@ module Puma
|
|
646
679
|
end
|
647
680
|
end
|
648
681
|
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
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
|
653
691
|
end
|
654
692
|
|
655
693
|
if no_body
|
@@ -681,8 +719,8 @@ module Puma
|
|
681
719
|
|
682
720
|
begin
|
683
721
|
res_body.each do |part|
|
722
|
+
next if part.bytesize.zero?
|
684
723
|
if chunked
|
685
|
-
next if part.bytesize.zero?
|
686
724
|
fast_write client, part.bytesize.to_s(16)
|
687
725
|
fast_write client, line_ending
|
688
726
|
fast_write client, part
|
@@ -720,7 +758,7 @@ module Puma
|
|
720
758
|
end
|
721
759
|
private :fetch_status_code
|
722
760
|
|
723
|
-
# Given the
|
761
|
+
# Given the request +env+ from +client+ and the partial body +body+
|
724
762
|
# plus a potential Content-Length value +cl+, finish reading
|
725
763
|
# the body and return it.
|
726
764
|
#
|
@@ -776,15 +814,21 @@ module Puma
|
|
776
814
|
|
777
815
|
# A fallback rack response if +@app+ raises as exception.
|
778
816
|
#
|
779
|
-
def lowlevel_error(e)
|
817
|
+
def lowlevel_error(e, env, status=500)
|
780
818
|
if handler = @options[:lowlevel_error_handler]
|
781
|
-
|
819
|
+
if handler.arity == 1
|
820
|
+
return handler.call(e)
|
821
|
+
elsif handler.arity == 2
|
822
|
+
return handler.call(e, env)
|
823
|
+
else
|
824
|
+
return handler.call(e, env, status)
|
825
|
+
end
|
782
826
|
end
|
783
827
|
|
784
828
|
if @leak_stack_on_error
|
785
|
-
[
|
829
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
786
830
|
else
|
787
|
-
[
|
831
|
+
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
788
832
|
end
|
789
833
|
end
|
790
834
|
|
@@ -828,38 +872,53 @@ module Puma
|
|
828
872
|
@events.debug "Drained #{count} additional connections."
|
829
873
|
end
|
830
874
|
|
831
|
-
@
|
875
|
+
if @status != :restart
|
876
|
+
@binder.close
|
877
|
+
end
|
878
|
+
|
879
|
+
if @thread_pool
|
880
|
+
if timeout = @options[:force_shutdown_after]
|
881
|
+
@thread_pool.shutdown timeout.to_i
|
882
|
+
else
|
883
|
+
@thread_pool.shutdown
|
884
|
+
end
|
885
|
+
end
|
832
886
|
end
|
833
887
|
|
834
|
-
|
835
|
-
|
836
|
-
#
|
837
|
-
def stop(sync=false)
|
888
|
+
def notify_safely(message)
|
889
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
838
890
|
begin
|
839
|
-
@notify <<
|
891
|
+
@notify << message
|
840
892
|
rescue IOError
|
841
|
-
|
893
|
+
# The server, in another thread, is shutting down
|
894
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
895
|
+
rescue RuntimeError => e
|
896
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
897
|
+
if e.message.include?('IOError')
|
898
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
899
|
+
else
|
900
|
+
raise e
|
901
|
+
end
|
842
902
|
end
|
903
|
+
end
|
904
|
+
private :notify_safely
|
843
905
|
|
906
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
907
|
+
# off the request queue before finally exiting.
|
908
|
+
|
909
|
+
def stop(sync=false)
|
910
|
+
notify_safely(STOP_COMMAND)
|
844
911
|
@thread.join if @thread && sync
|
845
912
|
end
|
846
913
|
|
847
914
|
def halt(sync=false)
|
848
|
-
|
849
|
-
@notify << HALT_COMMAND
|
850
|
-
rescue IOError
|
851
|
-
# The server, in another thread, is shutting down
|
852
|
-
end
|
853
|
-
|
915
|
+
notify_safely(HALT_COMMAND)
|
854
916
|
@thread.join if @thread && sync
|
855
917
|
end
|
856
918
|
|
857
|
-
def begin_restart
|
858
|
-
|
859
|
-
|
860
|
-
rescue IOError
|
861
|
-
# The server, in another thread, is shutting down
|
862
|
-
end
|
919
|
+
def begin_restart(sync=false)
|
920
|
+
notify_safely(RESTART_COMMAND)
|
921
|
+
@thread.join if @thread && sync
|
863
922
|
end
|
864
923
|
|
865
924
|
def fast_write(io, str)
|
@@ -882,5 +941,28 @@ module Puma
|
|
882
941
|
end
|
883
942
|
end
|
884
943
|
private :fast_write
|
944
|
+
|
945
|
+
ThreadLocalKey = :puma_server
|
946
|
+
|
947
|
+
def self.current
|
948
|
+
Thread.current[ThreadLocalKey]
|
949
|
+
end
|
950
|
+
|
951
|
+
def shutting_down?
|
952
|
+
@status == :stop || @status == :restart
|
953
|
+
end
|
954
|
+
|
955
|
+
def possible_header_injection?(header_value)
|
956
|
+
HTTP_INJECTION_REGEX =~ header_value.to_s
|
957
|
+
end
|
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
|
885
967
|
end
|
886
968
|
end
|