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