puma 3.4.0 → 3.12.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/{History.txt → History.md} +356 -74
- data/README.md +143 -227
- data/docs/architecture.md +36 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
- 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/plugins.md +28 -0
- data/docs/restart.md +39 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +124 -22
- data/ext/puma_http11/extconf.rb +2 -0
- data/ext/puma_http11/http11_parser.c +291 -447
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/io_buffer.c +7 -7
- data/ext/puma_http11/mini_ssl.c +67 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +76 -94
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
- data/ext/puma_http11/puma_http11.c +1 -0
- data/lib/puma.rb +13 -5
- data/lib/puma/app/status.rb +8 -0
- data/lib/puma/binder.rb +46 -21
- data/lib/puma/cli.rb +49 -33
- data/lib/puma/client.rb +149 -4
- data/lib/puma/cluster.rb +55 -13
- data/lib/puma/commonlogger.rb +19 -20
- data/lib/puma/compat.rb +3 -7
- data/lib/puma/configuration.rb +136 -131
- data/lib/puma/const.rb +19 -37
- data/lib/puma/control_cli.rb +38 -35
- data/lib/puma/convenient.rb +3 -3
- data/lib/puma/detect.rb +3 -1
- data/lib/puma/dsl.rb +86 -57
- data/lib/puma/events.rb +17 -13
- data/lib/puma/io_buffer.rb +1 -1
- data/lib/puma/jruby_restart.rb +0 -1
- data/lib/puma/launcher.rb +61 -30
- data/lib/puma/minissl.rb +85 -4
- data/lib/puma/null_io.rb +6 -13
- data/lib/puma/plugin.rb +12 -1
- data/lib/puma/plugin/tmp_restart.rb +1 -2
- data/lib/puma/rack/builder.rb +3 -0
- data/lib/puma/rack/urlmap.rb +9 -8
- data/lib/puma/reactor.rb +144 -0
- data/lib/puma/runner.rb +27 -1
- data/lib/puma/server.rb +135 -33
- data/lib/puma/single.rb +17 -3
- data/lib/puma/tcp_logger.rb +8 -1
- data/lib/puma/thread_pool.rb +70 -20
- data/lib/puma/util.rb +1 -5
- data/lib/rack/handler/puma.rb +58 -17
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +9 -2
- data/tools/jungle/init.d/puma +85 -58
- data/tools/jungle/init.d/run-puma +16 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/jungle/upstart/puma.conf +1 -1
- data/tools/trickletest.rb +1 -1
- metadata +22 -94
- data/Gemfile +0 -13
- data/Manifest.txt +0 -78
- data/Rakefile +0 -158
- data/docs/config.md +0 -0
- data/lib/puma/rack/backports/uri/common_18.rb +0 -59
- data/lib/puma/rack/backports/uri/common_192.rb +0 -55
- data/puma.gemspec +0 -52
data/lib/puma/null_io.rb
CHANGED
@@ -1,42 +1,35 @@
|
|
1
1
|
module Puma
|
2
|
-
|
3
2
|
# Provides an IO-like object that always appears to contain no data.
|
4
3
|
# Used as the value for rack.input when the request has no body.
|
5
4
|
#
|
6
5
|
class NullIO
|
7
|
-
# Always returns nil
|
8
|
-
#
|
9
6
|
def gets
|
10
7
|
nil
|
11
8
|
end
|
12
9
|
|
13
|
-
# Never yields
|
14
|
-
#
|
15
10
|
def each
|
16
11
|
end
|
17
12
|
|
18
|
-
# Mimics IO#read with no data
|
13
|
+
# Mimics IO#read with no data.
|
19
14
|
#
|
20
|
-
def read(count=nil,
|
15
|
+
def read(count = nil, _buffer = nil)
|
21
16
|
(count && count > 0) ? nil : ""
|
22
17
|
end
|
23
18
|
|
24
|
-
# Does nothing
|
25
|
-
#
|
26
19
|
def rewind
|
27
20
|
end
|
28
21
|
|
29
|
-
# Does nothing
|
30
|
-
#
|
31
22
|
def close
|
32
23
|
end
|
33
24
|
|
34
|
-
# Always zero
|
35
|
-
#
|
36
25
|
def size
|
37
26
|
0
|
38
27
|
end
|
39
28
|
|
29
|
+
def eof?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
40
33
|
def sync=(v)
|
41
34
|
end
|
42
35
|
|
data/lib/puma/plugin.rb
CHANGED
@@ -28,6 +28,7 @@ module Puma
|
|
28
28
|
class PluginRegistry
|
29
29
|
def initialize
|
30
30
|
@plugins = {}
|
31
|
+
@background = []
|
31
32
|
end
|
32
33
|
|
33
34
|
def register(name, cls)
|
@@ -53,6 +54,16 @@ module Puma
|
|
53
54
|
|
54
55
|
raise UnknownPlugin, "file failed to register a plugin"
|
55
56
|
end
|
57
|
+
|
58
|
+
def add_background(blk)
|
59
|
+
@background << blk
|
60
|
+
end
|
61
|
+
|
62
|
+
def fire_background
|
63
|
+
@background.each do |b|
|
64
|
+
Thread.new(&b)
|
65
|
+
end
|
66
|
+
end
|
56
67
|
end
|
57
68
|
|
58
69
|
Plugins = PluginRegistry.new
|
@@ -93,7 +104,7 @@ module Puma
|
|
93
104
|
end
|
94
105
|
|
95
106
|
def in_background(&blk)
|
96
|
-
|
107
|
+
Plugins.add_background blk
|
97
108
|
end
|
98
109
|
|
99
110
|
def workers_supported?
|
@@ -8,7 +8,7 @@ Puma::Plugin.create do
|
|
8
8
|
|
9
9
|
# If we can't write to the path, then just don't bother with this plugin
|
10
10
|
begin
|
11
|
-
File.write
|
11
|
+
File.write(path, "") unless File.exist?(path)
|
12
12
|
orig = File.stat(path).mtime
|
13
13
|
rescue SystemCallError
|
14
14
|
return
|
@@ -32,4 +32,3 @@ Puma::Plugin.create do
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
|
-
|
data/lib/puma/rack/builder.rb
CHANGED
data/lib/puma/rack/urlmap.rb
CHANGED
@@ -43,15 +43,17 @@ module Puma::Rack
|
|
43
43
|
def call(env)
|
44
44
|
path = env['PATH_INFO']
|
45
45
|
script_name = env['SCRIPT_NAME']
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
http_host = env['HTTP_HOST']
|
47
|
+
server_name = env['SERVER_NAME']
|
48
|
+
server_port = env['SERVER_PORT']
|
49
|
+
|
50
|
+
is_same_server = casecmp?(http_host, server_name) ||
|
51
|
+
casecmp?(http_host, "#{server_name}:#{server_port}")
|
49
52
|
|
50
53
|
@mapping.each do |host, location, match, app|
|
51
|
-
unless casecmp?(
|
52
|
-
|| casecmp?(
|
53
|
-
|| (!host &&
|
54
|
-
casecmp?(hHost, sName+':'+sPort)))
|
54
|
+
unless casecmp?(http_host, host) \
|
55
|
+
|| casecmp?(server_name, host) \
|
56
|
+
|| (!host && is_same_server)
|
55
57
|
next
|
56
58
|
end
|
57
59
|
|
@@ -87,4 +89,3 @@ module Puma::Rack
|
|
87
89
|
end
|
88
90
|
end
|
89
91
|
end
|
90
|
-
|
data/lib/puma/reactor.rb
CHANGED
@@ -2,15 +2,54 @@ require 'puma/util'
|
|
2
2
|
require 'puma/minissl'
|
3
3
|
|
4
4
|
module Puma
|
5
|
+
# Internal Docs, Not a public interface.
|
6
|
+
#
|
7
|
+
# The Reactor object is responsible for ensuring that a request has been
|
8
|
+
# completely received before it starts to be processed. This may be known as read buffering.
|
9
|
+
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
10
|
+
# such as nginx) then the application would be subject to a slow client attack.
|
11
|
+
#
|
12
|
+
# Each Puma "worker" process has its own Reactor. For example if you start puma with `$ puma -w 5` then
|
13
|
+
# it will have 5 workers and each worker will have it's own reactor.
|
14
|
+
#
|
15
|
+
# For a graphical representation of how the reactor works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
|
16
|
+
#
|
17
|
+
# ## Reactor Flow
|
18
|
+
#
|
19
|
+
# A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance.
|
20
|
+
# The reactor stores the request in an array and calls `IO.select` on the array in a loop.
|
21
|
+
#
|
22
|
+
# When the request is written to by the client then the `IO.select` will "wake up" and
|
23
|
+
# return the references to any objects that caused it to "wake". The reactor
|
24
|
+
# then loops through each of these request objects, and sees if they're complete. If they
|
25
|
+
# have a full header and body then the reactor passes the request to a thread pool.
|
26
|
+
# Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
|
27
|
+
#
|
28
|
+
# If the request is not complete, then it stays in the array, and the next time any
|
29
|
+
# data is written to that socket reference, then the loop is woken up and it is checked for completeness again.
|
30
|
+
#
|
31
|
+
# A detailed example is given in the docs for `run_internal` which is where the bulk
|
32
|
+
# of this logic lives.
|
5
33
|
class Reactor
|
6
34
|
DefaultSleepFor = 5
|
7
35
|
|
36
|
+
# Creates an instance of Puma::Reactor
|
37
|
+
#
|
38
|
+
# The `server` argument is an instance of `Puma::Server`
|
39
|
+
# this is used to write a response for "low level errors"
|
40
|
+
# when there is an exception inside of the reactor.
|
41
|
+
#
|
42
|
+
# The `app_pool` is an instance of `Puma::ThreadPool`.
|
43
|
+
# Once a request is fully formed (header and body are received)
|
44
|
+
# it will be passed to the `app_pool`.
|
8
45
|
def initialize(server, app_pool)
|
9
46
|
@server = server
|
10
47
|
@events = server.events
|
11
48
|
@app_pool = app_pool
|
12
49
|
|
13
50
|
@mutex = Mutex.new
|
51
|
+
|
52
|
+
# Read / Write pipes to wake up internal while loop
|
14
53
|
@ready, @trigger = Puma::Util.pipe
|
15
54
|
@input = []
|
16
55
|
@sleep_for = DefaultSleepFor
|
@@ -21,6 +60,64 @@ module Puma
|
|
21
60
|
|
22
61
|
private
|
23
62
|
|
63
|
+
|
64
|
+
# Until a request is added via the `add` method this method will internally
|
65
|
+
# loop, waiting on the `sockets` array objects. The only object in this
|
66
|
+
# array at first is the `@ready` IO object, which is the read end of a pipe
|
67
|
+
# connected to `@trigger` object. When `@trigger` is written to, then the loop
|
68
|
+
# will break on `IO.select` and return an array.
|
69
|
+
#
|
70
|
+
# ## When a request is added:
|
71
|
+
#
|
72
|
+
# When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
|
73
|
+
# Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
|
74
|
+
#
|
75
|
+
# When that happens, the internal loop stops blocking at `IO.select` and returns a reference
|
76
|
+
# to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
|
77
|
+
# When `@trigger` is written-to, the loop "wakes" and the `ready`
|
78
|
+
# variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
|
79
|
+
# first IO object is the `@ready` object. This first array `[#<IO:fd 10>]`
|
80
|
+
# is saved as a `reads` variable.
|
81
|
+
#
|
82
|
+
# The `reads` variable is iterated through. In the case that the object
|
83
|
+
# is the same as the `@ready` input pipe, then we know that there was a `trigger` event.
|
84
|
+
#
|
85
|
+
# If there was a trigger event, then one byte of `@ready` is read into memory. In the case of the first request,
|
86
|
+
# the reactor sees that it's a `"*"` value and the reactor adds the contents of `@input` into the `sockets` array.
|
87
|
+
# The while then loop continues to iterate again, but now the `sockets` array contains a `Puma::Client` instance in addition
|
88
|
+
# to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
|
89
|
+
#
|
90
|
+
# Since the `Puma::Client` in this example has data that has not been read yet,
|
91
|
+
# the `IO.select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
|
92
|
+
# `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
|
93
|
+
#
|
94
|
+
# Each element in the first entry is iterated over. The `Puma::Client` object is not
|
95
|
+
# the `@ready` pipe, so the reactor checks to see if it has the fully header and body with
|
96
|
+
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
97
|
+
# then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
|
98
|
+
# can pick up the request and begin to execute application logic. This is done
|
99
|
+
# via `@app_pool << c`. The `Puma::Client` is then removed from the `sockets` array.
|
100
|
+
#
|
101
|
+
# If the request body is not present then nothing will happen, and the loop will iterate
|
102
|
+
# again. When the client sends more data to the socket the `Puma::Client` object will
|
103
|
+
# wake up the `IO.select` and it can again be checked to see if it's ready to be
|
104
|
+
# passed to the thread pool.
|
105
|
+
#
|
106
|
+
# ## Time Out Case
|
107
|
+
#
|
108
|
+
# In addition to being woken via a write to one of the sockets the `IO.select` will
|
109
|
+
# periodically "time out" of the sleep. One of the functions of this is to check for
|
110
|
+
# any requests that have "timed out". At the end of the loop it's checked to see if
|
111
|
+
# the first element in the `@timeout` array has exceed it's allowed time. If so,
|
112
|
+
# the client object is removed from the timeout aray, a 408 response is written.
|
113
|
+
# Then it's connection is closed, and the object is removed from the `sockets` array
|
114
|
+
# that watches for new data.
|
115
|
+
#
|
116
|
+
# This behavior loops until all the objects that have timed out have been removed.
|
117
|
+
#
|
118
|
+
# Once all the timeouts have been processed, the next duration of the `IO.select` sleep
|
119
|
+
# will be set to be equal to the amount of time it will take for the next timeout to occur.
|
120
|
+
# This calculation happens in `calculate_sleep`.
|
24
121
|
def run_internal
|
25
122
|
sockets = @sockets
|
26
123
|
|
@@ -28,6 +125,7 @@ module Puma
|
|
28
125
|
begin
|
29
126
|
ready = IO.select sockets, nil, nil, @sleep_for
|
30
127
|
rescue IOError => e
|
128
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
31
129
|
if sockets.any? { |socket| socket.closed? }
|
32
130
|
STDERR.puts "Error in select: #{e.message} (#{e.class})"
|
33
131
|
STDERR.puts e.backtrace
|
@@ -75,6 +173,15 @@ module Puma
|
|
75
173
|
sockets.delete c
|
76
174
|
end
|
77
175
|
|
176
|
+
# Don't report these to the lowlevel_error handler, otherwise
|
177
|
+
# will be flooding them with errors when persistent connections
|
178
|
+
# are closed.
|
179
|
+
rescue ConnectionError
|
180
|
+
c.write_500
|
181
|
+
c.close
|
182
|
+
|
183
|
+
sockets.delete c
|
184
|
+
|
78
185
|
# SSL handshake failure
|
79
186
|
rescue MiniSSL::SSLError => e
|
80
187
|
@server.lowlevel_error(e, c.env)
|
@@ -153,6 +260,16 @@ module Puma
|
|
153
260
|
end
|
154
261
|
end
|
155
262
|
|
263
|
+
# The `calculate_sleep` sets the value that the `IO.select` will
|
264
|
+
# sleep for in the main reactor loop when no sockets are being written to.
|
265
|
+
#
|
266
|
+
# The values kept in `@timeouts` are sorted so that the first timeout
|
267
|
+
# comes first in the array. When there are no timeouts the default timeout is used.
|
268
|
+
#
|
269
|
+
# Otherwise a sleep value is set that is the same as the amount of time it
|
270
|
+
# would take for the first element to time out.
|
271
|
+
#
|
272
|
+
# If that value is in the past, then a sleep value of zero is used.
|
156
273
|
def calculate_sleep
|
157
274
|
if @timeouts.empty?
|
158
275
|
@sleep_for = DefaultSleepFor
|
@@ -167,6 +284,31 @@ module Puma
|
|
167
284
|
end
|
168
285
|
end
|
169
286
|
|
287
|
+
# This method adds a connection to the reactor
|
288
|
+
#
|
289
|
+
# Typically called by `Puma::Server` the value passed in
|
290
|
+
# is usually a `Puma::Client` object that responds like an IO
|
291
|
+
# object.
|
292
|
+
#
|
293
|
+
# The main body of the reactor loop is in `run_internal` and it
|
294
|
+
# will sleep on `IO.select`. When a new connection is added to the
|
295
|
+
# reactor it cannot be added directly to the `sockets` aray, because
|
296
|
+
# the `IO.select` will not be watching for it yet.
|
297
|
+
#
|
298
|
+
# Instead what needs to happen is that `IO.select` needs to be woken up,
|
299
|
+
# the contents of `@input` added to the `sockets` array, and then
|
300
|
+
# another call to `IO.select` needs to happen. Since the `Puma::Client`
|
301
|
+
# object can be read immediately, it does not block, but instead returns
|
302
|
+
# right away.
|
303
|
+
#
|
304
|
+
# This behavior is accomplished by writing to `@trigger` which wakes up
|
305
|
+
# the `IO.select` and then there is logic to detect the value of `*`,
|
306
|
+
# pull the contents from `@input` and add them to the sockets array.
|
307
|
+
#
|
308
|
+
# If the object passed in has a timeout value in `timeout_at` then
|
309
|
+
# it is added to a `@timeouts` array. This array is then re-arranged
|
310
|
+
# so that the first element to timeout will be at the front of the
|
311
|
+
# array. Then a value to sleep for is derived in the call to `calculate_sleep`
|
170
312
|
def add(c)
|
171
313
|
@mutex.synchronize do
|
172
314
|
@input << c
|
@@ -186,6 +328,7 @@ module Puma
|
|
186
328
|
begin
|
187
329
|
@trigger << "c"
|
188
330
|
rescue IOError
|
331
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
189
332
|
end
|
190
333
|
end
|
191
334
|
|
@@ -193,6 +336,7 @@ module Puma
|
|
193
336
|
begin
|
194
337
|
@trigger << "!"
|
195
338
|
rescue IOError
|
339
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
196
340
|
end
|
197
341
|
|
198
342
|
@thread.join
|
data/lib/puma/runner.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
+
require 'puma/server'
|
2
|
+
require 'puma/const'
|
3
|
+
|
1
4
|
module Puma
|
5
|
+
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
6
|
+
# serve requests. This class spawns a new instance of `Puma::Server` via
|
7
|
+
# a call to `start_server`.
|
2
8
|
class Runner
|
3
9
|
def initialize(cli, events)
|
4
10
|
@launcher = cli
|
@@ -16,6 +22,10 @@ module Puma
|
|
16
22
|
@options[:environment] == "development"
|
17
23
|
end
|
18
24
|
|
25
|
+
def test?
|
26
|
+
@options[:environment] == "test"
|
27
|
+
end
|
28
|
+
|
19
29
|
def log(str)
|
20
30
|
@events.log str
|
21
31
|
end
|
@@ -94,18 +104,30 @@ module Puma
|
|
94
104
|
end
|
95
105
|
end
|
96
106
|
|
107
|
+
def redirected_io?
|
108
|
+
@options[:redirect_stdout] || @options[:redirect_stderr]
|
109
|
+
end
|
110
|
+
|
97
111
|
def redirect_io
|
98
112
|
stdout = @options[:redirect_stdout]
|
99
113
|
stderr = @options[:redirect_stderr]
|
100
114
|
append = @options[:redirect_append]
|
101
115
|
|
102
116
|
if stdout
|
117
|
+
unless Dir.exist?(File.dirname(stdout))
|
118
|
+
raise "Cannot redirect STDOUT to #{stdout}"
|
119
|
+
end
|
120
|
+
|
103
121
|
STDOUT.reopen stdout, (append ? "a" : "w")
|
104
122
|
STDOUT.sync = true
|
105
123
|
STDOUT.puts "=== puma startup: #{Time.now} ==="
|
106
124
|
end
|
107
125
|
|
108
126
|
if stderr
|
127
|
+
unless Dir.exist?(File.dirname(stderr))
|
128
|
+
raise "Cannot redirect STDERR to #{stderr}"
|
129
|
+
end
|
130
|
+
|
109
131
|
STDERR.reopen stderr, (append ? "a" : "w")
|
110
132
|
STDERR.sync = true
|
111
133
|
STDERR.puts "=== puma startup: #{Time.now} ==="
|
@@ -146,7 +168,11 @@ module Puma
|
|
146
168
|
server.tcp_mode!
|
147
169
|
end
|
148
170
|
|
149
|
-
|
171
|
+
if @options[:early_hints]
|
172
|
+
server.early_hints = true
|
173
|
+
end
|
174
|
+
|
175
|
+
unless development? || test?
|
150
176
|
server.leak_stack_on_error = false
|
151
177
|
end
|
152
178
|
|
data/lib/puma/server.rb
CHANGED
@@ -23,6 +23,15 @@ require 'socket'
|
|
23
23
|
module Puma
|
24
24
|
|
25
25
|
# The HTTP Server itself. Serves out a single Rack app.
|
26
|
+
#
|
27
|
+
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
28
|
+
# to generate one or more `Puma::Server` instances capable of handling requests.
|
29
|
+
# Each Puma process will contain one `Puma::Server` instacne.
|
30
|
+
#
|
31
|
+
# The `Puma::Server` instance pulls requests from the socket, adds them to a
|
32
|
+
# `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
|
33
|
+
#
|
34
|
+
# Each `Puma::Server` will have one reactor and one thread pool.
|
26
35
|
class Server
|
27
36
|
|
28
37
|
include Puma::Const
|
@@ -57,19 +66,19 @@ module Puma
|
|
57
66
|
|
58
67
|
@min_threads = 0
|
59
68
|
@max_threads = 16
|
60
|
-
@auto_trim_time =
|
69
|
+
@auto_trim_time = 30
|
61
70
|
@reaping_time = 1
|
62
71
|
|
63
72
|
@thread = nil
|
64
73
|
@thread_pool = nil
|
74
|
+
@early_hints = nil
|
65
75
|
|
66
|
-
@persistent_timeout = PERSISTENT_TIMEOUT
|
76
|
+
@persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
|
77
|
+
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
67
78
|
|
68
79
|
@binder = Binder.new(events)
|
69
80
|
@own_binder = true
|
70
81
|
|
71
|
-
@first_data_timeout = FIRST_DATA_TIMEOUT
|
72
|
-
|
73
82
|
@leak_stack_on_error = true
|
74
83
|
|
75
84
|
@options = options
|
@@ -78,9 +87,11 @@ module Puma
|
|
78
87
|
ENV['RACK_ENV'] ||= "development"
|
79
88
|
|
80
89
|
@mode = :http
|
90
|
+
|
91
|
+
@precheck_closing = true
|
81
92
|
end
|
82
93
|
|
83
|
-
attr_accessor :binder, :leak_stack_on_error
|
94
|
+
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
84
95
|
|
85
96
|
forward :add_tcp_listener, :@binder
|
86
97
|
forward :add_ssl_listener, :@binder
|
@@ -100,6 +111,8 @@ module Puma
|
|
100
111
|
# packetizes our stream. This improves both latency and throughput.
|
101
112
|
#
|
102
113
|
if RUBY_PLATFORM =~ /linux/
|
114
|
+
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
115
|
+
|
103
116
|
# 6 == Socket::IPPROTO_TCP
|
104
117
|
# 3 == TCP_CORK
|
105
118
|
# 1/0 == turn on/off
|
@@ -107,6 +120,7 @@ module Puma
|
|
107
120
|
begin
|
108
121
|
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
109
122
|
rescue IOError, SystemCallError
|
123
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
110
124
|
end
|
111
125
|
end
|
112
126
|
|
@@ -114,6 +128,24 @@ module Puma
|
|
114
128
|
begin
|
115
129
|
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
116
130
|
rescue IOError, SystemCallError
|
131
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def closed_socket?(socket)
|
136
|
+
return false unless socket.kind_of? TCPSocket
|
137
|
+
return false unless @precheck_closing
|
138
|
+
|
139
|
+
begin
|
140
|
+
tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
|
141
|
+
rescue IOError, SystemCallError
|
142
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
143
|
+
@precheck_closing = false
|
144
|
+
false
|
145
|
+
else
|
146
|
+
state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
|
147
|
+
# TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
|
148
|
+
(state >= 6 && state <= 9) || state == 11
|
117
149
|
end
|
118
150
|
end
|
119
151
|
else
|
@@ -122,6 +154,10 @@ module Puma
|
|
122
154
|
|
123
155
|
def uncork_socket(socket)
|
124
156
|
end
|
157
|
+
|
158
|
+
def closed_socket?(socket)
|
159
|
+
false
|
160
|
+
end
|
125
161
|
end
|
126
162
|
|
127
163
|
def backlog
|
@@ -132,6 +168,18 @@ module Puma
|
|
132
168
|
@thread_pool and @thread_pool.spawned
|
133
169
|
end
|
134
170
|
|
171
|
+
|
172
|
+
# This number represents the number of requests that
|
173
|
+
# the server is capable of taking right now.
|
174
|
+
#
|
175
|
+
# For example if the number is 5 then it means
|
176
|
+
# there are 5 threads sitting idle ready to take
|
177
|
+
# a request. If one request comes in, then the
|
178
|
+
# value would be 4 until it finishes processing.
|
179
|
+
def pool_capacity
|
180
|
+
@thread_pool and @thread_pool.pool_capacity
|
181
|
+
end
|
182
|
+
|
135
183
|
# Lopez Mode == raw tcp apps
|
136
184
|
|
137
185
|
def run_lopez_mode(background=true)
|
@@ -193,7 +241,11 @@ module Puma
|
|
193
241
|
# nothing
|
194
242
|
rescue Errno::ECONNABORTED
|
195
243
|
# client closed the socket even before accept
|
196
|
-
|
244
|
+
begin
|
245
|
+
io.close
|
246
|
+
rescue
|
247
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
248
|
+
end
|
197
249
|
end
|
198
250
|
end
|
199
251
|
end
|
@@ -210,7 +262,12 @@ module Puma
|
|
210
262
|
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
211
263
|
STDERR.puts e.backtrace
|
212
264
|
ensure
|
213
|
-
|
265
|
+
begin
|
266
|
+
@check.close
|
267
|
+
rescue
|
268
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
269
|
+
end
|
270
|
+
|
214
271
|
@notify.close
|
215
272
|
|
216
273
|
if @status != :restart and @own_binder
|
@@ -242,6 +299,10 @@ module Puma
|
|
242
299
|
@thread_pool = ThreadPool.new(@min_threads,
|
243
300
|
@max_threads,
|
244
301
|
IOBuffer) do |client, buffer|
|
302
|
+
|
303
|
+
# Advertise this server into the thread
|
304
|
+
Thread.current[ThreadLocalKey] = self
|
305
|
+
|
245
306
|
process_now = false
|
246
307
|
|
247
308
|
begin
|
@@ -264,7 +325,7 @@ module Puma
|
|
264
325
|
client.close
|
265
326
|
|
266
327
|
@events.parse_error self, client.env, e
|
267
|
-
rescue ConnectionError
|
328
|
+
rescue ConnectionError, EOFError
|
268
329
|
client.close
|
269
330
|
else
|
270
331
|
if process_now
|
@@ -335,13 +396,17 @@ module Puma
|
|
335
396
|
end
|
336
397
|
|
337
398
|
pool << client
|
338
|
-
pool.wait_until_not_full
|
399
|
+
pool.wait_until_not_full
|
339
400
|
end
|
340
401
|
rescue SystemCallError
|
341
402
|
# nothing
|
342
403
|
rescue Errno::ECONNABORTED
|
343
404
|
# client closed the socket even before accept
|
344
|
-
|
405
|
+
begin
|
406
|
+
io.close
|
407
|
+
rescue
|
408
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
409
|
+
end
|
345
410
|
end
|
346
411
|
end
|
347
412
|
end
|
@@ -354,7 +419,7 @@ module Puma
|
|
354
419
|
|
355
420
|
graceful_shutdown if @status == :stop || @status == :restart
|
356
421
|
if queue_requests
|
357
|
-
@reactor.clear!
|
422
|
+
@reactor.clear!
|
358
423
|
@reactor.shutdown
|
359
424
|
end
|
360
425
|
rescue Exception => e
|
@@ -399,6 +464,7 @@ module Puma
|
|
399
464
|
#
|
400
465
|
def process_client(client, buffer)
|
401
466
|
begin
|
467
|
+
|
402
468
|
clean_thread_locals = @options[:clean_thread_locals]
|
403
469
|
close_socket = true
|
404
470
|
|
@@ -462,6 +528,7 @@ module Puma
|
|
462
528
|
begin
|
463
529
|
client.close if close_socket
|
464
530
|
rescue IOError, SystemCallError
|
531
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
465
532
|
# Already closed
|
466
533
|
rescue StandardError => e
|
467
534
|
@events.unknown_error self, e, "Client"
|
@@ -493,7 +560,9 @@ module Puma
|
|
493
560
|
|
494
561
|
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
495
562
|
|
496
|
-
env
|
563
|
+
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
564
|
+
# so only set the env value if there actually is a value.
|
565
|
+
env[QUERY_STRING] = uri.query if uri.query
|
497
566
|
end
|
498
567
|
|
499
568
|
env[PATH_INFO] = env[REQUEST_PATH]
|
@@ -541,6 +610,8 @@ module Puma
|
|
541
610
|
env = req.env
|
542
611
|
client = req.io
|
543
612
|
|
613
|
+
return false if closed_socket?(client)
|
614
|
+
|
544
615
|
normalize_env env, req
|
545
616
|
|
546
617
|
env[PUMA_SOCKET] = client
|
@@ -559,6 +630,24 @@ module Puma
|
|
559
630
|
env[RACK_INPUT] = body
|
560
631
|
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
561
632
|
|
633
|
+
if @early_hints
|
634
|
+
env[EARLY_HINTS] = lambda { |headers|
|
635
|
+
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
636
|
+
|
637
|
+
headers.each_pair do |k, vs|
|
638
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
639
|
+
vs.to_s.split(NEWLINE).each do |v|
|
640
|
+
fast_write client, "#{k}: #{v}\r\n"
|
641
|
+
end
|
642
|
+
else
|
643
|
+
fast_write client, "#{k}: #{vs}\r\n"
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
fast_write client, "\r\n".freeze
|
648
|
+
}
|
649
|
+
end
|
650
|
+
|
562
651
|
# A rack extension. If the app writes #call'ables to this
|
563
652
|
# array, we will invoke them when the request is done.
|
564
653
|
#
|
@@ -587,8 +676,8 @@ module Puma
|
|
587
676
|
headers = {}
|
588
677
|
res_body = ["Request was internally terminated early\n"]
|
589
678
|
|
590
|
-
rescue
|
591
|
-
@events.unknown_error self, e, "Rack app"
|
679
|
+
rescue Exception => e
|
680
|
+
@events.unknown_error self, e, "Rack app", env
|
592
681
|
|
593
682
|
status, headers, res_body = lowlevel_error(e, env)
|
594
683
|
end
|
@@ -656,7 +745,7 @@ module Puma
|
|
656
745
|
next
|
657
746
|
end
|
658
747
|
|
659
|
-
if vs.respond_to?(:to_s)
|
748
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
660
749
|
vs.to_s.split(NEWLINE).each do |v|
|
661
750
|
lines.append k, colon, v, line_ending
|
662
751
|
end
|
@@ -700,8 +789,8 @@ module Puma
|
|
700
789
|
|
701
790
|
begin
|
702
791
|
res_body.each do |part|
|
792
|
+
next if part.bytesize.zero?
|
703
793
|
if chunked
|
704
|
-
next if part.bytesize.zero?
|
705
794
|
fast_write client, part.bytesize.to_s(16)
|
706
795
|
fast_write client, line_ending
|
707
796
|
fast_write client, part
|
@@ -860,35 +949,38 @@ module Puma
|
|
860
949
|
end
|
861
950
|
end
|
862
951
|
|
863
|
-
|
864
|
-
# off the request queue before finally exiting.
|
865
|
-
#
|
866
|
-
def stop(sync=false)
|
952
|
+
def notify_safely(message)
|
867
953
|
begin
|
868
|
-
@notify <<
|
954
|
+
@notify << message
|
869
955
|
rescue IOError
|
870
|
-
|
956
|
+
# The server, in another thread, is shutting down
|
957
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
958
|
+
rescue RuntimeError => e
|
959
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
960
|
+
if e.message.include?('IOError')
|
961
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
962
|
+
else
|
963
|
+
raise e
|
964
|
+
end
|
871
965
|
end
|
966
|
+
end
|
967
|
+
private :notify_safely
|
968
|
+
|
969
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
970
|
+
# off the request queue before finally exiting.
|
872
971
|
|
972
|
+
def stop(sync=false)
|
973
|
+
notify_safely(STOP_COMMAND)
|
873
974
|
@thread.join if @thread && sync
|
874
975
|
end
|
875
976
|
|
876
977
|
def halt(sync=false)
|
877
|
-
|
878
|
-
@notify << HALT_COMMAND
|
879
|
-
rescue IOError
|
880
|
-
# The server, in another thread, is shutting down
|
881
|
-
end
|
882
|
-
|
978
|
+
notify_safely(HALT_COMMAND)
|
883
979
|
@thread.join if @thread && sync
|
884
980
|
end
|
885
981
|
|
886
982
|
def begin_restart
|
887
|
-
|
888
|
-
@notify << RESTART_COMMAND
|
889
|
-
rescue IOError
|
890
|
-
# The server, in another thread, is shutting down
|
891
|
-
end
|
983
|
+
notify_safely(RESTART_COMMAND)
|
892
984
|
end
|
893
985
|
|
894
986
|
def fast_write(io, str)
|
@@ -911,5 +1003,15 @@ module Puma
|
|
911
1003
|
end
|
912
1004
|
end
|
913
1005
|
private :fast_write
|
1006
|
+
|
1007
|
+
ThreadLocalKey = :puma_server
|
1008
|
+
|
1009
|
+
def self.current
|
1010
|
+
Thread.current[ThreadLocalKey]
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
def shutting_down?
|
1014
|
+
@status == :stop || @status == :restart
|
1015
|
+
end
|
914
1016
|
end
|
915
1017
|
end
|