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