puma 6.6.0 → 7.0.0.pre1
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 +4 -4
- data/History.md +26 -4
- data/README.md +8 -8
- data/docs/signals.md +2 -2
- data/docs/stats.md +2 -1
- data/docs/systemd.md +1 -1
- data/ext/puma_http11/org/jruby/puma/Http11.java +1 -1
- data/lib/puma/client.rb +60 -19
- data/lib/puma/cluster/worker.rb +9 -8
- data/lib/puma/cluster/worker_handle.rb +35 -5
- data/lib/puma/cluster.rb +29 -12
- data/lib/puma/configuration.rb +4 -6
- data/lib/puma/const.rb +2 -4
- data/lib/puma/dsl.rb +20 -19
- data/lib/puma/reactor.rb +11 -0
- data/lib/puma/request.rb +16 -21
- data/lib/puma/server.rb +58 -36
- data/lib/puma/single.rb +1 -1
- data/lib/puma/thread_pool.rb +24 -67
- data/tools/Dockerfile +3 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 695cb479d32857b62538bba8ced9800338e2d36d2eb988cbb79b64f71bcfd549
|
4
|
+
data.tar.gz: 131ea05b36e3408f9611e69bc42c9301a934d9cfc271093ab5bef7b9b8558752
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90b89539f0ed2d5bf8dd6832f3cbffe93df0318b1ce8c7ecf051e86b3f6c057f8ab7c98e99f660f15625ed63a4ae05b5fedbaf882d6fd3d17a4f1a193a922c33
|
7
|
+
data.tar.gz: 0423f25bc1e9286b1b001074c2a7544c88d7b770d3018b738bc7aaad926c2767d2269908f5e180cd6c6cb9e5612b384eedddf7523752cd8737f81bedcf165009
|
data/History.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## 7.0.0.pre1 / 2025-07-31
|
2
|
+
|
3
|
+
* Changed
|
4
|
+
* Fix long tail response problem with keepalive connections ([#3678])
|
5
|
+
|
6
|
+
## 6.6.1 / 2025-07-30
|
7
|
+
|
8
|
+
* Bugfixes
|
9
|
+
* Accept `to_path` to be `nil` on request bodies ([#3635])
|
10
|
+
* Fix single runner stats before the server start ([#3572])
|
11
|
+
* Fix incomplete worker boot state on refork ([#3601])
|
12
|
+
* Improve HttpParserError messages for better debugging ([#3586])
|
13
|
+
* Fix refork logs to distinguish from phased restarts ([#3598])
|
14
|
+
* Fix `rack.after_reply` so it doesn't interrupt chain on error ([#3680])
|
15
|
+
|
1
16
|
## 6.6.0 / 2025-01-29
|
2
17
|
|
3
18
|
* Features
|
@@ -163,7 +178,7 @@
|
|
163
178
|
|
164
179
|
* Features
|
165
180
|
* Ability to supply a custom logger ([#2770], [#2511])
|
166
|
-
* Warn when
|
181
|
+
* Warn when cluster mode-only hooks are defined in single mode ([#3089])
|
167
182
|
* Adds the on_booted event ([#2709])
|
168
183
|
|
169
184
|
* Bugfixes
|
@@ -758,7 +773,7 @@ Each patchlevel release contains a separate security fix. We recommend simply up
|
|
758
773
|
* Fix Java 8 support ([#1773])
|
759
774
|
* Fix error `uninitialized constant Puma::Cluster` ([#1731])
|
760
775
|
* Fix `not_token` being able to be set to true ([#1803])
|
761
|
-
* Fix "Hang on SIGTERM with ruby 2.6 in
|
776
|
+
* Fix "Hang on SIGTERM with ruby 2.6 in cluster mode" (PR [#1741], [#1674], [#1720], [#1730], [#1755])
|
762
777
|
|
763
778
|
## 3.12.1 / 2019-03-19
|
764
779
|
|
@@ -1169,7 +1184,7 @@ Each patchlevel release contains a separate security fix. We recommend simply up
|
|
1169
1184
|
* 4 minor features:
|
1170
1185
|
|
1171
1186
|
* Listen to unix socket with provided backlog if any
|
1172
|
-
* Improves the
|
1187
|
+
* Improves the cluster mode stats to report worker stats
|
1173
1188
|
* Pass the env to the lowlevel_error handler. Fixes [#854]
|
1174
1189
|
* Treat path-like hosts as unix sockets. Fixes [#824]
|
1175
1190
|
|
@@ -1896,7 +1911,7 @@ The "clearly I don't have enough tests for the config" release.
|
|
1896
1911
|
|
1897
1912
|
* 3 doc changes:
|
1898
1913
|
* Add note about on_worker_boot hook
|
1899
|
-
* Add some documentation for
|
1914
|
+
* Add some documentation for Cluster mode
|
1900
1915
|
* Added quotes to /etc/puma.conf
|
1901
1916
|
|
1902
1917
|
## 2.0.1 / 2013-04-30
|
@@ -2150,6 +2165,13 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
2150
2165
|
* Bugfixes
|
2151
2166
|
* Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
|
2152
2167
|
|
2168
|
+
[#3678]:https://github.com/puma/puma/pull/3678 "PR by @MSP-Greg, merged 2025-07-31"
|
2169
|
+
[#3680]:https://github.com/puma/puma/pull/3680 "PR by @byroot, merged 2025-07-31"
|
2170
|
+
[#3572]:https://github.com/puma/puma/pull/3572 "PR by @barthez, merged 2025-02-06"
|
2171
|
+
[#3586]:https://github.com/puma/puma/pull/3586 "PR by @MSP-Greg, merged 2025-02-03"
|
2172
|
+
[#3598]:https://github.com/puma/puma/pull/3598 "PR by @joshuay03, merged 2025-01-31"
|
2173
|
+
[#3601]:https://github.com/puma/puma/pull/3601 "PR by @joshuay03, merged 2025-01-31"
|
2174
|
+
[#3635]:https://github.com/puma/puma/pull/3635 "PR by @LevitatingBusinessMan, merged 2025-05-08"
|
2153
2175
|
[#3570]:https://github.com/puma/puma/pull/3570 "PR by @mohamedhafez, merged 2024-12-30"
|
2154
2176
|
[#3567]:https://github.com/puma/puma/issues/3567 "Issue by @mohamedhafez, closed 2024-12-30"
|
2155
2177
|
[#3383]:https://github.com/puma/puma/pull/3383 "PR by @joshuay03, merged 2024-11-29"
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
# Puma: A Ruby Web Server Built For Parallelism
|
6
6
|
|
7
|
-
[](https://github.com/puma/puma/actions/workflows/tests.yml?query=branch%3Amaster)
|
8
8
|
[](https://codeclimate.com/github/puma/puma)
|
9
9
|
[]( https://stackoverflow.com/questions/tagged/puma )
|
10
10
|
|
@@ -102,9 +102,9 @@ Puma will automatically scale the number of threads, from the minimum until it c
|
|
102
102
|
|
103
103
|
Be aware that additionally Puma creates threads on its own for internal purposes (e.g. handling slow clients). So, even if you specify -t 1:1, expect around 7 threads created in your application.
|
104
104
|
|
105
|
-
###
|
105
|
+
### Cluster mode
|
106
106
|
|
107
|
-
Puma also offers "
|
107
|
+
Puma also offers "cluster mode". Cluster mode `fork`s workers from a master process. Each child process still has its own thread pool. You can tune the number of workers with the `-w` (or `--workers`) flag:
|
108
108
|
|
109
109
|
```
|
110
110
|
$ puma -t 8:32 -w 3
|
@@ -116,13 +116,13 @@ Or with the `WEB_CONCURRENCY` environment variable:
|
|
116
116
|
$ WEB_CONCURRENCY=3 puma -t 8:32
|
117
117
|
```
|
118
118
|
|
119
|
-
Note that threads are still used in
|
119
|
+
Note that threads are still used in cluster mode, and the `-t` thread flag setting is per worker, so `-w 2 -t 16:16` will spawn 32 threads in total, with 16 in each worker process.
|
120
120
|
|
121
121
|
If the `WEB_CONCURRENCY` environment variable is set to `"auto"` and the `concurrent-ruby` gem is available in your application, Puma will set the worker process count to the result of [available processors](https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent.html#available_processor_count-class_method).
|
122
122
|
|
123
123
|
For an in-depth discussion of the tradeoffs of thread and process count settings, [see our docs](https://github.com/puma/puma/blob/9282a8efa5a0c48e39c60d22ca70051a25df9f55/docs/kubernetes.md#workers-per-pod-and-other-config-issues).
|
124
124
|
|
125
|
-
In
|
125
|
+
In cluster mode, Puma can "preload" your application. This loads all the application code *prior* to forking. Preloading reduces total memory usage of your application via an operating system feature called [copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write).
|
126
126
|
|
127
127
|
If the `WEB_CONCURRENCY` environment variable is set to a value > 1 (and `--prune-bundler` has not been specified), preloading will be enabled by default. Otherwise, you can use the `--preload` flag from the command line:
|
128
128
|
|
@@ -140,9 +140,9 @@ preload_app!
|
|
140
140
|
|
141
141
|
Preloading can’t be used with phased restart, since phased restart kills and restarts workers one-by-one, and preloading copies the code of master into the workers.
|
142
142
|
|
143
|
-
####
|
143
|
+
#### Cluster mode hooks
|
144
144
|
|
145
|
-
When using
|
145
|
+
When using cluster mode, Puma's configuration DSL provides `before_fork` and `on_worker_boot`
|
146
146
|
hooks to run code when the master process forks and child workers are booted respectively.
|
147
147
|
|
148
148
|
It is recommended to use these hooks with `preload_app!`, otherwise constants loaded by your
|
@@ -176,7 +176,7 @@ after_refork do
|
|
176
176
|
end
|
177
177
|
```
|
178
178
|
|
179
|
-
Importantly, note the following considerations when Ruby forks a child process:
|
179
|
+
Importantly, note the following considerations when Ruby forks a child process:
|
180
180
|
|
181
181
|
1. File descriptors such as network sockets **are** copied from the parent to the forked
|
182
182
|
child process. Dual-use of the same sockets by parent and child will result in I/O conflicts
|
data/docs/signals.md
CHANGED
@@ -17,13 +17,13 @@ $ ps aux | grep tail
|
|
17
17
|
schneems 87152 0.0 0.0 2432772 492 s032 S+ 12:46PM 0:00.00 tail -f my.log
|
18
18
|
```
|
19
19
|
|
20
|
-
You can send a signal in Ruby using the [Process module](https://ruby-
|
20
|
+
You can send a signal in Ruby using the [Process module](https://docs.ruby-lang.org/en/master/Process.html#method-c-kill):
|
21
21
|
|
22
22
|
```
|
23
23
|
$ irb
|
24
24
|
> puts pid
|
25
25
|
=> 87152
|
26
|
-
Process.detach(pid) # https://ruby-
|
26
|
+
Process.detach(pid) # https://docs.ruby-lang.org/en/master/Process.html#method-c-detach
|
27
27
|
Process.kill("TERM", pid)
|
28
28
|
```
|
29
29
|
|
data/docs/stats.md
CHANGED
@@ -65,7 +65,8 @@ When Puma runs in single mode, these stats are available at the top level. When
|
|
65
65
|
and is not used for any internal decisions, unlike `busy_theads`, which is usually a more useful stat.
|
66
66
|
* max_threads: the maximum number of threads Puma is configured to spool per worker
|
67
67
|
* requests_count: the number of requests this worker has served since starting
|
68
|
-
|
68
|
+
* reactor_max: the maximum observed number of requests held in Puma's "reactor" which is used for asyncronously buffering request bodies. This stat is reset on every call, so it's the maximum value observed since the last stat call.
|
69
|
+
* backlog_max: the maximum number of requests that have been fully buffered by the reactor and placed in a ready queue, but have not yet been picked up by a server thread. This stat is reset on every call, so it's the maximum value observed since the last stat call.
|
69
70
|
|
70
71
|
### cluster mode
|
71
72
|
|
data/docs/systemd.md
CHANGED
@@ -72,7 +72,7 @@ systemd and Puma also support socket activation, where systemd opens the
|
|
72
72
|
listening socket(s) in advance and provides them to the Puma master process on
|
73
73
|
startup. Among other advantages, this keeps listening sockets open across puma
|
74
74
|
restarts and achieves graceful restarts, including when upgraded Puma, and is
|
75
|
-
compatible with both
|
75
|
+
compatible with both cluster mode and application preload.
|
76
76
|
|
77
77
|
**Note:** Any wrapper scripts which `exec`, or other indirections in `ExecStart`
|
78
78
|
may result in activated socket file descriptors being closed before reaching the
|
@@ -29,7 +29,7 @@ public class Http11 extends RubyObject {
|
|
29
29
|
public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
|
30
30
|
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
|
31
31
|
public final static int MAX_FRAGMENT_LENGTH = 1024;
|
32
|
-
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element
|
32
|
+
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element FRAGMENT is longer than the 1024 allowed length.";
|
33
33
|
public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
|
34
34
|
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
|
35
35
|
public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
|
data/lib/puma/client.rb
CHANGED
@@ -111,7 +111,8 @@ module Puma
|
|
111
111
|
end
|
112
112
|
|
113
113
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
114
|
-
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
114
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
115
|
+
:requests_served
|
115
116
|
|
116
117
|
attr_writer :peerip, :http_content_length_limit
|
117
118
|
|
@@ -150,11 +151,12 @@ module Puma
|
|
150
151
|
end
|
151
152
|
|
152
153
|
# Number of seconds until the timeout elapses.
|
154
|
+
# @!attribute [r] timeout
|
153
155
|
def timeout
|
154
156
|
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
155
157
|
end
|
156
158
|
|
157
|
-
def reset
|
159
|
+
def reset
|
158
160
|
@parser.reset
|
159
161
|
@io_buffer.reset
|
160
162
|
@read_header = true
|
@@ -166,11 +168,14 @@ module Puma
|
|
166
168
|
@peerip = nil if @remote_addr_header
|
167
169
|
@in_last_chunk = false
|
168
170
|
@http_content_length_limit_exceeded = false
|
171
|
+
end
|
169
172
|
|
173
|
+
# only used with back-to-back requests contained in the buffer
|
174
|
+
def process_back_to_back_requests
|
170
175
|
if @buffer
|
171
176
|
return false unless try_to_parse_proxy_protocol
|
172
177
|
|
173
|
-
@parsed_bytes =
|
178
|
+
@parsed_bytes = parser_execute
|
174
179
|
|
175
180
|
if @parser.finished?
|
176
181
|
return setup_body
|
@@ -178,19 +183,15 @@ module Puma
|
|
178
183
|
raise HttpParserError,
|
179
184
|
"HEADER is longer than allowed, aborting client early."
|
180
185
|
end
|
181
|
-
|
182
|
-
return false
|
183
|
-
else
|
184
|
-
begin
|
185
|
-
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
186
|
-
return try_to_finish
|
187
|
-
end
|
188
|
-
rescue IOError
|
189
|
-
# swallow it
|
190
|
-
end
|
191
186
|
end
|
192
187
|
end
|
193
188
|
|
189
|
+
# if a client sends back-to-back requests, the buffer may contain one or more
|
190
|
+
# of them.
|
191
|
+
def has_back_to_back_requests?
|
192
|
+
!(@buffer.nil? || @buffer.empty?)
|
193
|
+
end
|
194
|
+
|
194
195
|
def close
|
195
196
|
tempfile_close
|
196
197
|
begin
|
@@ -273,26 +274,28 @@ module Puma
|
|
273
274
|
|
274
275
|
return false unless try_to_parse_proxy_protocol
|
275
276
|
|
276
|
-
@parsed_bytes =
|
277
|
+
@parsed_bytes = parser_execute
|
277
278
|
|
278
279
|
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
279
280
|
@http_content_length_limit_exceeded = true
|
280
281
|
end
|
281
282
|
|
282
283
|
if @parser.finished?
|
283
|
-
|
284
|
+
setup_body
|
284
285
|
elsif @parsed_bytes >= MAX_HEADER
|
285
286
|
raise HttpParserError,
|
286
287
|
"HEADER is longer than allowed, aborting client early."
|
288
|
+
else
|
289
|
+
false
|
287
290
|
end
|
288
|
-
|
289
|
-
false
|
290
291
|
end
|
291
292
|
|
292
293
|
def eagerly_finish
|
293
294
|
return true if @ready
|
294
|
-
|
295
|
-
|
295
|
+
while @to_io.wait_readable(0) # rubocop: disable Style/WhileUntilModifier
|
296
|
+
return true if try_to_finish
|
297
|
+
end
|
298
|
+
false
|
296
299
|
end
|
297
300
|
|
298
301
|
def finish(timeout)
|
@@ -300,6 +303,44 @@ module Puma
|
|
300
303
|
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
301
304
|
end
|
302
305
|
|
306
|
+
# Wraps `@parser.execute` and adds meaningful error messages
|
307
|
+
# @return [Integer] bytes of buffer read by parser
|
308
|
+
#
|
309
|
+
def parser_execute
|
310
|
+
@parser.execute(@env, @buffer, @parsed_bytes)
|
311
|
+
rescue => e
|
312
|
+
@env[HTTP_CONNECTION] = 'close'
|
313
|
+
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
314
|
+
|
315
|
+
req, _ = @buffer.split "\r\n\r\n"
|
316
|
+
request_line, headers = req.split "\r\n", 2
|
317
|
+
|
318
|
+
# below checks for request issues and changes error message accordingly
|
319
|
+
if !@env.key? REQUEST_METHOD
|
320
|
+
if request_line.count(' ') != 2
|
321
|
+
# maybe this is an SSL connection ?
|
322
|
+
raise e
|
323
|
+
else
|
324
|
+
method = request_line[/\A[^ ]+/]
|
325
|
+
raise e, "Invalid HTTP format, parsing fails. Bad method #{method}"
|
326
|
+
end
|
327
|
+
elsif !@env.key? REQUEST_PATH
|
328
|
+
path = request_line[/\A[^ ]+ +([^ ?\r\n]+)/, 1]
|
329
|
+
raise e, "Invalid HTTP format, parsing fails. Bad path #{path}"
|
330
|
+
elsif request_line.match?(/\A[^ ]+ +[^ ?\r\n]+\?/) && !@env.key?(QUERY_STRING)
|
331
|
+
query = request_line[/\A[^ ]+ +[^? ]+\?([^ ]+)/, 1]
|
332
|
+
raise e, "Invalid HTTP format, parsing fails. Bad query #{query}"
|
333
|
+
elsif !@env.key? SERVER_PROTOCOL
|
334
|
+
# protocol is bad
|
335
|
+
text = request_line[/[^ ]*\z/]
|
336
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad protocol #{text}"
|
337
|
+
elsif !headers.empty?
|
338
|
+
# headers are bad
|
339
|
+
hdrs = headers.split("\r\n").map { |h| h.gsub "\n", '\n'}.join "\n"
|
340
|
+
raise HttpParserError, "Invalid HTTP format, parsing fails. Bad headers\n#{hdrs}"
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
303
344
|
def timeout!
|
304
345
|
write_error(408) if in_data_phase
|
305
346
|
raise ConnectionError
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -128,14 +128,15 @@ module Puma
|
|
128
128
|
|
129
129
|
while true
|
130
130
|
begin
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
io << payload
|
131
|
+
payload = base_payload.dup
|
132
|
+
|
133
|
+
hsh = server.stats
|
134
|
+
hsh.each do |k, v|
|
135
|
+
payload << %Q! "#{k}":#{v || 0},!
|
136
|
+
end
|
137
|
+
# sub call properly adds 'closing' string
|
138
|
+
io << payload.sub(/,\z/, " }\n")
|
139
|
+
server.reset_max
|
139
140
|
rescue IOError
|
140
141
|
Puma::Util.purge_interrupt_queue
|
141
142
|
break
|
@@ -4,13 +4,15 @@ module Puma
|
|
4
4
|
class Cluster < Runner
|
5
5
|
#—————————————————————— DO NOT USE — this class is for internal use only ———
|
6
6
|
|
7
|
-
|
8
7
|
# This class represents a worker process from the perspective of the puma
|
9
8
|
# master process. It contains information about the process and its health
|
10
9
|
# and it exposes methods to control the process via IPC. It does not
|
11
10
|
# include the actual logic executed by the worker process itself. For that,
|
12
11
|
# see Puma::Cluster::Worker.
|
13
12
|
class WorkerHandle # :nodoc:
|
13
|
+
# array of stat 'max' keys
|
14
|
+
WORKER_MAX_KEYS = [:backlog_max, :reactor_max]
|
15
|
+
|
14
16
|
def initialize(idx, pid, phase, options)
|
15
17
|
@index = idx
|
16
18
|
@pid = pid
|
@@ -23,6 +25,7 @@ module Puma
|
|
23
25
|
@last_checkin = Time.now
|
24
26
|
@last_status = {}
|
25
27
|
@term = false
|
28
|
+
@worker_max = Array.new WORKER_MAX_KEYS.length, 0
|
26
29
|
end
|
27
30
|
|
28
31
|
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
@@ -51,12 +54,39 @@ module Puma
|
|
51
54
|
@term
|
52
55
|
end
|
53
56
|
|
54
|
-
STATUS_PATTERN = /{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads":(?<max_threads>\d*), "requests_count":(?<requests_count>\d*), "busy_threads":(?<busy_threads>\d*) }/
|
55
|
-
private_constant :STATUS_PATTERN
|
56
|
-
|
57
57
|
def ping!(status)
|
58
|
+
hsh = {}
|
59
|
+
k, v = nil, nil
|
60
|
+
# @todo remove each once Ruby 2.5 is no longer supported
|
61
|
+
status.tr('}{"', '').strip.split(", ").each do |kv|
|
62
|
+
cntr = 0
|
63
|
+
kv.split(':').each do |t|
|
64
|
+
if cntr == 0
|
65
|
+
k = t
|
66
|
+
cntr = 1
|
67
|
+
else
|
68
|
+
v = t
|
69
|
+
end
|
70
|
+
end
|
71
|
+
hsh[k.to_sym] = v.to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
# check stat max values, we can't signal workers to reset the max values,
|
75
|
+
# so we do so here
|
76
|
+
WORKER_MAX_KEYS.each_with_index do |key, idx|
|
77
|
+
if hsh[key] < @worker_max[idx]
|
78
|
+
hsh[key] = @worker_max[idx]
|
79
|
+
else
|
80
|
+
@worker_max[idx] = hsh[key]
|
81
|
+
end
|
82
|
+
end
|
58
83
|
@last_checkin = Time.now
|
59
|
-
@last_status =
|
84
|
+
@last_status = hsh
|
85
|
+
end
|
86
|
+
|
87
|
+
# Resets max values to zero. Called whenever `Cluster#stats` is called
|
88
|
+
def reset_max
|
89
|
+
WORKER_MAX_KEYS.length.times { |idx| @worker_max[idx] = 0 }
|
60
90
|
end
|
61
91
|
|
62
92
|
# @see Puma::Cluster#check_workers
|
data/lib/puma/cluster.rb
CHANGED
@@ -22,6 +22,7 @@ module Puma
|
|
22
22
|
@workers = []
|
23
23
|
@next_check = Time.now
|
24
24
|
|
25
|
+
@worker_max = [] # keeps track of 'max' stat values
|
25
26
|
@phased_restart = false
|
26
27
|
end
|
27
28
|
|
@@ -44,10 +45,15 @@ module Puma
|
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
|
-
def start_phased_restart
|
48
|
+
def start_phased_restart(refork = false)
|
48
49
|
@events.fire_on_restart!
|
50
|
+
|
49
51
|
@phase += 1
|
50
|
-
|
52
|
+
if refork
|
53
|
+
log "- Starting worker refork, phase: #{@phase}"
|
54
|
+
else
|
55
|
+
log "- Starting phased worker restart, phase: #{@phase}"
|
56
|
+
end
|
51
57
|
|
52
58
|
# Be sure to change the directory again before loading
|
53
59
|
# the app. This way we can pick up new code.
|
@@ -166,7 +172,7 @@ module Puma
|
|
166
172
|
(@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
|
167
173
|
end
|
168
174
|
|
169
|
-
def check_workers
|
175
|
+
def check_workers(refork = false)
|
170
176
|
return if @next_check >= Time.now
|
171
177
|
|
172
178
|
@next_check = Time.now + @options[:worker_check_interval]
|
@@ -184,7 +190,12 @@ module Puma
|
|
184
190
|
w = @workers.find { |x| x.phase != @phase }
|
185
191
|
|
186
192
|
if w
|
187
|
-
|
193
|
+
if refork
|
194
|
+
log "- Stopping #{w.pid} for refork..."
|
195
|
+
else
|
196
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
197
|
+
end
|
198
|
+
|
188
199
|
unless w.term?
|
189
200
|
w.term
|
190
201
|
log "- #{w.signal} sent to #{w.pid}..."
|
@@ -228,7 +239,7 @@ module Puma
|
|
228
239
|
def phased_restart(refork = false)
|
229
240
|
return false if @options[:preload_app] && !refork
|
230
241
|
|
231
|
-
@phased_restart = true
|
242
|
+
@phased_restart = refork ? :refork : true
|
232
243
|
wakeup!
|
233
244
|
|
234
245
|
true
|
@@ -258,11 +269,14 @@ module Puma
|
|
258
269
|
end
|
259
270
|
|
260
271
|
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
261
|
-
# the master process.
|
272
|
+
# the master process. Calling this also resets stat 'max' values to zero.
|
262
273
|
# @!attribute [r] stats
|
274
|
+
# @return [Hash]
|
275
|
+
|
263
276
|
def stats
|
264
277
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
265
278
|
worker_status = @workers.map do |w|
|
279
|
+
w.reset_max
|
266
280
|
{
|
267
281
|
started_at: utc_iso8601(w.started_at),
|
268
282
|
pid: w.pid,
|
@@ -273,7 +287,6 @@ module Puma
|
|
273
287
|
last_status: w.last_status,
|
274
288
|
}
|
275
289
|
end
|
276
|
-
|
277
290
|
{
|
278
291
|
started_at: utc_iso8601(@started_at),
|
279
292
|
workers: @workers.size,
|
@@ -368,7 +381,7 @@ module Puma
|
|
368
381
|
|
369
382
|
before = Thread.list.reject(&fork_safe)
|
370
383
|
|
371
|
-
log "* Restarts: (\u2714) hot (\u2716) phased"
|
384
|
+
log "* Restarts: (\u2714) hot (\u2716) phased (#{@options[:fork_worker] ? "\u2714" : "\u2716"}) refork"
|
372
385
|
log "* Preloading application"
|
373
386
|
load_and_bind
|
374
387
|
|
@@ -386,7 +399,7 @@ module Puma
|
|
386
399
|
end
|
387
400
|
end
|
388
401
|
else
|
389
|
-
log "* Restarts: (\u2714) hot (\u2714) phased"
|
402
|
+
log "* Restarts: (\u2714) hot (\u2714) phased (#{@options[:fork_worker] ? "\u2714" : "\u2716"}) refork"
|
390
403
|
|
391
404
|
unless @config.app_configured?
|
392
405
|
error "No application configured, nothing to run"
|
@@ -448,13 +461,17 @@ module Puma
|
|
448
461
|
end
|
449
462
|
|
450
463
|
if @phased_restart
|
451
|
-
start_phased_restart
|
464
|
+
start_phased_restart(@phased_restart == :refork)
|
465
|
+
|
466
|
+
in_phased_restart = @phased_restart
|
452
467
|
@phased_restart = false
|
453
|
-
|
468
|
+
|
454
469
|
workers_not_booted = @options[:workers]
|
470
|
+
# worker 0 is not restarted on refork
|
471
|
+
workers_not_booted -= 1 if in_phased_restart == :refork
|
455
472
|
end
|
456
473
|
|
457
|
-
check_workers
|
474
|
+
check_workers(in_phased_restart == :refork)
|
458
475
|
|
459
476
|
if read.wait_readable([0, @next_check - Time.now].max)
|
460
477
|
req = read.read_nonblock(1)
|
data/lib/puma/configuration.rb
CHANGED
@@ -140,12 +140,10 @@ module Puma
|
|
140
140
|
io_selector_backend: :auto,
|
141
141
|
log_requests: false,
|
142
142
|
logger: STDOUT,
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
|
147
|
-
# well behaved client from monopolizing the thread forever.
|
148
|
-
max_fast_inline: 10,
|
143
|
+
# Limits how many requests a keep alive connection can make.
|
144
|
+
# The connection will be closed after it reaches `max_keep_alive`
|
145
|
+
# requests.
|
146
|
+
max_keep_alive: 25,
|
149
147
|
max_threads: Puma.mri? ? 5 : 16,
|
150
148
|
min_threads: 0,
|
151
149
|
mode: :http,
|
data/lib/puma/const.rb
CHANGED
@@ -100,13 +100,11 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "7.0.0.pre1"
|
104
|
+
CODE_NAME = "Romantic Warrior"
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
107
107
|
|
108
|
-
FAST_TRACK_KA_TIMEOUT = 0.2
|
109
|
-
|
110
108
|
# How long to wait when getting some write blocking on the socket when
|
111
109
|
# sending data back
|
112
110
|
WRITE_TIMEOUT = 10
|
data/lib/puma/dsl.rb
CHANGED
@@ -654,8 +654,6 @@ module Puma
|
|
654
654
|
# @example
|
655
655
|
# state_permission 0600
|
656
656
|
#
|
657
|
-
# @version 5.0.0
|
658
|
-
#
|
659
657
|
def state_permission(permission)
|
660
658
|
@options[:state_permission] = permission
|
661
659
|
end
|
@@ -811,7 +809,7 @@ module Puma
|
|
811
809
|
|
812
810
|
alias_method :after_worker_boot, :after_worker_fork
|
813
811
|
|
814
|
-
# Code to run after puma is booted (works for both
|
812
|
+
# Code to run after puma is booted (works for both single and cluster modes).
|
815
813
|
#
|
816
814
|
# @example
|
817
815
|
# on_booted do
|
@@ -822,7 +820,7 @@ module Puma
|
|
822
820
|
@config.options[:events].on_booted(&block)
|
823
821
|
end
|
824
822
|
|
825
|
-
# Code to run after puma is stopped (works for both
|
823
|
+
# Code to run after puma is stopped (works for both single and cluster modes).
|
826
824
|
#
|
827
825
|
# @example
|
828
826
|
# on_stopped do
|
@@ -851,8 +849,6 @@ module Puma
|
|
851
849
|
# 3.times {GC.start}
|
852
850
|
# end
|
853
851
|
#
|
854
|
-
# @version 5.0.0
|
855
|
-
#
|
856
852
|
def on_refork(key = nil, &block)
|
857
853
|
warn_if_in_single_mode('on_refork')
|
858
854
|
|
@@ -1111,7 +1107,7 @@ module Puma
|
|
1111
1107
|
|
1112
1108
|
# Set the timeout for worker shutdown.
|
1113
1109
|
#
|
1114
|
-
# The default is
|
1110
|
+
# The default is 30 seconds.
|
1115
1111
|
#
|
1116
1112
|
# @note Cluster mode only.
|
1117
1113
|
#
|
@@ -1143,10 +1139,10 @@ module Puma
|
|
1143
1139
|
# @see Puma::Cluster#cull_workers
|
1144
1140
|
#
|
1145
1141
|
def worker_culling_strategy(strategy)
|
1146
|
-
|
1142
|
+
strategy = strategy.to_sym
|
1147
1143
|
|
1148
1144
|
if ![:youngest, :oldest].include?(strategy)
|
1149
|
-
raise "Invalid value for worker_culling_strategy - #{
|
1145
|
+
raise "Invalid value for worker_culling_strategy - #{strategy}"
|
1150
1146
|
end
|
1151
1147
|
|
1152
1148
|
@options[:worker_culling_strategy] = strategy
|
@@ -1194,8 +1190,6 @@ module Puma
|
|
1194
1190
|
# @see Puma::Server#handle_servers
|
1195
1191
|
# @see Puma::ThreadPool#wait_for_less_busy_worker
|
1196
1192
|
#
|
1197
|
-
# @version 5.0.0
|
1198
|
-
#
|
1199
1193
|
def wait_for_less_busy_worker(val=0.005)
|
1200
1194
|
@options[:wait_for_less_busy_worker] = val.to_f
|
1201
1195
|
end
|
@@ -1269,24 +1263,31 @@ module Puma
|
|
1269
1263
|
# A refork will automatically trigger once after the specified number of requests
|
1270
1264
|
# (default 1000), or pass 0 to disable auto refork.
|
1271
1265
|
#
|
1266
|
+
# @note This is experimental.
|
1272
1267
|
# @note Cluster mode only.
|
1273
1268
|
#
|
1274
|
-
# @version 5.0.0
|
1275
|
-
#
|
1276
1269
|
def fork_worker(after_requests=1000)
|
1277
1270
|
@options[:fork_worker] = Integer(after_requests)
|
1278
1271
|
end
|
1279
1272
|
|
1280
|
-
#
|
1281
|
-
# the reactor to be subject to normal ordering.
|
1273
|
+
# @deprecated Use {#max_keep_alive} instead.
|
1282
1274
|
#
|
1283
|
-
|
1275
|
+
def max_fast_inline(num_of_requests)
|
1276
|
+
warn "[WARNING] `max_fast_inline` is deprecated use `max_keep_alive` instead"
|
1277
|
+
@options[:max_keep_alive] ||= Float(num_of_requests) unless num_of_requests.nil?
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
# The number of requests a keep-alive client can submit before being closed.
|
1281
|
+
# Note that some applications (server to server) may benefit from a very high
|
1282
|
+
# number or Float::INFINITY.
|
1283
|
+
#
|
1284
|
+
# The default is 25.
|
1284
1285
|
#
|
1285
1286
|
# @example
|
1286
|
-
#
|
1287
|
+
# max_keep_alive 20
|
1287
1288
|
#
|
1288
|
-
def
|
1289
|
-
@options[:
|
1289
|
+
def max_keep_alive(num_of_requests)
|
1290
|
+
@options[:max_keep_alive] = Float(num_of_requests) unless num_of_requests.nil?
|
1290
1291
|
end
|
1291
1292
|
|
1292
1293
|
# When `true`, keep-alive connections are maintained on inbound requests.
|
data/lib/puma/reactor.rb
CHANGED
@@ -15,6 +15,12 @@ module Puma
|
|
15
15
|
#
|
16
16
|
# The implementation uses a Queue to synchronize adding new objects from the internal select loop.
|
17
17
|
class Reactor
|
18
|
+
|
19
|
+
# @!attribute [rw] reactor_max
|
20
|
+
# Maximum number of clients in the selector. Reset with calls to `Server.stats`.
|
21
|
+
attr_accessor :reactor_max
|
22
|
+
attr_reader :reactor_size
|
23
|
+
|
18
24
|
# Create a new Reactor to monitor IO objects added by #add.
|
19
25
|
# The provided block will be invoked when an IO has data available to read,
|
20
26
|
# its timeout elapses, or when the Reactor shuts down.
|
@@ -29,6 +35,8 @@ module Puma
|
|
29
35
|
@input = Queue.new
|
30
36
|
@timeouts = []
|
31
37
|
@block = block
|
38
|
+
@reactor_size = 0
|
39
|
+
@reactor_max = 0
|
32
40
|
end
|
33
41
|
|
34
42
|
# Run the internal select loop, using a background thread by default.
|
@@ -108,6 +116,8 @@ module Puma
|
|
108
116
|
# Start monitoring the object.
|
109
117
|
def register(client)
|
110
118
|
@selector.register(client.to_io, :r).value = client
|
119
|
+
@reactor_size += 1
|
120
|
+
@reactor_max = @reactor_size if @reactor_max < @reactor_size
|
111
121
|
@timeouts << client
|
112
122
|
rescue ArgumentError
|
113
123
|
# unreadable clients raise error when processed by NIO
|
@@ -118,6 +128,7 @@ module Puma
|
|
118
128
|
def wakeup!(client)
|
119
129
|
if @block.call client
|
120
130
|
@selector.deregister client.to_io
|
131
|
+
@reactor_size -= 1
|
121
132
|
@timeouts.delete client
|
122
133
|
end
|
123
134
|
end
|
data/lib/puma/request.rb
CHANGED
@@ -135,12 +135,15 @@ module Puma
|
|
135
135
|
uncork_socket client.io
|
136
136
|
app_body.close if app_body.respond_to? :close
|
137
137
|
client&.tempfile_close
|
138
|
-
after_reply = env[RACK_AFTER_REPLY]
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
138
|
+
if after_reply = env[RACK_AFTER_REPLY]
|
139
|
+
after_reply.each do |o|
|
140
|
+
begin
|
141
|
+
o.call
|
142
|
+
rescue StandardError => e
|
143
|
+
@log_writer.debug_error e
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
144
147
|
end
|
145
148
|
|
146
149
|
# Assembles the headers and prepares the body for actually sending the
|
@@ -161,17 +164,7 @@ module Puma
|
|
161
164
|
return false if closed_socket?(socket)
|
162
165
|
|
163
166
|
# Close the connection after a reasonable number of inline requests
|
164
|
-
|
165
|
-
# This allows Puma to service connections fairly when the number
|
166
|
-
# of concurrent connections exceeds the size of the threadpool.
|
167
|
-
force_keep_alive = if @enable_keep_alives
|
168
|
-
requests < @max_fast_inline ||
|
169
|
-
@thread_pool.busy_threads < @max_threads ||
|
170
|
-
!client.listener.to_io.wait_readable(0)
|
171
|
-
else
|
172
|
-
# Always set force_keep_alive to false if the server has keep-alives not enabled.
|
173
|
-
false
|
174
|
-
end
|
167
|
+
force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
|
175
168
|
|
176
169
|
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
177
170
|
|
@@ -191,7 +184,8 @@ module Puma
|
|
191
184
|
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
192
185
|
body = res_body
|
193
186
|
content_length = body.size
|
194
|
-
elsif res_body.respond_to?(:to_path) &&
|
187
|
+
elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
|
188
|
+
File.readable?(fn)
|
195
189
|
body = File.open fn, 'rb'
|
196
190
|
content_length = body.size
|
197
191
|
close_body = true
|
@@ -199,7 +193,7 @@ module Puma
|
|
199
193
|
body = res_body
|
200
194
|
end
|
201
195
|
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
202
|
-
File.readable?(fn = res_body.to_path)
|
196
|
+
(fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
|
203
197
|
body = File.open fn, 'rb'
|
204
198
|
content_length = body.size
|
205
199
|
close_body = true
|
@@ -263,7 +257,8 @@ module Puma
|
|
263
257
|
|
264
258
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
265
259
|
body.close if close_body
|
266
|
-
keep_alive
|
260
|
+
# if we're shutting down, close keep_alive connections
|
261
|
+
!shutting_down? && keep_alive
|
267
262
|
end
|
268
263
|
|
269
264
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -581,7 +576,7 @@ module Puma
|
|
581
576
|
# response body
|
582
577
|
# @param io_buffer [Puma::IOBuffer] modified inn place
|
583
578
|
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
584
|
-
# status and `@
|
579
|
+
# status and `@max_keep_alive`
|
585
580
|
# @return [Hash] resp_info
|
586
581
|
# @version 5.0.3
|
587
582
|
#
|
data/lib/puma/server.rb
CHANGED
@@ -94,8 +94,9 @@ module Puma
|
|
94
94
|
@min_threads = @options[:min_threads]
|
95
95
|
@max_threads = @options[:max_threads]
|
96
96
|
@queue_requests = @options[:queue_requests]
|
97
|
-
@
|
97
|
+
@max_keep_alive = @options[:max_keep_alive]
|
98
98
|
@enable_keep_alives = @options[:enable_keep_alives]
|
99
|
+
@enable_keep_alives &&= @queue_requests
|
99
100
|
@io_selector_backend = @options[:io_selector_backend]
|
100
101
|
@http_content_length_limit = @options[:http_content_length_limit]
|
101
102
|
|
@@ -220,7 +221,6 @@ module Puma
|
|
220
221
|
@thread_pool&.spawned
|
221
222
|
end
|
222
223
|
|
223
|
-
|
224
224
|
# This number represents the number of requests that
|
225
225
|
# the server is capable of taking right now.
|
226
226
|
#
|
@@ -324,6 +324,7 @@ module Puma
|
|
324
324
|
pool = @thread_pool
|
325
325
|
queue_requests = @queue_requests
|
326
326
|
drain = options[:drain_on_shutdown] ? 0 : nil
|
327
|
+
max_flt = @max_threads.to_f
|
327
328
|
|
328
329
|
addr_send_name, addr_value = case options[:remote_address]
|
329
330
|
when :value
|
@@ -364,8 +365,20 @@ module Puma
|
|
364
365
|
if sock == check
|
365
366
|
break if handle_check
|
366
367
|
else
|
367
|
-
|
368
|
-
|
368
|
+
# if ThreadPool out_of_band code is running, we don't want to add
|
369
|
+
# clients until the code is finished.
|
370
|
+
sleep 0.001 while pool.out_of_band_running
|
371
|
+
|
372
|
+
# only use delay when clustered and busy
|
373
|
+
if pool.busy_threads >= @max_threads
|
374
|
+
if @clustered
|
375
|
+
delay = 0.0001 * ((@reactor&.reactor_size || 0) + pool.busy_threads * 1.5)/max_flt
|
376
|
+
sleep delay
|
377
|
+
else
|
378
|
+
# use small sleep for busy single worker
|
379
|
+
sleep 0.0001
|
380
|
+
end
|
381
|
+
end
|
369
382
|
|
370
383
|
io = begin
|
371
384
|
sock.accept_nonblock
|
@@ -453,8 +466,7 @@ module Puma
|
|
453
466
|
requests = 0
|
454
467
|
|
455
468
|
begin
|
456
|
-
if @queue_requests &&
|
457
|
-
!client.eagerly_finish
|
469
|
+
if @queue_requests && !client.eagerly_finish
|
458
470
|
|
459
471
|
client.set_timeout(@first_data_timeout)
|
460
472
|
if @reactor.add client
|
@@ -467,39 +479,33 @@ module Puma
|
|
467
479
|
client.finish(@first_data_timeout)
|
468
480
|
end
|
469
481
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
break
|
478
|
-
when true
|
479
|
-
ThreadPool.clean_thread_locals if clean_thread_locals
|
480
|
-
|
481
|
-
requests += 1
|
482
|
+
@requests_count += 1
|
483
|
+
case handle_request(client, requests + 1)
|
484
|
+
when false
|
485
|
+
when :async
|
486
|
+
close_socket = false
|
487
|
+
when true
|
488
|
+
ThreadPool.clean_thread_locals if clean_thread_locals
|
482
489
|
|
483
|
-
|
484
|
-
# socket for a short time before returning to the reactor.
|
485
|
-
fast_check = @status == :run
|
490
|
+
requests += 1
|
486
491
|
|
487
|
-
|
488
|
-
# number of inline requests if there are other requests pending.
|
489
|
-
fast_check = false if requests >= @max_fast_inline &&
|
490
|
-
@thread_pool.backlog > 0
|
492
|
+
client.reset
|
491
493
|
|
492
|
-
|
493
|
-
|
494
|
-
|
494
|
+
# This indicates data exists in the client read buffer and there may be
|
495
|
+
# additional requests on it, so process them
|
496
|
+
next_request_ready = if client.has_back_to_back_requests?
|
497
|
+
with_force_shutdown(client) { client.process_back_to_back_requests }
|
498
|
+
else
|
499
|
+
nil
|
500
|
+
end
|
495
501
|
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
502
|
+
if next_request_ready
|
503
|
+
@thread_pool << client
|
504
|
+
close_socket = false
|
505
|
+
elsif @queue_requests
|
506
|
+
client.set_timeout @persistent_timeout
|
507
|
+
if @reactor.add client
|
508
|
+
close_socket = false
|
503
509
|
end
|
504
510
|
end
|
505
511
|
end
|
@@ -650,7 +656,16 @@ module Puma
|
|
650
656
|
|
651
657
|
# List of methods invoked by #stats.
|
652
658
|
# @version 5.0.0
|
653
|
-
STAT_METHODS = [
|
659
|
+
STAT_METHODS = [
|
660
|
+
:backlog,
|
661
|
+
:running,
|
662
|
+
:pool_capacity,
|
663
|
+
:busy_threads,
|
664
|
+
:backlog_max,
|
665
|
+
:max_threads,
|
666
|
+
:requests_count,
|
667
|
+
:reactor_max,
|
668
|
+
].freeze
|
654
669
|
|
655
670
|
# Returns a hash of stats about the running server for reporting purposes.
|
656
671
|
# @version 5.0.0
|
@@ -660,9 +675,16 @@ module Puma
|
|
660
675
|
stats = @thread_pool&.stats || {}
|
661
676
|
stats[:max_threads] = @max_threads
|
662
677
|
stats[:requests_count] = @requests_count
|
678
|
+
stats[:reactor_max] = @reactor.reactor_max
|
679
|
+
reset_max
|
663
680
|
stats
|
664
681
|
end
|
665
682
|
|
683
|
+
def reset_max
|
684
|
+
@reactor.reactor_max = 0
|
685
|
+
@thread_pool.reset_max
|
686
|
+
end
|
687
|
+
|
666
688
|
# below are 'delegations' to binder
|
667
689
|
# remove in Puma 7?
|
668
690
|
|
data/lib/puma/single.rb
CHANGED
data/lib/puma/thread_pool.rb
CHANGED
@@ -25,6 +25,8 @@ module Puma
|
|
25
25
|
# up its work before leaving the thread to die on the vine.
|
26
26
|
SHUTDOWN_GRACE_TIME = 5 # seconds
|
27
27
|
|
28
|
+
attr_reader :out_of_band_running
|
29
|
+
|
28
30
|
# Maintain a minimum of +min+ and maximum of +max+ threads
|
29
31
|
# in the pool.
|
30
32
|
#
|
@@ -35,9 +37,9 @@ module Puma
|
|
35
37
|
@not_empty = ConditionVariable.new
|
36
38
|
@not_full = ConditionVariable.new
|
37
39
|
@mutex = Mutex.new
|
40
|
+
@todo = Queue.new
|
38
41
|
|
39
|
-
@
|
40
|
-
|
42
|
+
@backlog_max = 0
|
41
43
|
@spawned = 0
|
42
44
|
@waiting = 0
|
43
45
|
|
@@ -50,6 +52,7 @@ module Puma
|
|
50
52
|
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
51
53
|
@block = block
|
52
54
|
@out_of_band = options[:out_of_band]
|
55
|
+
@out_of_band_running = false
|
53
56
|
@clean_thread_locals = options[:clean_thread_locals]
|
54
57
|
@before_thread_start = options[:before_thread_start]
|
55
58
|
@before_thread_exit = options[:before_thread_exit]
|
@@ -89,20 +92,33 @@ module Puma
|
|
89
92
|
# @return [Hash] hash containing stat info from ThreadPool
|
90
93
|
def stats
|
91
94
|
with_mutex do
|
95
|
+
temp = @backlog_max
|
96
|
+
@backlog_max = 0
|
92
97
|
{ backlog: @todo.size,
|
93
98
|
running: @spawned,
|
94
99
|
pool_capacity: @waiting + (@max - @spawned),
|
95
|
-
busy_threads: @spawned - @waiting + @todo.size
|
100
|
+
busy_threads: @spawned - @waiting + @todo.size,
|
101
|
+
backlog_max: temp
|
96
102
|
}
|
97
103
|
end
|
98
104
|
end
|
99
105
|
|
106
|
+
def reset_max
|
107
|
+
with_mutex { @backlog_max = 0 }
|
108
|
+
end
|
109
|
+
|
100
110
|
# How many objects have yet to be processed by the pool?
|
101
111
|
#
|
102
112
|
def backlog
|
103
113
|
with_mutex { @todo.size }
|
104
114
|
end
|
105
115
|
|
116
|
+
# The maximum size of the backlog
|
117
|
+
#
|
118
|
+
def backlog_max
|
119
|
+
with_mutex { @backlog_max }
|
120
|
+
end
|
121
|
+
|
106
122
|
# @!attribute [r] pool_capacity
|
107
123
|
def pool_capacity
|
108
124
|
waiting + (@max - spawned)
|
@@ -214,12 +230,14 @@ module Puma
|
|
214
230
|
|
215
231
|
# we execute on idle hook when all threads are free
|
216
232
|
return false unless @spawned == @waiting
|
217
|
-
|
233
|
+
@out_of_band_running = true
|
218
234
|
@out_of_band.each(&:call)
|
219
235
|
true
|
220
236
|
rescue Exception => e
|
221
237
|
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
222
238
|
true
|
239
|
+
ensure
|
240
|
+
@out_of_band_running = false
|
223
241
|
end
|
224
242
|
|
225
243
|
private :trigger_out_of_band_hook
|
@@ -239,6 +257,8 @@ module Puma
|
|
239
257
|
end
|
240
258
|
|
241
259
|
@todo << work
|
260
|
+
t = @todo.size
|
261
|
+
@backlog_max = t if t > @backlog_max
|
242
262
|
|
243
263
|
if @waiting < @todo.size and @spawned < @max
|
244
264
|
spawn_thread
|
@@ -248,69 +268,6 @@ module Puma
|
|
248
268
|
end
|
249
269
|
end
|
250
270
|
|
251
|
-
# This method is used by `Puma::Server` to let the server know when
|
252
|
-
# the thread pool can pull more requests from the socket and
|
253
|
-
# pass to the reactor.
|
254
|
-
#
|
255
|
-
# The general idea is that the thread pool can only work on a fixed
|
256
|
-
# number of requests at the same time. If it is already processing that
|
257
|
-
# number of requests then it is at capacity. If another Puma process has
|
258
|
-
# spare capacity, then the request can be left on the socket so the other
|
259
|
-
# worker can pick it up and process it.
|
260
|
-
#
|
261
|
-
# For example: if there are 5 threads, but only 4 working on
|
262
|
-
# requests, this method will not wait and the `Puma::Server`
|
263
|
-
# can pull a request right away.
|
264
|
-
#
|
265
|
-
# If there are 5 threads and all 5 of them are busy, then it will
|
266
|
-
# pause here, and wait until the `not_full` condition variable is
|
267
|
-
# signaled, usually this indicates that a request has been processed.
|
268
|
-
#
|
269
|
-
# It's important to note that even though the server might accept another
|
270
|
-
# request, it might not be added to the `@todo` array right away.
|
271
|
-
# For example if a slow client has only sent a header, but not a body
|
272
|
-
# then the `@todo` array would stay the same size as the reactor works
|
273
|
-
# to try to buffer the request. In that scenario the next call to this
|
274
|
-
# method would not block and another request would be added into the reactor
|
275
|
-
# by the server. This would continue until a fully buffered request
|
276
|
-
# makes it through the reactor and can then be processed by the thread pool.
|
277
|
-
def wait_until_not_full
|
278
|
-
with_mutex do
|
279
|
-
while true
|
280
|
-
return if @shutdown
|
281
|
-
|
282
|
-
# If we can still spin up new threads and there
|
283
|
-
# is work queued that cannot be handled by waiting
|
284
|
-
# threads, then accept more work until we would
|
285
|
-
# spin up the max number of threads.
|
286
|
-
return if busy_threads < @max
|
287
|
-
|
288
|
-
@not_full.wait @mutex
|
289
|
-
end
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
# @version 5.0.0
|
294
|
-
def wait_for_less_busy_worker(delay_s)
|
295
|
-
return unless delay_s && delay_s > 0
|
296
|
-
|
297
|
-
# Ruby MRI does GVL, this can result
|
298
|
-
# in processing contention when multiple threads
|
299
|
-
# (requests) are running concurrently
|
300
|
-
return unless Puma.mri?
|
301
|
-
|
302
|
-
with_mutex do
|
303
|
-
return if @shutdown
|
304
|
-
|
305
|
-
# do not delay, if we are not busy
|
306
|
-
return unless busy_threads > 0
|
307
|
-
|
308
|
-
# this will be signaled once a request finishes,
|
309
|
-
# which can happen earlier than delay
|
310
|
-
@not_full.wait @mutex, delay_s
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
271
|
# If there are any free threads in the pool, tell one to go ahead
|
315
272
|
# and exit. If +force+ is true, then a trim request is requested
|
316
273
|
# even if all threads are being utilized.
|
data/tools/Dockerfile
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
FROM ruby:3.2
|
4
4
|
|
5
|
+
RUN apt-get update && apt-get install -y ragel
|
6
|
+
|
5
7
|
# throw errors if Gemfile has been modified since Gemfile.lock
|
6
8
|
RUN bundle config --global frozen 1
|
7
9
|
|
@@ -13,4 +15,4 @@ RUN bundle install
|
|
13
15
|
RUN bundle exec rake compile
|
14
16
|
|
15
17
|
EXPOSE 9292
|
16
|
-
CMD bundle exec bin/puma test/rackup/hello.ru
|
18
|
+
CMD ["bundle", "exec", "bin/puma", "test/rackup/hello.ru"]
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 7.0.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Phoenix
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: nio4r
|
@@ -145,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
145
|
- !ruby/object:Gem::Version
|
146
146
|
version: '0'
|
147
147
|
requirements: []
|
148
|
-
rubygems_version: 3.6.
|
148
|
+
rubygems_version: 3.6.9
|
149
149
|
specification_version: 4
|
150
150
|
summary: A Ruby/Rack web server built for parallelism.
|
151
151
|
test_files: []
|