puma 7.0.3 → 7.1.0
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 +40 -1
- data/README.md +7 -2
- data/docs/kubernetes.md +2 -2
- data/docs/stats.md +1 -1
- data/ext/puma_http11/mini_ssl.c +18 -3
- data/ext/puma_http11/org/jruby/puma/Http11.java +9 -1
- data/ext/puma_http11/puma_http11.c +23 -11
- data/lib/puma/client.rb +21 -25
- data/lib/puma/cluster/worker_handle.rb +2 -2
- data/lib/puma/cluster.rb +9 -7
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/configuration.rb +1 -0
- data/lib/puma/const.rb +2 -2
- data/lib/puma/dsl.rb +25 -4
- data/lib/puma/launcher.rb +29 -24
- data/lib/puma/server.rb +78 -60
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +10 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb282989c2e0ee20b007c5b0cdf231caf106816210101572346098f6863502c7
|
4
|
+
data.tar.gz: f4217f8221a64fcd6f5bf2c2249ed8dc6d512ba167e93ef4273b0accb574d26d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2070bae758f22fc8b0784c6b426a7177d65e9039fd2d1969d1241e8f101e46a04afdc5f93f5b6646eb0d9fb15fd6b47f87fb0cee478c3c58e840d3ae852b754a
|
7
|
+
data.tar.gz: 6d35fd83049a36ec8017fb859899fddd0305b90977a14f707f551b1e802d822100bf10126ce43598bf23bbf17deb581135955f61028ee56d0ba3e161718f9d79
|
data/History.md
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
## 7.1.0 / 2025-10-16
|
2
|
+
|
3
|
+
* Features
|
4
|
+
* Introduce `after_worker_shutdown` hook ([#3707])
|
5
|
+
* Reintroduce keepalive "fast inline" behavior. Provides faster (8x on JRuby & 1.4x on Ruby) pipeline processing ([#3794])
|
6
|
+
|
7
|
+
* Bugfixes
|
8
|
+
* Skip reading zero bytes when request body is buffered ([#3795])
|
9
|
+
* Fix `PUMA_LOG_CONFIG=1` logging twice with prune_bundler enabled ([#3778])
|
10
|
+
* Fix prune_bundler not showing in `PUMA_LOG_CONFIG=1` output ([#3779])
|
11
|
+
* Guard ThreadPool method call, which may be nil during shutdown ([#3791], [#3790])
|
12
|
+
* Set `Thread.current.puma_server` in Thread init code, not every request ([#3774])
|
13
|
+
* Fix race condition while deleting pidfile ([#3657])
|
14
|
+
|
15
|
+
## 7.0.4 / 2025-09-23
|
16
|
+
|
17
|
+
* Bugfixes
|
18
|
+
* Fix SSL_shutdown error handling ([#3703])
|
19
|
+
* Strip whitespace from the beginnings of request header values. ([#3742])
|
20
|
+
|
21
|
+
* Performance
|
22
|
+
* puma_http11.c: Use interned UTF-8 strings for hash keys ([#3754])
|
23
|
+
* Move sleep cluster logic to its own class ([#3746], [#3740])
|
24
|
+
|
1
25
|
## 7.0.3 / 2025-09-13
|
2
26
|
|
3
27
|
* Performance
|
@@ -26,7 +50,8 @@
|
|
26
50
|
* Raise an ArgumentError if no block given to hooks ([#3377])
|
27
51
|
* Don't set env['HTTP_VERSION'] for Rack > 3.1 ([#3711], [#3576])
|
28
52
|
* Runner.rb - remove `ruby_engine` method, deprecated Nov-2024 ([#3701])
|
29
|
-
*
|
53
|
+
* Config `preload_app!` is now the default for clustered mode ([#3297])
|
54
|
+
* Config instance must be `clamp`-d before reading any values ([#3297])
|
30
55
|
* Response headers set to lowercase ([#3704])
|
31
56
|
* Update minimum Ruby version to 3.0 ([#3698])
|
32
57
|
* Rename callback hooks ([#3438])
|
@@ -2234,6 +2259,20 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
2234
2259
|
* Bugfixes
|
2235
2260
|
* Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
|
2236
2261
|
|
2262
|
+
[#3707]:https://github.com/puma/puma/pull/3707 "PR by @nerdrew, merged 2025-10-02"
|
2263
|
+
[#3794]:https://github.com/puma/puma/pull/3794 "PR by @schneems, merged 2025-10-16"
|
2264
|
+
[#3795]:https://github.com/puma/puma/pull/3795 "PR by @MSP-Greg, merged 2025-10-16"
|
2265
|
+
[#3778]:https://github.com/puma/puma/pull/3778 "PR by @joshuay03, merged 2025-10-16"
|
2266
|
+
[#3779]:https://github.com/puma/puma/pull/3779 "PR by @joshuay03, merged 2025-10-16"
|
2267
|
+
[#3791]:https://github.com/puma/puma/pull/3791 "PR by @MSP-Greg, merged 2025-10-09"
|
2268
|
+
[#3790]:https://github.com/puma/puma/issues/3790 "Issue by @eric-wtfoxtrot, closed 2025-10-09"
|
2269
|
+
[#3774]:https://github.com/puma/puma/pull/3774 "PR by @MSP-Greg, merged 2025-10-16"
|
2270
|
+
[#3657]:https://github.com/puma/puma/pull/3657 "PR by @marksmith, merged 2025-10-16"
|
2271
|
+
[#3703]:https://github.com/puma/puma/pull/3703 "PR by @marshall-lee, merged 2025-09-20"
|
2272
|
+
[#3742]:https://github.com/puma/puma/pull/3742 "PR by @kenballus, merged 2025-09-18"
|
2273
|
+
[#3754]:https://github.com/puma/puma/pull/3754 "PR by @byroot, merged 2025-09-18"
|
2274
|
+
[#3746]:https://github.com/puma/puma/pull/3746 "PR by @schneems, merged 2025-09-18"
|
2275
|
+
[#3740]:https://github.com/puma/puma/issues/3740 "Issue by @joshuay03, closed 2025-09-18"
|
2237
2276
|
[#3748]:https://github.com/puma/puma/pull/3748 "PR by @MSP-Greg, merged 2025-09-14"
|
2238
2277
|
[#3749]:https://github.com/puma/puma/pull/3749 "PR by @schneems, merged 2025-09-14"
|
2239
2278
|
[#3736]:https://github.com/puma/puma/pull/3736 "PR by @MSP-Greg, merged 2025-09-08"
|
data/README.md
CHANGED
@@ -142,8 +142,8 @@ Preloading can’t be used with phased restart, since phased restart kills and r
|
|
142
142
|
|
143
143
|
#### Cluster mode hooks
|
144
144
|
|
145
|
-
When using clustered mode, Puma's configuration DSL provides `before_fork` and `
|
146
|
-
hooks to run code when the master process forks
|
145
|
+
When using clustered mode, Puma's configuration DSL provides `before_fork`, `before_worker_boot`, and `after_worker_shutdown`
|
146
|
+
hooks to run code when the master process forks, the child workers are booted, and after each child worker exits respectively.
|
147
147
|
|
148
148
|
It is recommended to use these hooks with `preload_app!`, otherwise constants loaded by your
|
149
149
|
application (such as `Rails`) will not be available inside the hooks.
|
@@ -157,6 +157,11 @@ end
|
|
157
157
|
before_worker_boot do
|
158
158
|
# Add code to run inside the Puma worker process after forking.
|
159
159
|
end
|
160
|
+
|
161
|
+
after_worker_shutdown do |worker_handle|
|
162
|
+
# Add code to run inside the Puma master process after a worker exits. `worker.process_status` can be used to get the
|
163
|
+
# `Process::Status` of the exited worker.
|
164
|
+
end
|
160
165
|
```
|
161
166
|
|
162
167
|
In addition, there is an `before_refork` and `after_refork` hooks which are used only in [`fork_worker` mode](docs/fork_worker.md),
|
data/docs/kubernetes.md
CHANGED
@@ -10,7 +10,7 @@ Assuming you already have a running cluster and docker image repository, you can
|
|
10
10
|
|
11
11
|
A basic Dockerfile example:
|
12
12
|
|
13
|
-
```
|
13
|
+
```Dockerfile
|
14
14
|
FROM ruby:3.4.5-alpine # can be updated to newer ruby versions
|
15
15
|
RUN apk update && apk add build-base # and any other packages you need
|
16
16
|
|
@@ -28,7 +28,7 @@ CMD bundle exec rackup -o 0.0.0.0
|
|
28
28
|
|
29
29
|
A sample `deployment.yaml`:
|
30
30
|
|
31
|
-
```
|
31
|
+
```yaml
|
32
32
|
---
|
33
33
|
apiVersion: apps/v1
|
34
34
|
kind: Deployment
|
data/docs/stats.md
CHANGED
@@ -62,7 +62,7 @@ When Puma runs in single mode, these stats are available at the top level. When
|
|
62
62
|
this is a "wholistic" stat reflecting the overall current state of work to be done and the capacity to do it.
|
63
63
|
* pool_capacity: `how many threads are waiting to receive work` + `max_threads` - `running`. In a typical configuration where `min_threads`
|
64
64
|
and `max_threads` are configured to the same number, this is simply `how many threads are waiting to receive work`. This number exists only as a stat
|
65
|
-
and is not used for any internal decisions, unlike `
|
65
|
+
and is not used for any internal decisions, unlike `busy_threads`, 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.
|
data/ext/puma_http11/mini_ssl.c
CHANGED
@@ -660,14 +660,29 @@ VALUE engine_shutdown(VALUE self) {
|
|
660
660
|
|
661
661
|
TypedData_Get_Struct(self, ms_conn, &engine_data_type, conn);
|
662
662
|
|
663
|
+
if (SSL_in_init(conn->ssl)) {
|
664
|
+
// Avoid "shutdown while in init" error
|
665
|
+
// See https://github.com/openssl/openssl/blob/openssl-3.5.2/ssl/ssl_lib.c#L2827-L2828
|
666
|
+
return Qtrue;
|
667
|
+
}
|
668
|
+
|
663
669
|
ERR_clear_error();
|
664
670
|
|
665
671
|
ok = SSL_shutdown(conn->ssl);
|
666
|
-
|
667
|
-
|
672
|
+
// See https://github.com/openssl/openssl/blob/openssl-3.5.2/ssl/ssl_lib.c#L2792-L2797
|
673
|
+
// for description of SSL_shutdown return values.
|
674
|
+
switch (ok) {
|
675
|
+
case 0:
|
676
|
+
// "close notify" alert is sent by us.
|
677
|
+
return Qfalse;
|
678
|
+
case 1:
|
679
|
+
// "close notify" alert was received from peer.
|
680
|
+
return Qtrue;
|
681
|
+
default:
|
682
|
+
raise_error(conn->ssl, ok);
|
668
683
|
}
|
669
684
|
|
670
|
-
return
|
685
|
+
return Qnil;
|
671
686
|
}
|
672
687
|
|
673
688
|
VALUE engine_init(VALUE self) {
|
@@ -109,6 +109,10 @@ public class Http11 extends RubyObject {
|
|
109
109
|
return (RubyClass)runtime.getModule("Puma").getConstant("HttpParserError");
|
110
110
|
}
|
111
111
|
|
112
|
+
private static boolean is_ows(int c) {
|
113
|
+
return c == ' ' || c == '\t';
|
114
|
+
}
|
115
|
+
|
112
116
|
public static void http_field(Ruby runtime, RubyHash req, ByteList buffer, int field, int flen, int value, int vlen) {
|
113
117
|
RubyString f;
|
114
118
|
IRubyObject v;
|
@@ -127,7 +131,11 @@ public class Http11 extends RubyObject {
|
|
127
131
|
}
|
128
132
|
}
|
129
133
|
|
130
|
-
while (vlen > 0 &&
|
134
|
+
while (vlen > 0 && is_ows(buffer.get(value + vlen - 1))) vlen--;
|
135
|
+
while (vlen > 0 && is_ows(buffer.get(value))) {
|
136
|
+
vlen--;
|
137
|
+
value++;
|
138
|
+
}
|
131
139
|
|
132
140
|
if (b.equals(CONTENT_LENGTH_BYTELIST) || b.equals(CONTENT_TYPE_BYTELIST)) {
|
133
141
|
f = RubyString.newString(runtime, b);
|
@@ -7,6 +7,7 @@
|
|
7
7
|
#define RSTRING_NOT_MODIFIED 1
|
8
8
|
|
9
9
|
#include "ruby.h"
|
10
|
+
#include "ruby/encoding.h"
|
10
11
|
#include "ext_help.h"
|
11
12
|
#include <assert.h>
|
12
13
|
#include <string.h>
|
@@ -48,8 +49,11 @@ static VALUE global_request_path;
|
|
48
49
|
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
|
49
50
|
|
50
51
|
/** Defines global strings in the init method. */
|
51
|
-
|
52
|
-
|
52
|
+
static inline void DEF_GLOBAL(VALUE *var, const char *cstr)
|
53
|
+
{
|
54
|
+
rb_global_variable(var);
|
55
|
+
*var = rb_enc_interned_str_cstr(cstr, rb_utf8_encoding());
|
56
|
+
}
|
53
57
|
|
54
58
|
/* Defines the maximum allowed lengths for various input elements.*/
|
55
59
|
#ifndef PUMA_REQUEST_URI_MAX_LENGTH
|
@@ -134,13 +138,13 @@ static void init_common_fields(void)
|
|
134
138
|
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
135
139
|
|
136
140
|
for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
|
141
|
+
rb_global_variable(&cf->value);
|
137
142
|
if(cf->raw) {
|
138
143
|
cf->value = rb_str_new(cf->name, cf->len);
|
139
144
|
} else {
|
140
145
|
memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
|
141
146
|
cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
|
142
147
|
}
|
143
|
-
rb_global_variable(&cf->value);
|
144
148
|
}
|
145
149
|
}
|
146
150
|
|
@@ -155,6 +159,10 @@ static VALUE find_common_field_value(const char *field, size_t flen)
|
|
155
159
|
return Qnil;
|
156
160
|
}
|
157
161
|
|
162
|
+
static int is_ows(const char c) {
|
163
|
+
return c == ' ' || c == '\t';
|
164
|
+
}
|
165
|
+
|
158
166
|
void http_field(puma_parser* hp, const char *field, size_t flen,
|
159
167
|
const char *value, size_t vlen)
|
160
168
|
{
|
@@ -181,7 +189,11 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
|
|
181
189
|
f = rb_str_new(hp->buf, new_size);
|
182
190
|
}
|
183
191
|
|
184
|
-
while (vlen > 0 &&
|
192
|
+
while (vlen > 0 && is_ows(value[vlen - 1])) vlen--;
|
193
|
+
while (vlen > 0 && is_ows(value[0])) {
|
194
|
+
vlen--;
|
195
|
+
value++;
|
196
|
+
}
|
185
197
|
|
186
198
|
/* check for duplicate header */
|
187
199
|
v = rb_hash_aref(hp->request, f);
|
@@ -468,15 +480,15 @@ void Init_puma_http11(void)
|
|
468
480
|
VALUE mPuma = rb_define_module("Puma");
|
469
481
|
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
|
470
482
|
|
471
|
-
DEF_GLOBAL(
|
472
|
-
DEF_GLOBAL(
|
473
|
-
DEF_GLOBAL(
|
474
|
-
DEF_GLOBAL(
|
475
|
-
DEF_GLOBAL(
|
476
|
-
DEF_GLOBAL(
|
483
|
+
DEF_GLOBAL(&global_request_method, "REQUEST_METHOD");
|
484
|
+
DEF_GLOBAL(&global_request_uri, "REQUEST_URI");
|
485
|
+
DEF_GLOBAL(&global_fragment, "FRAGMENT");
|
486
|
+
DEF_GLOBAL(&global_query_string, "QUERY_STRING");
|
487
|
+
DEF_GLOBAL(&global_server_protocol, "SERVER_PROTOCOL");
|
488
|
+
DEF_GLOBAL(&global_request_path, "REQUEST_PATH");
|
477
489
|
|
478
|
-
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
|
479
490
|
rb_global_variable(&eHttpParserError);
|
491
|
+
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
|
480
492
|
|
481
493
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
482
494
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
data/lib/puma/client.rb
CHANGED
@@ -500,40 +500,36 @@ module Puma
|
|
500
500
|
# after this
|
501
501
|
remain = @body_remain
|
502
502
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
503
|
+
# don't bother with reading zero bytes
|
504
|
+
unless remain.zero?
|
505
|
+
begin
|
506
|
+
chunk = @io.read_nonblock(remain.clamp(0, CHUNK_SIZE), @read_buffer)
|
507
|
+
rescue IO::WaitReadable
|
508
|
+
return false
|
509
|
+
rescue SystemCallError, IOError
|
510
|
+
raise ConnectionError, "Connection error detected during read"
|
511
|
+
end
|
508
512
|
|
509
|
-
|
510
|
-
chunk
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
513
|
+
# No chunk means a closed socket
|
514
|
+
unless chunk
|
515
|
+
@body.close
|
516
|
+
@buffer = nil
|
517
|
+
set_ready
|
518
|
+
raise EOFError
|
519
|
+
end
|
516
520
|
|
517
|
-
|
518
|
-
unless chunk
|
519
|
-
@body.close
|
520
|
-
@buffer = nil
|
521
|
-
set_ready
|
522
|
-
raise EOFError
|
521
|
+
remain -= @body.write(chunk)
|
523
522
|
end
|
524
523
|
|
525
|
-
remain -= @body.write(chunk)
|
526
|
-
|
527
524
|
if remain <= 0
|
528
525
|
@body.rewind
|
529
526
|
@buffer = nil
|
530
527
|
set_ready
|
531
|
-
|
528
|
+
true
|
529
|
+
else
|
530
|
+
@body_remain = remain
|
531
|
+
false
|
532
532
|
end
|
533
|
-
|
534
|
-
@body_remain = remain
|
535
|
-
|
536
|
-
false
|
537
533
|
end
|
538
534
|
|
539
535
|
def read_chunked_body
|
@@ -28,10 +28,10 @@ module Puma
|
|
28
28
|
@worker_max = Array.new WORKER_MAX_KEYS.length, 0
|
29
29
|
end
|
30
30
|
|
31
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
31
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at, :process_status
|
32
32
|
|
33
33
|
# @version 5.0.0
|
34
|
-
attr_writer :pid, :phase
|
34
|
+
attr_writer :pid, :phase, :process_status
|
35
35
|
|
36
36
|
def booted?
|
37
37
|
@stage == :booted
|
data/lib/puma/cluster.rb
CHANGED
@@ -23,7 +23,7 @@ module Puma
|
|
23
23
|
@next_check = Time.now
|
24
24
|
|
25
25
|
@worker_max = [] # keeps track of 'max' stat values
|
26
|
-
@
|
26
|
+
@pending_phased_restart = false
|
27
27
|
end
|
28
28
|
|
29
29
|
# Returns the list of cluster worker handles.
|
@@ -238,7 +238,7 @@ module Puma
|
|
238
238
|
def phased_restart(refork = false)
|
239
239
|
return false if @options[:preload_app] && !refork
|
240
240
|
|
241
|
-
@
|
241
|
+
@pending_phased_restart = refork ? :refork : true
|
242
242
|
wakeup!
|
243
243
|
|
244
244
|
true
|
@@ -456,11 +456,11 @@ module Puma
|
|
456
456
|
break
|
457
457
|
end
|
458
458
|
|
459
|
-
if @
|
460
|
-
start_phased_restart(@
|
459
|
+
if @pending_phased_restart
|
460
|
+
start_phased_restart(@pending_phased_restart == :refork)
|
461
461
|
|
462
|
-
in_phased_restart = @
|
463
|
-
@
|
462
|
+
in_phased_restart = @pending_phased_restart
|
463
|
+
@pending_phased_restart = false
|
464
464
|
|
465
465
|
workers_not_booted = @options[:workers]
|
466
466
|
# worker 0 is not restarted on refork
|
@@ -583,7 +583,9 @@ module Puma
|
|
583
583
|
# `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
|
584
584
|
# 2. When `fork_worker` is enabled, some worker may not be direct children,
|
585
585
|
# but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
|
586
|
-
if reaped_children.delete(w.pid) || Process.
|
586
|
+
if (status = reaped_children.delete(w.pid) || Process.wait2(w.pid, Process::WNOHANG)&.last)
|
587
|
+
w.process_status = status
|
588
|
+
@config.run_hooks(:after_worker_shutdown, w, @log_writer)
|
587
589
|
true
|
588
590
|
else
|
589
591
|
w.term if w.term?
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
# Calculate a delay value for sleeping when running in clustered mode
|
5
|
+
#
|
6
|
+
# The main reason this is a class is so it can be unit tested independently.
|
7
|
+
# This makes modification easier in the future if we can encode properties of the
|
8
|
+
# delay into a test instead of relying on end-to-end testing only.
|
9
|
+
#
|
10
|
+
# This is an imprecise mechanism to address specific goals:
|
11
|
+
#
|
12
|
+
# - Evenly distribute requests across all workers at start
|
13
|
+
# - Evenly distribute CPU resources across all workers
|
14
|
+
#
|
15
|
+
# ## Goal: Distribute requests across workers at start
|
16
|
+
#
|
17
|
+
# There was a perf bug in Puma where one worker would wake up slightly before the rest and accept
|
18
|
+
# all the requests on the socket even though it didn't have enough resources to process all of them.
|
19
|
+
# This was originally fixed by never calling accept when a worker had more requests than threads
|
20
|
+
# already https://github.com/puma/puma/pull/3678/files/2736ebddb3fc8528e5150b5913fba251c37a8bf7#diff-a95f46e7ce116caddc9b9a9aa81004246d5210d5da5f4df90a818c780630166bL251-L291
|
21
|
+
#
|
22
|
+
# With the introduction of true keepalive support, there are two ways a request can come in:
|
23
|
+
# - A new request from a new client comes into the socket and it must be "accept"-ed
|
24
|
+
# - A keepalive request is served and the connection is retained. Another request is then accepted
|
25
|
+
#
|
26
|
+
# Ideally the server handles requests in the order they come in, and ideally it doesn't accept more requests than it can handle.
|
27
|
+
# These goals are contradictory, because when the server is at maximum capacity due to keepalive connections, it could mean we
|
28
|
+
# block all new requests, even if those came in before the new request on the older keepalive connection.
|
29
|
+
#
|
30
|
+
# ## Goal: Distribute CPU resources across all workers
|
31
|
+
#
|
32
|
+
# - This issue was opened https://github.com/puma/puma/issues/2078
|
33
|
+
#
|
34
|
+
# There are several entangled issues and it's not exactly clear what the root cause is, but the observable outcome
|
35
|
+
# was that performance was better with a small sleep, and that eventually became the default.
|
36
|
+
#
|
37
|
+
# An attempt to describe why this works is here: https://github.com/puma/puma/issues/2078#issuecomment-3287032470.
|
38
|
+
#
|
39
|
+
# Summarizing: The delay is for tuning the rate at which "accept" is called on the socket.
|
40
|
+
# Puma works by calling "accept" nonblock on the socket in a loop. When there are multiple workers
|
41
|
+
# (processes), they will "race" to accept a request at roughly the same rate. However, if one
|
42
|
+
# worker has all threads busy processing requests, then accepting a new request might "steal" it from
|
43
|
+
# a less busy worker. If a worker has no work to do, it should loop as fast as possible.
|
44
|
+
#
|
45
|
+
# ## Solution: Distribute requests across workers at start
|
46
|
+
#
|
47
|
+
# For now, both goals are framed as "load balancing" across workers (processes) and achieved through
|
48
|
+
# the same mechanism of sleeping longer to delay busier workers. Rather than the prior Puma 6.x
|
49
|
+
# and earlier behavior of using a binary on/off sleep value, we increase it an amount proportional
|
50
|
+
# to the load the server is under, capping the maximum delay to the scenario where all threads are busy
|
51
|
+
# and the todo list has reached a multiplier of the maximum number of threads.
|
52
|
+
#
|
53
|
+
# Private: API may change unexpectedly
|
54
|
+
class ClusterAcceptLoopDelay
|
55
|
+
attr_reader :max_delay
|
56
|
+
|
57
|
+
# Initialize happens once, `call` happens often. Perform global calculations here.
|
58
|
+
def initialize(
|
59
|
+
# Number of workers in the cluster
|
60
|
+
workers: ,
|
61
|
+
# Maximum delay in seconds i.e. 0.005 is 5 milliseconds
|
62
|
+
max_delay:
|
63
|
+
)
|
64
|
+
@on = max_delay > 0 && workers >= 2
|
65
|
+
@max_delay = max_delay.to_f
|
66
|
+
|
67
|
+
# Reach maximum delay when `max_threads * overload_multiplier` is reached in the system
|
68
|
+
@overload_multiplier = 25.0
|
69
|
+
end
|
70
|
+
|
71
|
+
def on?
|
72
|
+
@on
|
73
|
+
end
|
74
|
+
|
75
|
+
# We want the extreme values of this delay to be known (minimum and maximum) as well as
|
76
|
+
# a predictable curve between the two. i.e. no step functions or hard cliffs.
|
77
|
+
#
|
78
|
+
# Return value is always numeric. Returns 0 if there should be no delay.
|
79
|
+
def calculate(
|
80
|
+
# Number of threads working right now, plus number of requests in the todo list
|
81
|
+
busy_threads_plus_todo:,
|
82
|
+
# Maximum number of threads in the pool, note that the busy threads (alone) may go over this value at times
|
83
|
+
# if the pool needs to be reaped. The busy thread plus todo count may go over this value by a large amount.
|
84
|
+
max_threads:
|
85
|
+
)
|
86
|
+
max_value = @overload_multiplier * max_threads
|
87
|
+
# Approaches max delay when `busy_threads_plus_todo` approaches `max_value`
|
88
|
+
return max_delay * busy_threads_plus_todo.clamp(0, max_value) / max_value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -155,6 +155,7 @@ module Puma
|
|
155
155
|
out_of_band: [],
|
156
156
|
# Number of seconds for another request within a persistent session.
|
157
157
|
persistent_timeout: 65, # PUMA_PERSISTENT_TIMEOUT
|
158
|
+
prune_bundler: false,
|
158
159
|
queue_requests: true,
|
159
160
|
rackup: 'config.ru'.freeze,
|
160
161
|
raise_exception_on_sigterm: true,
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "7.0
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "7.1.0"
|
104
|
+
CODE_NAME = "Neon Witch"
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
107
107
|
|
data/lib/puma/dsl.rb
CHANGED
@@ -656,7 +656,8 @@ module Puma
|
|
656
656
|
@options[:state] = path.to_s
|
657
657
|
end
|
658
658
|
|
659
|
-
# Use +permission+ to restrict permissions for the state file.
|
659
|
+
# Use +permission+ to restrict permissions for the state file. By convention,
|
660
|
+
# +permission+ is an octal number (e.g. `0640` or `0o640`).
|
660
661
|
#
|
661
662
|
# @example
|
662
663
|
# state_permission 0600
|
@@ -818,6 +819,20 @@ module Puma
|
|
818
819
|
|
819
820
|
alias_method :after_worker_boot, :after_worker_fork
|
820
821
|
|
822
|
+
# Code to run in the master right after a worker has stopped. The worker's
|
823
|
+
# index and Process::Status are passed as arguments.
|
824
|
+
#
|
825
|
+
# @note Cluster mode only.
|
826
|
+
#
|
827
|
+
# @example
|
828
|
+
# after_worker_shutdown do |worker_handle|
|
829
|
+
# puts 'Worker crashed' unless worker_handle.process_status.success?
|
830
|
+
# end
|
831
|
+
#
|
832
|
+
def after_worker_shutdown(&block)
|
833
|
+
process_hook :after_worker_shutdown, nil, block, cluster_only: true
|
834
|
+
end
|
835
|
+
|
821
836
|
# Code to run after puma is booted (works for both single and cluster modes).
|
822
837
|
#
|
823
838
|
# @example
|
@@ -1207,13 +1222,19 @@ module Puma
|
|
1207
1222
|
end
|
1208
1223
|
|
1209
1224
|
|
1210
|
-
#
|
1211
|
-
#
|
1225
|
+
# Maximum delay of worker accept loop.
|
1226
|
+
#
|
1227
|
+
# Attempts to route traffic to less-busy workers by causing a busy worker to delay
|
1228
|
+
# listening on the socket, allowing workers which are not processing as many
|
1212
1229
|
# requests to pick up new requests first.
|
1213
1230
|
#
|
1214
1231
|
# The default is 0.005 seconds.
|
1215
1232
|
#
|
1216
|
-
#
|
1233
|
+
# To turn off this feature, set the value to 0.
|
1234
|
+
#
|
1235
|
+
# @note Cluster mode with >= 2 workers only.
|
1236
|
+
#
|
1237
|
+
# @note Interpreters with forking support only.
|
1217
1238
|
#
|
1218
1239
|
# @see Puma::Server#handle_servers
|
1219
1240
|
# @see Puma::ThreadPool#wait_for_less_busy_worker
|
data/lib/puma/launcher.rb
CHANGED
@@ -42,26 +42,39 @@ module Puma
|
|
42
42
|
# end
|
43
43
|
# Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
|
44
44
|
def initialize(conf, launcher_args={})
|
45
|
-
|
46
|
-
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
47
|
-
@events = launcher_args[:events] || Events.new
|
48
|
-
@argv = launcher_args[:argv] || []
|
49
|
-
@original_argv = @argv.dup
|
50
|
-
@config = conf
|
51
|
-
|
52
|
-
env = launcher_args.delete(:env) || ENV
|
45
|
+
## Minimal initialization for a potential early restart (e.g. when pruning bundle)
|
53
46
|
|
47
|
+
@config = conf
|
54
48
|
@config.clamp
|
49
|
+
|
55
50
|
@options = @config.options
|
56
51
|
|
52
|
+
@log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
|
53
|
+
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
54
|
+
@log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
|
55
|
+
@log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
|
57
56
|
@options[:log_writer] = @log_writer
|
58
57
|
@options[:logger] = @log_writer if clustered?
|
59
58
|
|
59
|
+
@events = launcher_args[:events] || Events.new
|
60
|
+
|
61
|
+
@argv = launcher_args[:argv] || []
|
62
|
+
@original_argv = @argv.dup
|
63
|
+
|
64
|
+
## End minimal initialization
|
65
|
+
|
66
|
+
generate_restart_data
|
67
|
+
Dir.chdir(@restart_dir)
|
68
|
+
|
69
|
+
prune_bundler!
|
70
|
+
|
71
|
+
env = launcher_args.delete(:env) || ENV
|
72
|
+
|
60
73
|
# Advertise the Configuration
|
61
74
|
Puma.cli_config = @config if defined?(Puma.cli_config)
|
62
75
|
log_config if env['PUMA_LOG_CONFIG']
|
63
76
|
|
64
|
-
@binder
|
77
|
+
@binder = Binder.new(@log_writer, @options)
|
65
78
|
@binder.create_inherited_fds(env).each { |k| env.delete k }
|
66
79
|
@binder.create_activated_fds(env).each { |k| env.delete k }
|
67
80
|
|
@@ -81,21 +94,10 @@ module Puma
|
|
81
94
|
)
|
82
95
|
end
|
83
96
|
|
84
|
-
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
85
|
-
@log_writer.formatter = @options[:log_formatter] if @options[:log_formatter]
|
86
|
-
|
87
|
-
@log_writer.custom_logger = @options[:custom_logger] if @options[:custom_logger]
|
88
|
-
|
89
|
-
generate_restart_data
|
90
|
-
|
91
97
|
if clustered? && !Puma.forkable?
|
92
98
|
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
93
99
|
end
|
94
100
|
|
95
|
-
Dir.chdir(@restart_dir)
|
96
|
-
|
97
|
-
prune_bundler!
|
98
|
-
|
99
101
|
@environment = @options[:environment] if @options[:environment]
|
100
102
|
set_rack_environment
|
101
103
|
|
@@ -139,7 +141,10 @@ module Puma
|
|
139
141
|
# Delete the configured pidfile
|
140
142
|
def delete_pidfile
|
141
143
|
path = @options[:pidfile]
|
142
|
-
|
144
|
+
begin
|
145
|
+
File.unlink(path) if path
|
146
|
+
rescue Errno::ENOENT
|
147
|
+
end
|
143
148
|
end
|
144
149
|
|
145
150
|
# Begin async shutdown of the server
|
@@ -381,9 +386,9 @@ module Puma
|
|
381
386
|
# using it.
|
382
387
|
@restart_dir = Dir.pwd
|
383
388
|
|
384
|
-
|
385
|
-
|
386
|
-
|
389
|
+
# Use the same trick as unicorn, namely favor PWD because
|
390
|
+
# it will contain an unresolved symlink, useful for when
|
391
|
+
# the pwd is /data/releases/current.
|
387
392
|
elsif dir = ENV['PWD']
|
388
393
|
s_env = File.stat(dir)
|
389
394
|
s_pwd = File.stat(Dir.pwd)
|
data/lib/puma/server.rb
CHANGED
@@ -13,14 +13,12 @@ require_relative 'binder'
|
|
13
13
|
require_relative 'util'
|
14
14
|
require_relative 'request'
|
15
15
|
require_relative 'configuration'
|
16
|
+
require_relative 'cluster_accept_loop_delay'
|
16
17
|
|
17
18
|
require 'socket'
|
18
19
|
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
19
20
|
|
20
21
|
module Puma
|
21
|
-
# Add `Thread#puma_server` and `Thread#puma_server=`
|
22
|
-
Thread.attr_accessor(:puma_server)
|
23
|
-
|
24
22
|
# The HTTP Server itself. Serves out a single Rack app.
|
25
23
|
#
|
26
24
|
# This class is used by the `Puma::Single` and `Puma::Cluster` classes
|
@@ -58,7 +56,6 @@ module Puma
|
|
58
56
|
attr_accessor :app
|
59
57
|
attr_accessor :binder
|
60
58
|
|
61
|
-
|
62
59
|
# Create a server for the rack app +app+.
|
63
60
|
#
|
64
61
|
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
@@ -110,6 +107,10 @@ module Puma
|
|
110
107
|
@enable_keep_alives &&= @queue_requests
|
111
108
|
@io_selector_backend = @options[:io_selector_backend]
|
112
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
|
+
)
|
113
114
|
|
114
115
|
if @options[:fiber_per_request]
|
115
116
|
singleton_class.prepend(FiberPerRequest)
|
@@ -245,11 +246,6 @@ module Puma
|
|
245
246
|
@thread_pool&.pool_capacity
|
246
247
|
end
|
247
248
|
|
248
|
-
# @!attribute [r] busy_threads
|
249
|
-
def busy_threads
|
250
|
-
@thread_pool&.busy_threads
|
251
|
-
end
|
252
|
-
|
253
249
|
# Runs the server.
|
254
250
|
#
|
255
251
|
# If +background+ is true (the default) then a thread is spun
|
@@ -263,10 +259,14 @@ module Puma
|
|
263
259
|
|
264
260
|
@status = :run
|
265
261
|
|
266
|
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) { |client| process_client client }
|
267
263
|
|
268
264
|
if @queue_requests
|
269
|
-
@reactor = Reactor.new(@io_selector_backend) { |c|
|
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
270
|
@reactor.run
|
271
271
|
end
|
272
272
|
|
@@ -291,6 +291,9 @@ module Puma
|
|
291
291
|
# This method is called from the Reactor thread when a queued Client receives data,
|
292
292
|
# times out, or when the Reactor is shutting down.
|
293
293
|
#
|
294
|
+
# While the code lives in the Server, the logic is executed on the reactor thread, independently
|
295
|
+
# from the server.
|
296
|
+
#
|
294
297
|
# It is responsible for ensuring that a request has been completely received
|
295
298
|
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
296
299
|
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
@@ -325,7 +328,7 @@ module Puma
|
|
325
328
|
end
|
326
329
|
rescue StandardError => e
|
327
330
|
client_error(e, client)
|
328
|
-
client
|
331
|
+
close_client_safely(client)
|
329
332
|
true
|
330
333
|
end
|
331
334
|
|
@@ -339,7 +342,6 @@ module Puma
|
|
339
342
|
pool = @thread_pool
|
340
343
|
queue_requests = @queue_requests
|
341
344
|
drain = options[:drain_on_shutdown] ? 0 : nil
|
342
|
-
max_flt = @max_threads.to_f
|
343
345
|
|
344
346
|
addr_send_name, addr_value = case options[:remote_address]
|
345
347
|
when :value
|
@@ -384,15 +386,13 @@ module Puma
|
|
384
386
|
# clients until the code is finished.
|
385
387
|
pool.wait_while_out_of_band_running
|
386
388
|
|
387
|
-
#
|
388
|
-
if pool.busy_threads
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
sleep 0.0001
|
395
|
-
end
|
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)
|
396
396
|
end
|
397
397
|
|
398
398
|
io = begin
|
@@ -401,11 +401,9 @@ module Puma
|
|
401
401
|
next
|
402
402
|
end
|
403
403
|
drain += 1 if shutting_down?
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
c.send(addr_send_name, addr_value) if addr_value
|
408
|
-
}
|
404
|
+
client = new_client(io, sock)
|
405
|
+
client.send(addr_send_name, addr_value) if addr_value
|
406
|
+
pool << client
|
409
407
|
end
|
410
408
|
end
|
411
409
|
rescue IOError, Errno::EBADF
|
@@ -442,6 +440,14 @@ module Puma
|
|
442
440
|
@events.fire :state, :done
|
443
441
|
end
|
444
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
|
449
|
+
end
|
450
|
+
|
445
451
|
# :nodoc:
|
446
452
|
def handle_check
|
447
453
|
cmd = @check.read(1)
|
@@ -472,9 +478,6 @@ module Puma
|
|
472
478
|
#
|
473
479
|
# Return true if one or more requests were processed.
|
474
480
|
def process_client(client)
|
475
|
-
# Advertise this server into the thread
|
476
|
-
Thread.current.puma_server = self
|
477
|
-
|
478
481
|
close_socket = true
|
479
482
|
|
480
483
|
requests = 0
|
@@ -493,31 +496,41 @@ module Puma
|
|
493
496
|
client.finish(@first_data_timeout)
|
494
497
|
end
|
495
498
|
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
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 false
|
505
|
+
when :async
|
506
|
+
close_socket = false
|
507
|
+
when true
|
508
|
+
requests += 1
|
503
509
|
|
504
|
-
|
510
|
+
client.reset
|
505
511
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
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
|
513
519
|
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
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
|
521
534
|
end
|
522
535
|
end
|
523
536
|
end
|
@@ -529,16 +542,21 @@ module Puma
|
|
529
542
|
ensure
|
530
543
|
client.io_buffer.reset
|
531
544
|
|
532
|
-
|
533
|
-
client.close if close_socket
|
534
|
-
rescue IOError, SystemCallError
|
535
|
-
# Already closed
|
536
|
-
rescue StandardError => e
|
537
|
-
@log_writer.unknown_error e, nil, "Client"
|
538
|
-
end
|
545
|
+
close_client_safely(client) if close_socket
|
539
546
|
end
|
540
547
|
end
|
541
548
|
|
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
|
559
|
+
|
542
560
|
# Triggers a client timeout if the thread-pool shuts down
|
543
561
|
# during execution of the provided block.
|
544
562
|
def with_force_shutdown(client, &block)
|
@@ -692,7 +710,7 @@ module Puma
|
|
692
710
|
|
693
711
|
def reset_max
|
694
712
|
@reactor.reactor_max = 0 if @reactor
|
695
|
-
@thread_pool
|
713
|
+
@thread_pool&.reset_max
|
696
714
|
end
|
697
715
|
|
698
716
|
# below are 'delegations' to binder
|
data/lib/puma/state_file.rb
CHANGED
@@ -32,10 +32,11 @@ module Puma
|
|
32
32
|
"#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
|
33
33
|
end
|
34
34
|
end
|
35
|
+
|
35
36
|
if permission
|
36
|
-
File.write path, contents, mode: 'wb:UTF-8'
|
37
|
-
else
|
38
37
|
File.write path, contents, mode: 'wb:UTF-8', perm: permission
|
38
|
+
else
|
39
|
+
File.write path, contents, mode: 'wb:UTF-8'
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -5,6 +5,10 @@ require 'thread'
|
|
5
5
|
require_relative 'io_buffer'
|
6
6
|
|
7
7
|
module Puma
|
8
|
+
|
9
|
+
# Add `Thread#puma_server` and `Thread#puma_server=`
|
10
|
+
Thread.attr_accessor(:puma_server)
|
11
|
+
|
8
12
|
# Internal Docs for A simple thread pool management object.
|
9
13
|
#
|
10
14
|
# Each Puma "worker" has a thread pool to process requests.
|
@@ -33,7 +37,9 @@ module Puma
|
|
33
37
|
# The block passed is the work that will be performed in each
|
34
38
|
# thread.
|
35
39
|
#
|
36
|
-
def initialize(name, options = {}, &block)
|
40
|
+
def initialize(name, options = {}, server: nil, &block)
|
41
|
+
@server = server
|
42
|
+
|
37
43
|
@not_empty = ConditionVariable.new
|
38
44
|
@not_full = ConditionVariable.new
|
39
45
|
@mutex = Mutex.new
|
@@ -134,6 +140,9 @@ module Puma
|
|
134
140
|
trigger_before_thread_start_hooks
|
135
141
|
th = Thread.new(@spawned) do |spawned|
|
136
142
|
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
143
|
+
# Advertise server into the thread
|
144
|
+
Thread.current.puma_server = @server
|
145
|
+
|
137
146
|
todo = @todo
|
138
147
|
block = @block
|
139
148
|
mutex = @mutex
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.0
|
4
|
+
version: 7.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Phoenix
|
@@ -87,6 +87,7 @@ files:
|
|
87
87
|
- lib/puma/cluster.rb
|
88
88
|
- lib/puma/cluster/worker.rb
|
89
89
|
- lib/puma/cluster/worker_handle.rb
|
90
|
+
- lib/puma/cluster_accept_loop_delay.rb
|
90
91
|
- lib/puma/commonlogger.rb
|
91
92
|
- lib/puma/configuration.rb
|
92
93
|
- lib/puma/const.rb
|
@@ -131,6 +132,7 @@ metadata:
|
|
131
132
|
homepage_uri: https://puma.io
|
132
133
|
source_code_uri: https://github.com/puma/puma
|
133
134
|
rubygems_mfa_required: 'true'
|
135
|
+
msys2_mingw_dependencies: openssl
|
134
136
|
rdoc_options: []
|
135
137
|
require_paths:
|
136
138
|
- lib
|