puma 2.1.1 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/History.md +3273 -0
- data/LICENSE +23 -20
- data/README.md +388 -100
- data/bin/puma-wild +25 -0
- data/bin/pumactl +1 -1
- data/docs/architecture.md +74 -0
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +137 -0
- data/docs/fork_worker.md +41 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +9 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +73 -0
- data/docs/nginx.md +5 -5
- data/docs/plugins.md +42 -0
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +65 -0
- data/docs/signals.md +98 -0
- data/docs/stats.md +148 -0
- data/docs/systemd.md +253 -0
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +59 -2
- data/ext/puma_http11/http11_parser.c +319 -487
- data/ext/puma_http11/http11_parser.h +15 -13
- data/ext/puma_http11/http11_parser.java.rl +27 -43
- data/ext/puma_http11/http11_parser.rl +27 -24
- data/ext/puma_http11/http11_parser_common.rl +8 -8
- data/ext/puma_http11/mini_ssl.c +696 -38
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +145 -113
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +139 -172
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
- data/ext/puma_http11/puma_http11.c +183 -175
- data/lib/puma/app/status.rb +77 -29
- data/lib/puma/binder.rb +349 -119
- data/lib/puma/cli.rb +164 -769
- data/lib/puma/client.rb +591 -132
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +127 -0
- data/lib/puma/cluster.rb +634 -0
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +115 -0
- data/lib/puma/configuration.rb +370 -202
- data/lib/puma/const.rb +266 -115
- data/lib/puma/control_cli.rb +219 -120
- data/lib/puma/detect.rb +45 -2
- data/lib/puma/dsl.rb +1496 -0
- data/lib/puma/error_logger.rb +115 -0
- data/lib/puma/events.rb +44 -60
- data/lib/puma/io_buffer.rb +48 -5
- data/lib/puma/jruby_restart.rb +2 -51
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +502 -0
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +96 -0
- data/lib/puma/minissl.rb +355 -37
- data/lib/puma/null_io.rb +79 -12
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +102 -133
- data/lib/puma/request.rb +703 -0
- data/lib/puma/runner.rb +211 -0
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +544 -436
- data/lib/puma/single.rb +72 -0
- data/lib/puma/state_file.rb +69 -0
- data/lib/puma/thread_pool.rb +282 -56
- data/lib/puma/util.rb +125 -0
- data/lib/puma.rb +80 -6
- data/lib/rack/handler/puma.rb +125 -47
- data/tools/Dockerfile +26 -0
- data/tools/trickletest.rb +44 -0
- metadata +77 -148
- data/COPYING +0 -55
- data/Gemfile +0 -10
- data/History.txt +0 -291
- data/Manifest.txt +0 -60
- data/Rakefile +0 -121
- data/TODO +0 -5
- data/docs/config.md +0 -0
- data/ext/puma_http11/ext_help.h +0 -15
- data/ext/puma_http11/io_buffer.c +0 -154
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/capistrano.rb +0 -34
- data/lib/puma/compat.rb +0 -11
- data/lib/puma/daemon_ext.rb +0 -20
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack_patch.rb +0 -25
- data/puma.gemspec +0 -45
- data/test/test_app_status.rb +0 -88
- data/test/test_cli.rb +0 -171
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -126
- data/test/test_integration.rb +0 -154
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -25
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -207
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_thread_pool.rb +0 -146
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -332
- data/tools/jungle/init.d/run-puma +0 -3
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -52
data/lib/puma/server.rb
CHANGED
|
@@ -1,102 +1,195 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'stringio'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
require 'puma/puma_http11'
|
|
17
|
-
|
|
18
|
-
unless Puma.const_defined? "IOBuffer"
|
|
19
|
-
require 'puma/io_buffer'
|
|
20
|
-
end
|
|
5
|
+
require_relative 'thread_pool'
|
|
6
|
+
require_relative 'const'
|
|
7
|
+
require_relative 'log_writer'
|
|
8
|
+
require_relative 'events'
|
|
9
|
+
require_relative 'null_io'
|
|
10
|
+
require_relative 'reactor'
|
|
11
|
+
require_relative 'client'
|
|
12
|
+
require_relative 'binder'
|
|
13
|
+
require_relative 'util'
|
|
14
|
+
require_relative 'request'
|
|
15
|
+
require_relative 'configuration'
|
|
16
|
+
require_relative 'cluster_accept_loop_delay'
|
|
21
17
|
|
|
22
18
|
require 'socket'
|
|
19
|
+
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
|
23
20
|
|
|
24
21
|
module Puma
|
|
25
|
-
|
|
26
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
|
23
|
+
#
|
|
24
|
+
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
|
25
|
+
# to generate one or more `Puma::Server` instances capable of handling requests.
|
|
26
|
+
# Each Puma process will contain one `Puma::Server` instance.
|
|
27
|
+
#
|
|
28
|
+
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
|
29
|
+
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
|
30
|
+
#
|
|
31
|
+
# Each `Puma::Server` will have one reactor and one thread pool.
|
|
27
32
|
class Server
|
|
33
|
+
module FiberPerRequest
|
|
34
|
+
def handle_request(client, requests)
|
|
35
|
+
Fiber.new do
|
|
36
|
+
super
|
|
37
|
+
end.resume
|
|
38
|
+
end
|
|
39
|
+
end
|
|
28
40
|
|
|
29
41
|
include Puma::Const
|
|
30
|
-
|
|
42
|
+
include Request
|
|
31
43
|
|
|
44
|
+
attr_reader :options
|
|
32
45
|
attr_reader :thread
|
|
46
|
+
attr_reader :log_writer
|
|
33
47
|
attr_reader :events
|
|
34
|
-
|
|
48
|
+
attr_reader :min_threads, :max_threads # for #stats
|
|
49
|
+
attr_reader :requests_count # @version 5.0.0
|
|
35
50
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
# @todo the following may be deprecated in the future
|
|
52
|
+
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
|
|
53
|
+
:leak_stack_on_error,
|
|
54
|
+
:persistent_timeout, :reaping_time
|
|
55
|
+
|
|
56
|
+
attr_accessor :app
|
|
57
|
+
attr_accessor :binder
|
|
40
58
|
|
|
41
59
|
# Create a server for the rack app +app+.
|
|
42
60
|
#
|
|
43
|
-
# +
|
|
44
|
-
#
|
|
61
|
+
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
|
62
|
+
#
|
|
63
|
+
# +events+ is a Puma::Events object used to notify application status events.
|
|
45
64
|
#
|
|
46
65
|
# Server#run returns a thread that you can join on to wait for the server
|
|
47
|
-
# to do
|
|
66
|
+
# to do its work.
|
|
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
|
+
#
|
|
72
|
+
# @note The `events` parameter is set to nil, and set to `Events.new` in code.
|
|
73
|
+
# Often `options` needs to be passed, but `events` does not. Using nil allows
|
|
74
|
+
# calling code to not require events.rb.
|
|
48
75
|
#
|
|
49
|
-
def initialize(app, events=
|
|
76
|
+
def initialize(app, events = nil, options = {})
|
|
50
77
|
@app = app
|
|
51
|
-
@events = events
|
|
52
|
-
|
|
53
|
-
@check, @notify = Puma::Util.pipe
|
|
78
|
+
@events = events || Events.new
|
|
54
79
|
|
|
80
|
+
@check, @notify = nil
|
|
55
81
|
@status = :stop
|
|
56
82
|
|
|
57
|
-
@min_threads = 0
|
|
58
|
-
@max_threads = 16
|
|
59
|
-
@auto_trim_time = 1
|
|
60
|
-
|
|
61
83
|
@thread = nil
|
|
62
84
|
@thread_pool = nil
|
|
85
|
+
@reactor = nil
|
|
86
|
+
|
|
87
|
+
@env_set_http_version = nil
|
|
63
88
|
|
|
64
|
-
@
|
|
89
|
+
@options = if options.is_a?(UserFileDefaultOptions)
|
|
90
|
+
options
|
|
91
|
+
else
|
|
92
|
+
UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@clustered = (@options.fetch :workers, 0) > 0
|
|
96
|
+
@worker_write = @options[:worker_write]
|
|
97
|
+
@log_writer = @options.fetch :log_writer, LogWriter.stdio
|
|
98
|
+
@early_hints = @options[:early_hints]
|
|
99
|
+
@first_data_timeout = @options[:first_data_timeout]
|
|
100
|
+
@persistent_timeout = @options[:persistent_timeout]
|
|
101
|
+
@idle_timeout = @options[:idle_timeout]
|
|
102
|
+
@min_threads = @options[:min_threads]
|
|
103
|
+
@max_threads = @options[:max_threads]
|
|
104
|
+
@queue_requests = @options[:queue_requests]
|
|
105
|
+
@max_keep_alive = @options[:max_keep_alive]
|
|
106
|
+
@enable_keep_alives = @options[:enable_keep_alives]
|
|
107
|
+
@enable_keep_alives &&= @queue_requests
|
|
108
|
+
@io_selector_backend = @options[:io_selector_backend]
|
|
109
|
+
@http_content_length_limit = @options[:http_content_length_limit]
|
|
110
|
+
@cluster_accept_loop_delay = ClusterAcceptLoopDelay.new(
|
|
111
|
+
workers: @options[:workers],
|
|
112
|
+
max_delay: @options[:wait_for_less_busy_worker] || 0 # Real default is in Configuration::DEFAULTS, this is for unit testing
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if @options[:fiber_per_request]
|
|
116
|
+
singleton_class.prepend(FiberPerRequest)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# make this a hash, since we prefer `key?` over `include?`
|
|
120
|
+
@supported_http_methods =
|
|
121
|
+
if @options[:supported_http_methods] == :any
|
|
122
|
+
:any
|
|
123
|
+
else
|
|
124
|
+
if (ary = @options[:supported_http_methods])
|
|
125
|
+
ary
|
|
126
|
+
else
|
|
127
|
+
SUPPORTED_HTTP_METHODS
|
|
128
|
+
end.sort.product([nil]).to_h.freeze
|
|
129
|
+
end
|
|
65
130
|
|
|
66
|
-
|
|
67
|
-
@
|
|
131
|
+
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
|
132
|
+
@leak_stack_on_error = @options[:environment] ? temp : true
|
|
68
133
|
|
|
69
|
-
@
|
|
134
|
+
@binder = Binder.new(log_writer, @options)
|
|
70
135
|
|
|
71
136
|
ENV['RACK_ENV'] ||= "development"
|
|
72
|
-
end
|
|
73
137
|
|
|
74
|
-
|
|
138
|
+
@mode = :http
|
|
75
139
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
140
|
+
@precheck_closing = true
|
|
141
|
+
|
|
142
|
+
@requests_count = 0
|
|
143
|
+
|
|
144
|
+
@idle_timeout_reached = false
|
|
145
|
+
end
|
|
79
146
|
|
|
80
147
|
def inherit_binder(bind)
|
|
81
148
|
@binder = bind
|
|
82
|
-
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class << self
|
|
152
|
+
# @!attribute [r] current
|
|
153
|
+
def current
|
|
154
|
+
Thread.current.puma_server
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# :nodoc:
|
|
158
|
+
# @version 5.0.0
|
|
159
|
+
def tcp_cork_supported?
|
|
160
|
+
Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# :nodoc:
|
|
164
|
+
# @version 5.0.0
|
|
165
|
+
def closed_socket_supported?
|
|
166
|
+
Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
|
|
167
|
+
end
|
|
168
|
+
private :tcp_cork_supported?
|
|
169
|
+
private :closed_socket_supported?
|
|
83
170
|
end
|
|
84
171
|
|
|
85
172
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
|
86
173
|
# packetizes our stream. This improves both latency and throughput.
|
|
174
|
+
# socket parameter may be an MiniSSL::Socket, so use to_io
|
|
87
175
|
#
|
|
88
|
-
if
|
|
176
|
+
if tcp_cork_supported?
|
|
89
177
|
# 6 == Socket::IPPROTO_TCP
|
|
90
178
|
# 3 == TCP_CORK
|
|
91
179
|
# 1/0 == turn on/off
|
|
92
180
|
def cork_socket(socket)
|
|
93
|
-
|
|
181
|
+
skt = socket.to_io
|
|
182
|
+
begin
|
|
183
|
+
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
|
|
184
|
+
rescue IOError, SystemCallError
|
|
185
|
+
end
|
|
94
186
|
end
|
|
95
187
|
|
|
96
188
|
def uncork_socket(socket)
|
|
189
|
+
skt = socket.to_io
|
|
97
190
|
begin
|
|
98
|
-
|
|
99
|
-
rescue IOError
|
|
191
|
+
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
|
192
|
+
rescue IOError, SystemCallError
|
|
100
193
|
end
|
|
101
194
|
end
|
|
102
195
|
else
|
|
@@ -107,12 +200,50 @@ module Puma
|
|
|
107
200
|
end
|
|
108
201
|
end
|
|
109
202
|
|
|
203
|
+
if closed_socket_supported?
|
|
204
|
+
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
|
205
|
+
|
|
206
|
+
def closed_socket?(socket)
|
|
207
|
+
skt = socket.to_io
|
|
208
|
+
return false unless skt.kind_of?(TCPSocket) && @precheck_closing
|
|
209
|
+
|
|
210
|
+
begin
|
|
211
|
+
tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
|
212
|
+
rescue IOError, SystemCallError
|
|
213
|
+
@precheck_closing = false
|
|
214
|
+
false
|
|
215
|
+
else
|
|
216
|
+
state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
|
|
217
|
+
# TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
|
|
218
|
+
(state >= 6 && state <= 9) || state == 11
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
else
|
|
222
|
+
def closed_socket?(socket)
|
|
223
|
+
false
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @!attribute [r] backlog
|
|
110
228
|
def backlog
|
|
111
|
-
@thread_pool
|
|
229
|
+
@thread_pool&.backlog
|
|
112
230
|
end
|
|
113
231
|
|
|
232
|
+
# @!attribute [r] running
|
|
114
233
|
def running
|
|
115
|
-
@thread_pool
|
|
234
|
+
@thread_pool&.spawned
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# This number represents the number of requests that
|
|
238
|
+
# the server is capable of taking right now.
|
|
239
|
+
#
|
|
240
|
+
# For example if the number is 5 then it means
|
|
241
|
+
# there are 5 threads sitting idle ready to take
|
|
242
|
+
# a request. If one request comes in, then the
|
|
243
|
+
# value would be 4 until it finishes processing.
|
|
244
|
+
# @!attribute [r] pool_capacity
|
|
245
|
+
def pool_capacity
|
|
246
|
+
@thread_pool&.pool_capacity
|
|
116
247
|
end
|
|
117
248
|
|
|
118
249
|
# Runs the server.
|
|
@@ -121,101 +252,205 @@ module Puma
|
|
|
121
252
|
# up in the background to handle requests. Otherwise requests
|
|
122
253
|
# are handled synchronously.
|
|
123
254
|
#
|
|
124
|
-
def run(background=true)
|
|
255
|
+
def run(background=true, thread_name: 'srv')
|
|
125
256
|
BasicSocket.do_not_reverse_lookup = true
|
|
126
257
|
|
|
258
|
+
@events.fire :state, :booting
|
|
259
|
+
|
|
127
260
|
@status = :run
|
|
128
261
|
|
|
129
|
-
@thread_pool = ThreadPool.new(
|
|
130
|
-
@max_threads,
|
|
131
|
-
IOBuffer) do |client, buffer|
|
|
132
|
-
process_now = false
|
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) { |client| process_client client }
|
|
133
263
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
rescue ConnectionError
|
|
142
|
-
client.close
|
|
143
|
-
else
|
|
144
|
-
if process_now
|
|
145
|
-
process_client client, buffer
|
|
146
|
-
else
|
|
147
|
-
client.set_timeout @first_data_timeout
|
|
148
|
-
@reactor.add client
|
|
149
|
-
end
|
|
150
|
-
end
|
|
264
|
+
if @queue_requests
|
|
265
|
+
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
266
|
+
# Inversion of control, the reactor is calling a method on the server when it
|
|
267
|
+
# is done buffering a request or receives a new request from a keepalive connection.
|
|
268
|
+
self.reactor_wakeup(c)
|
|
269
|
+
}
|
|
270
|
+
@reactor.run
|
|
151
271
|
end
|
|
152
272
|
|
|
153
|
-
@
|
|
273
|
+
@thread_pool.auto_reap! if options[:reaping_time]
|
|
274
|
+
@thread_pool.auto_trim! if @min_threads != @max_threads && options[:auto_trim_time]
|
|
154
275
|
|
|
155
|
-
@
|
|
276
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
|
156
277
|
|
|
157
|
-
|
|
158
|
-
@thread_pool.auto_trim!(@auto_trim_time)
|
|
159
|
-
end
|
|
278
|
+
@events.fire :state, :running
|
|
160
279
|
|
|
161
280
|
if background
|
|
162
|
-
@thread = Thread.new
|
|
281
|
+
@thread = Thread.new do
|
|
282
|
+
Puma.set_thread_name thread_name
|
|
283
|
+
handle_servers
|
|
284
|
+
end
|
|
163
285
|
return @thread
|
|
164
286
|
else
|
|
165
287
|
handle_servers
|
|
166
288
|
end
|
|
167
289
|
end
|
|
168
290
|
|
|
291
|
+
# This method is called from the Reactor thread when a queued Client receives data,
|
|
292
|
+
# times out, or when the Reactor is shutting down.
|
|
293
|
+
#
|
|
294
|
+
# While the code lives in the Server, the logic is executed on the reactor thread, independently
|
|
295
|
+
# from the server.
|
|
296
|
+
#
|
|
297
|
+
# It is responsible for ensuring that a request has been completely received
|
|
298
|
+
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
|
299
|
+
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
|
300
|
+
# such as nginx) then the application would be subject to a slow client attack.
|
|
301
|
+
#
|
|
302
|
+
# For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/main/docs/architecture.md).
|
|
303
|
+
#
|
|
304
|
+
# The method checks to see if it has the full header and body with
|
|
305
|
+
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
|
306
|
+
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
|
307
|
+
# so that a "worker thread" can pick up the request and begin to execute application logic.
|
|
308
|
+
# The Client is then removed from the reactor (return `true`).
|
|
309
|
+
#
|
|
310
|
+
# If a client object times out, a 408 response is written, its connection is closed,
|
|
311
|
+
# and the object is removed from the reactor (return `true`).
|
|
312
|
+
#
|
|
313
|
+
# If the Reactor is shutting down, all Clients are either timed out or passed to the
|
|
314
|
+
# ThreadPool, depending on their current state (#can_close?).
|
|
315
|
+
#
|
|
316
|
+
# Otherwise, if the full request is not ready then the client will remain in the reactor
|
|
317
|
+
# (return `false`). When the client sends more data to the socket the `Puma::Client` object
|
|
318
|
+
# will wake up and again be checked to see if it's ready to be passed to the thread pool.
|
|
319
|
+
def reactor_wakeup(client)
|
|
320
|
+
shutdown = !@queue_requests
|
|
321
|
+
if client.try_to_finish || (shutdown && !client.can_close?)
|
|
322
|
+
@thread_pool << client
|
|
323
|
+
elsif shutdown || client.timeout == 0
|
|
324
|
+
client.timeout!
|
|
325
|
+
else
|
|
326
|
+
client.set_timeout(@first_data_timeout)
|
|
327
|
+
false
|
|
328
|
+
end
|
|
329
|
+
rescue StandardError => e
|
|
330
|
+
client_error(e, client)
|
|
331
|
+
close_client_safely(client)
|
|
332
|
+
true
|
|
333
|
+
end
|
|
334
|
+
|
|
169
335
|
def handle_servers
|
|
336
|
+
@env_set_http_version = Object.const_defined?(:Rack) && ::Rack.respond_to?(:release) &&
|
|
337
|
+
Gem::Version.new(::Rack.release) < Gem::Version.new('3.1.0')
|
|
338
|
+
|
|
170
339
|
begin
|
|
171
340
|
check = @check
|
|
172
341
|
sockets = [check] + @binder.ios
|
|
173
342
|
pool = @thread_pool
|
|
343
|
+
queue_requests = @queue_requests
|
|
344
|
+
drain = options[:drain_on_shutdown] ? 0 : nil
|
|
345
|
+
|
|
346
|
+
addr_send_name, addr_value = case options[:remote_address]
|
|
347
|
+
when :value
|
|
348
|
+
[:peerip=, options[:remote_address_value]]
|
|
349
|
+
when :header
|
|
350
|
+
[:remote_addr_header=, options[:remote_address_header]]
|
|
351
|
+
when :proxy_protocol
|
|
352
|
+
[:expect_proxy_proto=, options[:remote_address_proxy_protocol]]
|
|
353
|
+
else
|
|
354
|
+
[nil, nil]
|
|
355
|
+
end
|
|
174
356
|
|
|
175
|
-
while @status == :run
|
|
357
|
+
while @status == :run || (drain && shutting_down?)
|
|
176
358
|
begin
|
|
177
|
-
ios = IO.select sockets
|
|
359
|
+
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
|
|
360
|
+
unless ios
|
|
361
|
+
unless shutting_down?
|
|
362
|
+
@idle_timeout_reached = true
|
|
363
|
+
|
|
364
|
+
if @clustered
|
|
365
|
+
@worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
|
|
366
|
+
next
|
|
367
|
+
else
|
|
368
|
+
@log_writer.log "- Idle timeout reached"
|
|
369
|
+
@status = :stop
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
break
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
if @idle_timeout_reached && @clustered
|
|
377
|
+
@idle_timeout_reached = false
|
|
378
|
+
@worker_write << "#{PipeRequest::PIPE_IDLE}#{Process.pid}\n" rescue nil
|
|
379
|
+
end
|
|
380
|
+
|
|
178
381
|
ios.first.each do |sock|
|
|
179
382
|
if sock == check
|
|
180
383
|
break if handle_check
|
|
181
384
|
else
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
385
|
+
# if ThreadPool out_of_band code is running, we don't want to add
|
|
386
|
+
# clients until the code is finished.
|
|
387
|
+
pool.wait_while_out_of_band_running
|
|
388
|
+
|
|
389
|
+
# A well rested herd (cluster) runs faster
|
|
390
|
+
if @cluster_accept_loop_delay.on? && (busy_threads_plus_todo = pool.busy_threads) > 0
|
|
391
|
+
delay = @cluster_accept_loop_delay.calculate(
|
|
392
|
+
max_threads: @max_threads,
|
|
393
|
+
busy_threads_plus_todo: busy_threads_plus_todo
|
|
394
|
+
)
|
|
395
|
+
sleep(delay)
|
|
188
396
|
end
|
|
397
|
+
|
|
398
|
+
io = begin
|
|
399
|
+
sock.accept_nonblock
|
|
400
|
+
rescue IO::WaitReadable
|
|
401
|
+
next
|
|
402
|
+
end
|
|
403
|
+
drain += 1 if shutting_down?
|
|
404
|
+
client = new_client(io, sock)
|
|
405
|
+
client.send(addr_send_name, addr_value) if addr_value
|
|
406
|
+
pool << client
|
|
189
407
|
end
|
|
190
408
|
end
|
|
191
|
-
rescue Errno::
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
rescue
|
|
195
|
-
@
|
|
409
|
+
rescue IOError, Errno::EBADF
|
|
410
|
+
# In the case that any of the sockets are unexpectedly close.
|
|
411
|
+
raise
|
|
412
|
+
rescue StandardError => e
|
|
413
|
+
@log_writer.unknown_error e, nil, "Listen loop"
|
|
196
414
|
end
|
|
197
415
|
end
|
|
198
416
|
|
|
199
|
-
|
|
200
|
-
@
|
|
417
|
+
@log_writer.debug "Drained #{drain} additional connections." if drain
|
|
418
|
+
@events.fire :state, @status
|
|
201
419
|
|
|
202
|
-
|
|
420
|
+
if queue_requests
|
|
421
|
+
@queue_requests = false
|
|
422
|
+
@reactor.shutdown
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
|
203
426
|
rescue Exception => e
|
|
204
|
-
|
|
205
|
-
STDERR.puts e.backtrace
|
|
427
|
+
@log_writer.unknown_error e, nil, "Exception handling servers"
|
|
206
428
|
ensure
|
|
207
|
-
|
|
208
|
-
@notify.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
429
|
+
# Errno::EBADF is infrequently raised
|
|
430
|
+
[@check, @notify].each do |io|
|
|
431
|
+
begin
|
|
432
|
+
io.close unless io.closed?
|
|
433
|
+
rescue Errno::EBADF
|
|
434
|
+
end
|
|
212
435
|
end
|
|
436
|
+
@notify = nil
|
|
437
|
+
@check = nil
|
|
213
438
|
end
|
|
439
|
+
|
|
440
|
+
@events.fire :state, :done
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# :nodoc:
|
|
444
|
+
def new_client(io, sock)
|
|
445
|
+
client = Client.new(io, @binder.env(sock))
|
|
446
|
+
client.listener = sock
|
|
447
|
+
client.http_content_length_limit = @http_content_length_limit
|
|
448
|
+
client
|
|
214
449
|
end
|
|
215
450
|
|
|
216
451
|
# :nodoc:
|
|
217
452
|
def handle_check
|
|
218
|
-
cmd = @check.read(1)
|
|
453
|
+
cmd = @check.read(1)
|
|
219
454
|
|
|
220
455
|
case cmd
|
|
221
456
|
when STOP_COMMAND
|
|
@@ -229,402 +464,275 @@ module Puma
|
|
|
229
464
|
return true
|
|
230
465
|
end
|
|
231
466
|
|
|
232
|
-
|
|
467
|
+
false
|
|
233
468
|
end
|
|
234
469
|
|
|
235
|
-
# Given a connection on +client+, handle the incoming requests
|
|
470
|
+
# Given a connection on +client+, handle the incoming requests,
|
|
471
|
+
# or queue the connection in the Reactor if no request is available.
|
|
236
472
|
#
|
|
237
|
-
# This method
|
|
473
|
+
# This method is called from a ThreadPool worker thread.
|
|
474
|
+
#
|
|
475
|
+
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
|
238
476
|
# indicates that it supports keep alive, wait for another request before
|
|
239
477
|
# returning.
|
|
240
478
|
#
|
|
241
|
-
|
|
479
|
+
# Return true if one or more requests were processed.
|
|
480
|
+
def process_client(client)
|
|
481
|
+
close_socket = true
|
|
482
|
+
|
|
483
|
+
requests = 0
|
|
484
|
+
|
|
242
485
|
begin
|
|
243
|
-
|
|
486
|
+
if @queue_requests && !client.eagerly_finish
|
|
244
487
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
when false
|
|
248
|
-
return
|
|
249
|
-
when :async
|
|
488
|
+
client.set_timeout(@first_data_timeout)
|
|
489
|
+
if @reactor.add client
|
|
250
490
|
close_socket = false
|
|
251
|
-
return
|
|
252
|
-
when true
|
|
253
|
-
buffer.reset
|
|
254
|
-
|
|
255
|
-
unless client.reset(@status == :run)
|
|
256
|
-
close_socket = false
|
|
257
|
-
client.set_timeout @persistent_timeout
|
|
258
|
-
@reactor.add client
|
|
259
|
-
return
|
|
260
|
-
end
|
|
491
|
+
return false
|
|
261
492
|
end
|
|
262
493
|
end
|
|
263
494
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
495
|
+
with_force_shutdown(client) do
|
|
496
|
+
client.finish(@first_data_timeout)
|
|
497
|
+
end
|
|
267
498
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
499
|
+
can_loop = true
|
|
500
|
+
while can_loop
|
|
501
|
+
can_loop = false
|
|
502
|
+
@requests_count += 1
|
|
503
|
+
case handle_request(client, requests + 1)
|
|
504
|
+
when :close
|
|
505
|
+
when :async
|
|
506
|
+
close_socket = false
|
|
507
|
+
when :keep_alive
|
|
508
|
+
requests += 1
|
|
509
|
+
|
|
510
|
+
client.reset
|
|
271
511
|
|
|
272
|
-
|
|
512
|
+
# This indicates data exists in the client read buffer and there may be
|
|
513
|
+
# additional requests on it, so process them
|
|
514
|
+
next_request_ready = if client.has_back_to_back_requests?
|
|
515
|
+
with_force_shutdown(client) { client.process_back_to_back_requests }
|
|
516
|
+
else
|
|
517
|
+
with_force_shutdown(client) { client.eagerly_finish }
|
|
518
|
+
end
|
|
273
519
|
|
|
274
|
-
|
|
520
|
+
if next_request_ready
|
|
521
|
+
# When Puma has spare threads, allow this one to be monopolized
|
|
522
|
+
# Perf optimization for https://github.com/puma/puma/issues/3788
|
|
523
|
+
if @thread_pool.waiting > 0
|
|
524
|
+
can_loop = true
|
|
525
|
+
else
|
|
526
|
+
@thread_pool << client
|
|
527
|
+
close_socket = false
|
|
528
|
+
end
|
|
529
|
+
elsif @queue_requests
|
|
530
|
+
client.set_timeout @persistent_timeout
|
|
531
|
+
if @reactor.add client
|
|
532
|
+
close_socket = false
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
true
|
|
275
538
|
rescue StandardError => e
|
|
276
|
-
client
|
|
539
|
+
client_error(e, client, requests)
|
|
540
|
+
# The ensure tries to close +client+ down
|
|
541
|
+
requests > 0
|
|
542
|
+
ensure
|
|
543
|
+
client.io_buffer.reset
|
|
277
544
|
|
|
278
|
-
|
|
545
|
+
close_client_safely(client) if close_socket
|
|
546
|
+
end
|
|
547
|
+
end
|
|
279
548
|
|
|
280
|
-
|
|
281
|
-
|
|
549
|
+
# :nodoc:
|
|
550
|
+
def close_client_safely(client)
|
|
551
|
+
client.close
|
|
552
|
+
rescue IOError, SystemCallError
|
|
553
|
+
# Already closed
|
|
554
|
+
rescue MiniSSL::SSLError => e
|
|
555
|
+
@log_writer.ssl_error e, client.io
|
|
556
|
+
rescue StandardError => e
|
|
557
|
+
@log_writer.unknown_error e, nil, "Client"
|
|
558
|
+
end
|
|
282
559
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
560
|
+
# Triggers a client timeout if the thread-pool shuts down
|
|
561
|
+
# during execution of the provided block.
|
|
562
|
+
def with_force_shutdown(client, &block)
|
|
563
|
+
@thread_pool.with_force_shutdown(&block)
|
|
564
|
+
rescue ThreadPool::ForceShutdown
|
|
565
|
+
client.timeout!
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# :nocov:
|
|
569
|
+
|
|
570
|
+
# Handle various error types thrown by Client I/O operations.
|
|
571
|
+
def client_error(e, client, requests = 1)
|
|
572
|
+
# Swallow, do not log
|
|
573
|
+
return if [ConnectionError, EOFError].include?(e.class)
|
|
574
|
+
|
|
575
|
+
case e
|
|
576
|
+
when MiniSSL::SSLError
|
|
577
|
+
lowlevel_error(e, client.env)
|
|
578
|
+
@log_writer.ssl_error e, client.io
|
|
579
|
+
when HttpParserError
|
|
580
|
+
response_to_error(client, requests, e, 400)
|
|
581
|
+
@log_writer.parse_error e, client
|
|
582
|
+
when HttpParserError501
|
|
583
|
+
response_to_error(client, requests, e, 501)
|
|
584
|
+
@log_writer.parse_error e, client
|
|
585
|
+
else
|
|
586
|
+
response_to_error(client, requests, e, 500)
|
|
587
|
+
@log_writer.unknown_error e, nil, "Read"
|
|
290
588
|
end
|
|
291
589
|
end
|
|
292
590
|
|
|
293
|
-
#
|
|
294
|
-
# and fixup keys to comply with Rack's env guidelines.
|
|
591
|
+
# A fallback rack response if +@app+ raises as exception.
|
|
295
592
|
#
|
|
296
|
-
def
|
|
297
|
-
if
|
|
298
|
-
if
|
|
299
|
-
|
|
300
|
-
|
|
593
|
+
def lowlevel_error(e, env, status=500)
|
|
594
|
+
if handler = options[:lowlevel_error_handler]
|
|
595
|
+
if handler.arity == 1
|
|
596
|
+
return handler.call(e)
|
|
597
|
+
elsif handler.arity == 2
|
|
598
|
+
return handler.call(e, env)
|
|
301
599
|
else
|
|
302
|
-
env
|
|
303
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
600
|
+
return handler.call(e, env, status)
|
|
304
601
|
end
|
|
305
|
-
else
|
|
306
|
-
env[SERVER_NAME] = LOCALHOST
|
|
307
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
308
602
|
end
|
|
309
603
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
|
604
|
+
if @leak_stack_on_error
|
|
605
|
+
backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
|
|
606
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
|
|
607
|
+
else
|
|
608
|
+
[status, {}, [""]]
|
|
316
609
|
end
|
|
317
|
-
|
|
318
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
|
319
|
-
|
|
320
|
-
# From http://www.ietf.org/rfc/rfc3875 :
|
|
321
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
|
322
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
323
|
-
# may not identify the ultimate source of the request.
|
|
324
|
-
# They identify the client for the immediate request to the
|
|
325
|
-
# server; that client may be a proxy, gateway, or other
|
|
326
|
-
# intermediary acting on behalf of the actual source client."
|
|
327
|
-
#
|
|
328
|
-
env[REMOTE_ADDR] = client.peeraddr.last
|
|
329
610
|
end
|
|
330
611
|
|
|
331
|
-
def
|
|
332
|
-
|
|
612
|
+
def response_to_error(client, requests, err, status_code)
|
|
613
|
+
status, headers, res_body = lowlevel_error(err, client.env, status_code)
|
|
614
|
+
prepare_response(status, headers, res_body, requests, client)
|
|
333
615
|
end
|
|
616
|
+
private :response_to_error
|
|
334
617
|
|
|
335
|
-
#
|
|
336
|
-
# in +body+, finish reading the body if there is one and invoke
|
|
337
|
-
# the rack app. Then construct the response and write it back to
|
|
338
|
-
# +client+
|
|
339
|
-
#
|
|
340
|
-
# +cl+ is the previously fetched Content-Length header if there
|
|
341
|
-
# was one. This is an optimization to keep from having to look
|
|
342
|
-
# it up again.
|
|
618
|
+
# Wait for all outstanding requests to finish.
|
|
343
619
|
#
|
|
344
|
-
def
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
normalize_env env, client
|
|
349
|
-
|
|
350
|
-
env[PUMA_SOCKET] = client
|
|
351
|
-
|
|
352
|
-
env[HIJACK_P] = true
|
|
353
|
-
env[HIJACK] = req
|
|
354
|
-
|
|
355
|
-
body = req.body
|
|
356
|
-
|
|
357
|
-
head = env[REQUEST_METHOD] == HEAD
|
|
358
|
-
|
|
359
|
-
env[RACK_INPUT] = body
|
|
360
|
-
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
|
361
|
-
|
|
362
|
-
# A rack extension. If the app writes #call'ables to this
|
|
363
|
-
# array, we will invoke them when the request is done.
|
|
364
|
-
#
|
|
365
|
-
after_reply = env[RACK_AFTER_REPLY] = []
|
|
366
|
-
|
|
367
|
-
begin
|
|
368
|
-
begin
|
|
369
|
-
status, headers, res_body = @app.call(env)
|
|
370
|
-
|
|
371
|
-
return :async if req.hijacked
|
|
372
|
-
|
|
373
|
-
status = status.to_i
|
|
374
|
-
|
|
375
|
-
if status == -1
|
|
376
|
-
unless headers.empty? and res_body == []
|
|
377
|
-
raise "async response must have empty headers and body"
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
return :async
|
|
381
|
-
end
|
|
382
|
-
rescue => e
|
|
383
|
-
@events.unknown_error self, e, "Rack app"
|
|
620
|
+
def graceful_shutdown
|
|
621
|
+
if options[:shutdown_debug]
|
|
622
|
+
threads = Thread.list
|
|
623
|
+
total = threads.size
|
|
384
624
|
|
|
385
|
-
|
|
386
|
-
end
|
|
625
|
+
pid = Process.pid
|
|
387
626
|
|
|
388
|
-
|
|
389
|
-
no_body = head
|
|
627
|
+
$stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
|
|
390
628
|
|
|
391
|
-
|
|
392
|
-
|
|
629
|
+
threads.each_with_index do |t,i|
|
|
630
|
+
$stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
|
|
631
|
+
$stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
|
|
393
632
|
end
|
|
633
|
+
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
|
634
|
+
end
|
|
394
635
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
colon = COLON
|
|
399
|
-
|
|
400
|
-
if env[HTTP_VERSION] == HTTP_11
|
|
401
|
-
allow_chunked = true
|
|
402
|
-
keep_alive = env[HTTP_CONNECTION] != CLOSE
|
|
403
|
-
include_keepalive_header = false
|
|
404
|
-
|
|
405
|
-
# An optimization. The most common response is 200, so we can
|
|
406
|
-
# reply with the proper 200 status without having to compute
|
|
407
|
-
# the response header.
|
|
408
|
-
#
|
|
409
|
-
if status == 200
|
|
410
|
-
lines << HTTP_11_200
|
|
411
|
-
else
|
|
412
|
-
lines.append "HTTP/1.1 ", status.to_s, " ",
|
|
413
|
-
HTTP_STATUS_CODES[status], line_ending
|
|
636
|
+
if @status != :restart
|
|
637
|
+
@binder.close
|
|
638
|
+
end
|
|
414
639
|
|
|
415
|
-
|
|
416
|
-
|
|
640
|
+
if @thread_pool
|
|
641
|
+
if timeout = options[:force_shutdown_after]
|
|
642
|
+
@thread_pool.shutdown timeout.to_f
|
|
417
643
|
else
|
|
418
|
-
|
|
419
|
-
keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
|
|
420
|
-
include_keepalive_header = keep_alive
|
|
421
|
-
|
|
422
|
-
# Same optimization as above for HTTP/1.1
|
|
423
|
-
#
|
|
424
|
-
if status == 200
|
|
425
|
-
lines << HTTP_10_200
|
|
426
|
-
else
|
|
427
|
-
lines.append "HTTP/1.0 ", status.to_s, " ",
|
|
428
|
-
HTTP_STATUS_CODES[status], line_ending
|
|
429
|
-
|
|
430
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
431
|
-
end
|
|
432
|
-
end
|
|
433
|
-
|
|
434
|
-
response_hijack = nil
|
|
435
|
-
|
|
436
|
-
headers.each do |k, vs|
|
|
437
|
-
case k
|
|
438
|
-
when CONTENT_LENGTH2
|
|
439
|
-
content_length = vs
|
|
440
|
-
next
|
|
441
|
-
when TRANSFER_ENCODING
|
|
442
|
-
allow_chunked = false
|
|
443
|
-
content_length = nil
|
|
444
|
-
when CONTENT_TYPE
|
|
445
|
-
next if no_body
|
|
446
|
-
when HIJACK
|
|
447
|
-
response_hijack = vs
|
|
448
|
-
next
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
vs.split(NEWLINE).each do |v|
|
|
452
|
-
lines.append k, colon, v, line_ending
|
|
453
|
-
end
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
if no_body
|
|
457
|
-
lines << line_ending
|
|
458
|
-
fast_write client, lines.to_s
|
|
459
|
-
return keep_alive
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
if include_keepalive_header
|
|
463
|
-
lines << CONNECTION_KEEP_ALIVE
|
|
464
|
-
elsif !keep_alive
|
|
465
|
-
lines << CONNECTION_CLOSE
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
unless response_hijack
|
|
469
|
-
if content_length
|
|
470
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
|
471
|
-
chunked = false
|
|
472
|
-
elsif allow_chunked
|
|
473
|
-
lines << TRANSFER_ENCODING_CHUNKED
|
|
474
|
-
chunked = true
|
|
475
|
-
end
|
|
476
|
-
end
|
|
477
|
-
|
|
478
|
-
lines << line_ending
|
|
479
|
-
|
|
480
|
-
fast_write client, lines.to_s
|
|
481
|
-
|
|
482
|
-
if response_hijack
|
|
483
|
-
response_hijack.call client
|
|
484
|
-
return :async
|
|
485
|
-
end
|
|
486
|
-
|
|
487
|
-
res_body.each do |part|
|
|
488
|
-
if chunked
|
|
489
|
-
client.syswrite part.bytesize.to_s(16)
|
|
490
|
-
client.syswrite line_ending
|
|
491
|
-
fast_write client, part
|
|
492
|
-
client.syswrite line_ending
|
|
493
|
-
else
|
|
494
|
-
fast_write client, part
|
|
495
|
-
end
|
|
496
|
-
|
|
497
|
-
client.flush
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
if chunked
|
|
501
|
-
client.syswrite CLOSE_CHUNKED
|
|
502
|
-
client.flush
|
|
644
|
+
@thread_pool.shutdown
|
|
503
645
|
end
|
|
504
|
-
|
|
505
|
-
ensure
|
|
506
|
-
uncork_socket client
|
|
507
|
-
|
|
508
|
-
body.close
|
|
509
|
-
res_body.close if res_body.respond_to? :close
|
|
510
|
-
|
|
511
|
-
after_reply.each { |o| o.call }
|
|
512
646
|
end
|
|
513
|
-
|
|
514
|
-
return keep_alive
|
|
515
647
|
end
|
|
516
648
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
content_length = cl.to_i
|
|
526
|
-
|
|
527
|
-
remain = content_length - body.bytesize
|
|
528
|
-
|
|
529
|
-
return StringIO.new(body) if remain <= 0
|
|
530
|
-
|
|
531
|
-
# Use a Tempfile if there is a lot of data left
|
|
532
|
-
if remain > MAX_BODY
|
|
533
|
-
stream = Tempfile.new(Const::PUMA_TMP_BASE)
|
|
534
|
-
stream.binmode
|
|
649
|
+
def notify_safely(message)
|
|
650
|
+
@notify << message
|
|
651
|
+
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
|
652
|
+
# The server, in another thread, is shutting down
|
|
653
|
+
rescue RuntimeError => e
|
|
654
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
|
655
|
+
if e.message.include?('IOError')
|
|
656
|
+
# ignore
|
|
535
657
|
else
|
|
536
|
-
|
|
537
|
-
# encoding as body.
|
|
538
|
-
stream = StringIO.new body[0,0]
|
|
539
|
-
end
|
|
540
|
-
|
|
541
|
-
stream.write body
|
|
542
|
-
|
|
543
|
-
# Read an odd sized chunk so we can read even sized ones
|
|
544
|
-
# after this
|
|
545
|
-
chunk = client.readpartial(remain % CHUNK_SIZE)
|
|
546
|
-
|
|
547
|
-
# No chunk means a closed socket
|
|
548
|
-
unless chunk
|
|
549
|
-
stream.close
|
|
550
|
-
return nil
|
|
658
|
+
raise e
|
|
551
659
|
end
|
|
660
|
+
end
|
|
661
|
+
private :notify_safely
|
|
552
662
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
# Raed the rest of the chunks
|
|
556
|
-
while remain > 0
|
|
557
|
-
chunk = client.readpartial(CHUNK_SIZE)
|
|
558
|
-
unless chunk
|
|
559
|
-
stream.close
|
|
560
|
-
return nil
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
remain -= stream.write(chunk)
|
|
564
|
-
end
|
|
663
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
|
664
|
+
# off the request queue before finally exiting.
|
|
565
665
|
|
|
566
|
-
|
|
666
|
+
def stop(sync=false)
|
|
667
|
+
notify_safely(STOP_COMMAND)
|
|
668
|
+
@thread.join if @thread && sync
|
|
669
|
+
end
|
|
567
670
|
|
|
568
|
-
|
|
671
|
+
def halt(sync=false)
|
|
672
|
+
notify_safely(HALT_COMMAND)
|
|
673
|
+
@thread.join if @thread && sync
|
|
569
674
|
end
|
|
570
675
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
[500, {}, ["Puma caught this error: #{e}\n#{e.backtrace.join("\n")}"]]
|
|
676
|
+
def begin_restart(sync=false)
|
|
677
|
+
notify_safely(RESTART_COMMAND)
|
|
678
|
+
@thread.join if @thread && sync
|
|
575
679
|
end
|
|
576
680
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
def graceful_shutdown
|
|
580
|
-
@thread_pool.shutdown if @thread_pool
|
|
681
|
+
def shutting_down?
|
|
682
|
+
@status == :stop || @status == :restart
|
|
581
683
|
end
|
|
582
684
|
|
|
583
|
-
#
|
|
584
|
-
#
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
685
|
+
# List of methods invoked by #stats.
|
|
686
|
+
# @version 5.0.0
|
|
687
|
+
STAT_METHODS = [
|
|
688
|
+
:backlog,
|
|
689
|
+
:running,
|
|
690
|
+
:pool_capacity,
|
|
691
|
+
:busy_threads,
|
|
692
|
+
:backlog_max,
|
|
693
|
+
:max_threads,
|
|
694
|
+
:requests_count,
|
|
695
|
+
:reactor_max,
|
|
696
|
+
].freeze
|
|
697
|
+
|
|
698
|
+
# Returns a hash of stats about the running server for reporting purposes.
|
|
699
|
+
# @version 5.0.0
|
|
700
|
+
# @!attribute [r] stats
|
|
701
|
+
# @return [Hash] hash containing stat info from `Server` and `ThreadPool`
|
|
702
|
+
def stats
|
|
703
|
+
stats = @thread_pool&.stats || {}
|
|
704
|
+
stats[:max_threads] = @max_threads
|
|
705
|
+
stats[:requests_count] = @requests_count
|
|
706
|
+
stats[:reactor_max] = @reactor.reactor_max if @reactor
|
|
707
|
+
reset_max
|
|
708
|
+
stats
|
|
709
|
+
end
|
|
592
710
|
|
|
593
|
-
|
|
711
|
+
def reset_max
|
|
712
|
+
@reactor.reactor_max = 0 if @reactor
|
|
713
|
+
@thread_pool&.reset_max
|
|
594
714
|
end
|
|
595
715
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
@notify << HALT_COMMAND
|
|
599
|
-
rescue IOError
|
|
600
|
-
# The server, in another thread, is shutting down
|
|
601
|
-
end
|
|
716
|
+
# below are 'delegations' to binder
|
|
717
|
+
# remove in Puma 7?
|
|
602
718
|
|
|
603
|
-
|
|
719
|
+
|
|
720
|
+
def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
|
|
721
|
+
@binder.add_tcp_listener host, port, optimize_for_latency, backlog
|
|
604
722
|
end
|
|
605
723
|
|
|
606
|
-
def
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
rescue IOError
|
|
610
|
-
# The server, in another thread, is shutting down
|
|
611
|
-
end
|
|
724
|
+
def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
|
|
725
|
+
backlog = 1024)
|
|
726
|
+
@binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
|
|
612
727
|
end
|
|
613
728
|
|
|
614
|
-
def
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
begin
|
|
618
|
-
n = io.syswrite str
|
|
619
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
620
|
-
IO.select(nil, [io], nil, 1)
|
|
621
|
-
retry
|
|
622
|
-
end
|
|
729
|
+
def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
|
|
730
|
+
@binder.add_unix_listener path, umask, mode, backlog
|
|
731
|
+
end
|
|
623
732
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
733
|
+
# @!attribute [r] connected_ports
|
|
734
|
+
def connected_ports
|
|
735
|
+
@binder.connected_ports
|
|
627
736
|
end
|
|
628
|
-
private :fast_write
|
|
629
737
|
end
|
|
630
738
|
end
|