puma 6.4.0-java → 6.4.2-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 725988ce035212443c9cd9c6e250dc80edb5de0d1b843a49d11158e5055699b8
4
- data.tar.gz: e5f9f2eb0cf769ff5497ce68bb565f8ead8057ca1293d83a2ff10b89044e58ef
3
+ metadata.gz: 1736c6dd01c94d6cf6a109965156ec60ec8a9deb17b42bdabc198f79d1b72c1a
4
+ data.tar.gz: 58e70495466aa055fc76038a23cf394167ca393959ab7bfa8a55306a7506f1ac
5
5
  SHA512:
6
- metadata.gz: f8a546a676d69611917f30f3839ca01f84f4374e40aa1167882784013b8bddfd95ce03d84aecff86017bf23c9cd7a5efa097fc4c746299da224866737388cdfe
7
- data.tar.gz: 7bbf658a44f85eb817101d86e565e03def93a580b31bf4cfd739ecca6c639b070bea75cb3660fe25ab9519396fed9fd31b664b625328430784fdc05cab922f78
6
+ metadata.gz: 5ee67bed8910a53a217a6d9f799cf7a64f145d5a80a0cbea224949e3ae2fe76ae31dc5e5991a7983f71cbe34836ef568cab41a6d0ae5ca7d21cf0f87b8fac09e
7
+ data.tar.gz: '0085fa5bd8492707be9c52b3618c0663a06a2aad62e5fe60b66521d51572f8159d22f5f14b265314c1a094bec0d506dabef74c514fb8813d9a31d9f5395bfd3d'
data/History.md CHANGED
@@ -1,8 +1,37 @@
1
+ ## 6.4.2 / 2024-01-08
2
+
3
+ * Security
4
+ * Limit the size of chunk extensions. Without this limit, an attacker could cause unbounded resource (CPU, network bandwidth) consumption. ([GHSA-c2f4-cvqm-65w2](https://github.com/puma/puma/security/advisories/GHSA-c2f4-cvqm-65w2))
5
+
6
+ ## 6.4.1 / 2024-01-03
7
+
8
+ * Bugfixes
9
+ * DSL#warn_if_in_single_mode - fixup when workers set via CLI ([#3256])
10
+ * Fix `idle-timeout` not working in cluster mode ([#3235], [#3228], [#3282], [#3283])
11
+ * Fix worker 0 timing out during phased restart ([#3225], [#2786])
12
+ * context_builder.rb - require openssl if verify_mode != 'none' ([#3179])
13
+ * Make puma cluster process suitable as PID 1 ([#3255])
14
+ * Improve Puma::NullIO consistency with real IO ([#3276])
15
+ * extconf.rb - fixup to detect openssl info in Ruby build ([#3271], [#3266])
16
+ * MiniSSL.java - set serialVersionUID, fix RaiseException deprecation ([#3270])
17
+ * dsl.rb - fix warn_if_in_single_mode when WEB_CONCURRENCY is set ([#3265], [#3264])
18
+
19
+ * Maintenance
20
+ * LOTS of test refactoring to make tests more stable and easier to write - thanks to @MSP-Greg!
21
+ * Fix bug in tests re: TestPuma::HOST4 ([#3254])
22
+ * Dockerfile for minimal repros: use Ruby 3.2, expect bundler installed ([#3245])
23
+ * fix define_method calls, use Symbol parameter instead of String ([#3293])
24
+
25
+ * Docs
26
+ * README.md - add the puma-acme plugin ([#3301])
27
+ * Remove `--keep-file-descriptors` flag from systemd docs ([#3248])
28
+ * Note symlink mechanism in restart documentation for hot restart ([#3298])
29
+
1
30
  ## 6.4.0 / 2023-09-21
2
31
 
3
32
  * Features
4
33
  * on_thread_exit hook ([#2920])
5
- * on_thread_start_hook ([#3195])
34
+ * on_thread_start_hook ([#3195])
6
35
  * Shutdown on idle ([#3209], [#2580])
7
36
  * New error message when control server port taken ([#3204])
8
37
 
@@ -12,13 +41,13 @@
12
41
 
13
42
  * Bugfixes
14
43
  * Bring the cert_pem: parameter into parity with the cert: parameter to ssl_bind. ([#3174])
15
- * Fix using control server with IPv6 host ([#3181])
44
+ * Fix using control server with IPv6 host ([#3181])
16
45
  * control_cli.rb - add require_relative 'log_writer' ([#3187])
17
46
  * Fix cases where fallback Rack response wasn't sent to the client ([#3094])
18
-
47
+
19
48
  ## 6.3.1 / 2023-08-18
20
49
 
21
- * Security
50
+ * Security
22
51
  * Address HTTP request smuggling vulnerabilities with zero-length Content Length header and trailer fields ([GHSA-68xg-gqqm-vgj8](https://github.com/puma/puma/security/advisories/GHSA-68xg-gqqm-vgj8))
23
52
 
24
53
  ## 6.3.0 / 2023-05-31
@@ -92,12 +121,12 @@
92
121
  * Refactor const.rb - freeze ([#3016])
93
122
 
94
123
  ## 6.0.1 / 2022-12-20
95
-
124
+
96
125
  * Bugfixes
97
126
  * Handle waking up a closed selector in Reactor#add ([#3005])
98
127
  * Fixup response processing, enumerable bodies ([#3004], [#3000])
99
128
  * Correctly close app body for all code paths ([#3002], [#2999])
100
- * Refactor
129
+ * Refactor
101
130
  * Add IOBuffer to Client, remove from ThreadPool thread instances ([#3013])
102
131
 
103
132
  ## 6.0.0 / 2022-10-14
@@ -126,12 +155,12 @@
126
155
  * Allow header values to be arrays (Rack 3) ([#2936], [#2931])
127
156
  * Export Puma/Ruby versions in /stats ([#2875])
128
157
  * Allow configuring request uri max length & request path max length ([#2840])
129
- * Add a couple of public accessors ([#2774])
158
+ * Add a couple of public accessors ([#2774])
130
159
  * Log entire backtrace when worker start fails ([#2891])
131
160
  * [jruby] Enable TLSv1.3 support ([#2886])
132
161
  * [jruby] support setting TLS protocols + rename ssl_cipher_list ([#2899])
133
162
  * [jruby] Support a truststore option ([#2849], [#2904], [#2884])
134
-
163
+
135
164
  * Bugfixes
136
165
  * Load the configuration before passing it to the binder ([#2897])
137
166
  * Do not raise error raised on HTTP methods we don't recognize or support, like CONNECT ([#2932], [#1441])
@@ -144,9 +173,14 @@
144
173
  * Ruby 3.2 will have native IO#wait_* methods, don't require io/wait ([#2903])
145
174
  * Various internal API refactorings ([#2942], [#2921], [#2922], [#2955])
146
175
 
176
+ ## 5.6.8 / 2024-01-08
177
+
178
+ * Security
179
+ * Limit the size of chunk extensions. Without this limit, an attacker could cause unbounded resource (CPU, network bandwidth) consumption. ([GHSA-c2f4-cvqm-65w2](https://github.com/puma/puma/security/advisories/GHSA-c2f4-cvqm-65w2))
180
+
147
181
  ## 5.6.7 / 2023-08-18
148
182
 
149
- * Security
183
+ * Security
150
184
  * Address HTTP request smuggling vulnerabilities with zero-length Content Length header and trailer fields ([GHSA-68xg-gqqm-vgj8](https://github.com/puma/puma/security/advisories/GHSA-68xg-gqqm-vgj8))
151
185
 
152
186
  ## 5.6.6 / 2023-06-21
@@ -2029,6 +2063,27 @@ be added back in a future date when a java Puma::MiniSSL is added.
2029
2063
  * Bugfixes
2030
2064
  * Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
2031
2065
 
2066
+ [#3256]:https://github.com/puma/puma/pull/3256 "PR by @MSP-Greg, merged 2023-10-16"
2067
+ [#3235]:https://github.com/puma/puma/pull/3235 "PR by @joshuay03, merged 2023-10-03"
2068
+ [#3228]:https://github.com/puma/puma/issues/3228 "Issue by @davidalejandroaguilar, closed 2023-10-03"
2069
+ [#3282]:https://github.com/puma/puma/issues/3282 "Issue by @bensheldon, closed 2024-01-02"
2070
+ [#3283]:https://github.com/puma/puma/pull/3283 "PR by @joshuay03, merged 2024-01-02"
2071
+ [#3225]:https://github.com/puma/puma/pull/3225 "PR by @joshuay03, merged 2023-09-27"
2072
+ [#2786]:https://github.com/puma/puma/issues/2786 "Issue by @vitiokss, closed 2023-09-27"
2073
+ [#3179]:https://github.com/puma/puma/pull/3179 "PR by @MSP-Greg, merged 2023-09-26"
2074
+ [#3255]:https://github.com/puma/puma/pull/3255 "PR by @casperisfine, merged 2023-10-19"
2075
+ [#3276]:https://github.com/puma/puma/pull/3276 "PR by @casperisfine, merged 2023-11-16"
2076
+ [#3271]:https://github.com/puma/puma/pull/3271 "PR by @MSP-Greg, merged 2023-10-30"
2077
+ [#3266]:https://github.com/puma/puma/issues/3266 "Issue by @Dragonicity, closed 2023-10-30"
2078
+ [#3270]:https://github.com/puma/puma/pull/3270 "PR by @MSP-Greg, merged 2023-10-30"
2079
+ [#3265]:https://github.com/puma/puma/pull/3265 "PR by @MSP-Greg, merged 2023-10-25"
2080
+ [#3264]:https://github.com/puma/puma/issues/3264 "Issue by @dentarg, closed 2023-10-25"
2081
+ [#3254]:https://github.com/puma/puma/pull/3254 "PR by @casperisfine, merged 2023-10-11"
2082
+ [#3245]:https://github.com/puma/puma/pull/3245 "PR by @olleolleolle, merged 2023-10-02"
2083
+ [#3293]:https://github.com/puma/puma/pull/3293 "PR by @MSP-Greg, merged 2023-12-21"
2084
+ [#3301]:https://github.com/puma/puma/pull/3301 "PR by @benburkert, merged 2023-12-29"
2085
+ [#3248]:https://github.com/puma/puma/pull/3248 "PR by @dentarg, merged 2023-10-04"
2086
+ [#3298]:https://github.com/puma/puma/pull/3298 "PR by @til, merged 2023-12-26"
2032
2087
  [#2920]:https://github.com/puma/puma/pull/2920 "PR by @biinari, merged 2023-07-11"
2033
2088
  [#3195]:https://github.com/puma/puma/pull/3195 "PR by @binarygit, merged 2023-08-15"
2034
2089
  [#3209]:https://github.com/puma/puma/pull/3209 "PR by @joshuay03, merged 2023-09-04"
@@ -2213,7 +2268,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
2213
2268
  [#2563]:https://github.com/puma/puma/pull/2563 "PR by @MSP-Greg, merged 2021-03-06"
2214
2269
  [#2504]:https://github.com/puma/puma/issues/2504 "Issue by @fsateler, closed 2021-03-06"
2215
2270
  [#2591]:https://github.com/puma/puma/pull/2591 "PR by @MSP-Greg, merged 2021-05-05"
2216
- [#2572]:https://github.com/puma/puma/issues/2572 "Issue by @josefbilendo, closed 2021-05-05"
2271
+ [#2572]:https://github.com/puma/puma/issues/2572 "Issue by @josef-krabath, closed 2021-05-05"
2217
2272
  [#2613]:https://github.com/puma/puma/pull/2613 "PR by @smcgivern, merged 2021-04-27"
2218
2273
  [#2605]:https://github.com/puma/puma/pull/2605 "PR by @pascalbetz, merged 2021-04-26"
2219
2274
  [#2584]:https://github.com/puma/puma/issues/2584 "Issue by @kaorihinata, closed 2021-04-26"
@@ -2529,7 +2584,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
2529
2584
  [#1110]:https://github.com/puma/puma/pull/1110 "PR by @montdidier, merged 2016-12-12"
2530
2585
  [#1135]:https://github.com/puma/puma/pull/1135 "PR by @jkraemer, merged 2016-11-19"
2531
2586
  [#1081]:https://github.com/puma/puma/pull/1081 "PR by @frodsan, merged 2016-09-08"
2532
- [#1138]:https://github.com/puma/puma/pull/1138 "PR by @steakknife, merged 2016-12-13"
2587
+ [#1138]:https://github.com/puma/puma/pull/1138 "PR by @skull-squadron, merged 2016-12-13"
2533
2588
  [#1118]:https://github.com/puma/puma/pull/1118 "PR by @hiroara, merged 2016-11-20"
2534
2589
  [#1075]:https://github.com/puma/puma/issues/1075 "Issue by @pvalena, closed 2016-09-06"
2535
2590
  [#932]:https://github.com/puma/puma/issues/932 "Issue by @everplays, closed 2016-07-24"
data/README.md CHANGED
@@ -410,6 +410,7 @@ Community guides:
410
410
  * [puma-plugin-statsd](https://github.com/yob/puma-plugin-statsd) — send Puma metrics to statsd
411
411
  * [puma-plugin-systemd](https://github.com/sj26/puma-plugin-systemd) — deeper integration with systemd for notify, status and watchdog. Puma 5.1.0 integrated notify and watchdog, which probably conflicts with this plugin. Puma 6.1.0 added status support which obsoletes the plugin entirely.
412
412
  * [puma-plugin-telemetry](https://github.com/babbel/puma-plugin-telemetry) - telemetry plugin for Puma offering various targets to publish
413
+ * [puma-acme](https://github.com/anchordotdev/puma-acme) - automatic SSL/HTTPS certificate provisioning and setup
413
414
 
414
415
  ### Monitoring
415
416
 
data/docs/kubernetes.md CHANGED
@@ -69,7 +69,7 @@ More discussions and links to relevant articles can be found in https://github.c
69
69
 
70
70
  With containerization, you will have to make a decision about how "big" to make each pod. Should you run 2 pods with 50 workers each? 25 pods, each with 4 workers? 100 pods, with each Puma running in single mode? Each scenario represents the same total amount of capacity (100 Puma processes that can respond to requests), but there are tradeoffs to make.
71
71
 
72
- * Worker counts should be somewhere between 4 and 32 in most cases. You want more than 4 in order to minimize time spent in request queueing for a free Puma worker, but probably less than ~32 because otherwise autoscaling is working in too large of an increment or they probably won't fit very well into your nodes.
72
+ * Worker counts should be somewhere between 4 and 32 in most cases. You want more than 4 in order to minimize time spent in request queueing for a free Puma worker, but probably less than ~32 because otherwise autoscaling is working in too large of an increment or they probably won't fit very well into your nodes. In any queueing system, queue time is proportional to 1/n, where n is the number of things pulling from the queue. Each pod will have its own request queue (i.e., the socket backlog). If you have 4 pods with 1 worker each (4 request queues), wait times are, proportionally, about 4 times higher than if you had 1 pod with 4 workers (1 request queue).
73
73
  * Unless you have a very I/O-heavy application (50%+ time spent waiting on IO), use the default thread count (5 for MRI). Using higher numbers of threads with low I/O wait (<50%) will lead to additional request queueing time (latency!) and additional memory usage.
74
74
  * More processes per pod reduces memory usage per process, because of copy-on-write memory and because the cost of the single master process is "amortized" over more child processes.
75
75
  * Don't run less than 4 processes per pod if you can. Low numbers of processes per pod will lead to high request queueing, which means you will have to run more pods.
data/docs/restart.md CHANGED
@@ -27,6 +27,7 @@ Any of the following will cause a Puma server to perform a hot restart:
27
27
 
28
28
  ### Additional notes
29
29
 
30
+ * The newly started Puma process changes its current working directory to the directory specified by the `directory` option. If `directory` is set to symlink, this is automatically re-evaluated, so this mechanism can be used to upgrade the application.
30
31
  * Only one version of the application is running at a time.
31
32
  * `on_restart` is invoked just before the server shuts down. This can be used to clean up resources (like long-lived database connections) gracefully. Since Ruby 2.0, it is not typically necessary to explicitly close file descriptors on restart. This is because any file descriptor opened by Ruby will have the `FD_CLOEXEC` flag set, meaning that file descriptors are closed on `exec`. `on_restart` is useful, though, if your application needs to perform any more graceful protocol-specific shutdown procedures before closing connections.
32
33
 
data/docs/systemd.md CHANGED
@@ -51,7 +51,7 @@ ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/puma.rb
51
51
  # Variant: Rails start.
52
52
  # ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/config/puma.rb ../config.ru
53
53
 
54
- # Variant: Use `bundle exec --keep-file-descriptors puma` instead of binstub
54
+ # Variant: Use `bundle exec puma` instead of binstub
55
55
  # Variant: Specify directives inline.
56
56
  # ExecStart=/<FULLPATH>/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
57
57
 
@@ -76,9 +76,7 @@ compatible with both clustered 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
79
- puma master process. For example, if using `bundle exec`, pass the
80
- `--keep-file-descriptors` flag. `bundle exec` can be avoided by using a `puma`
81
- executable generated by `bundle binstubs puma`. This is tracked in [#1499].
79
+ puma master process.
82
80
 
83
81
  **Note:** Socket activation doesn't currently work on JRuby. This is tracked in
84
82
  [#1367].
@@ -10,7 +10,11 @@ end
10
10
 
11
11
  unless ENV["PUMA_DISABLE_SSL"]
12
12
  # don't use pkg_config('openssl') if '--with-openssl-dir' is used
13
- has_openssl_dir = dir_config('openssl').any?
13
+ # also looks within the Ruby build for directory info
14
+ has_openssl_dir = dir_config('openssl').any? ||
15
+ RbConfig::CONFIG['configure_args']&.include?('openssl') ||
16
+ Dir.exist?("#{RbConfig::TOPDIR}/src/main/c/openssl") # TruffleRuby
17
+
14
18
  found_pkg_config = !has_openssl_dir && pkg_config('openssl')
15
19
 
16
20
  found_ssl = if !$mingw && found_pkg_config
@@ -546,7 +546,7 @@ NORETURN(void raise_error(SSL* ssl, int result));
546
546
 
547
547
  void raise_error(SSL* ssl, int result) {
548
548
  char buf[512];
549
- char msg[512];
549
+ char msg[768];
550
550
  const char* err_str;
551
551
  int err = errno;
552
552
  int mask = 4095;
@@ -804,6 +804,10 @@ void Init_mini_ssl(VALUE puma) {
804
804
 
805
805
  rb_define_method(eng, "init?", engine_init, 0);
806
806
 
807
+ /* @!attribute [r] peercert
808
+ * Returns `nil` when `MiniSSL::Context#verify_mode` is set to `VERIFY_NONE`.
809
+ * @return [String, nil] DER encoded cert
810
+ */
807
811
  rb_define_method(eng, "peercert", engine_peercert, 0);
808
812
 
809
813
  rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
@@ -47,6 +47,7 @@ import static javax.net.ssl.SSLEngineResult.Status;
47
47
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
48
48
 
49
49
  public class MiniSSL extends RubyObject { // MiniSSL::Engine
50
+ private static final long serialVersionUID = -6903439483039141234L;
50
51
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
51
52
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
52
53
  return new MiniSSL(runtime, klass);
@@ -500,7 +501,7 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
500
501
  }
501
502
 
502
503
  private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
503
- RaiseException ex = new RaiseException(runtime, errorClass, message, true);
504
+ RaiseException ex = RaiseException.from(runtime, errorClass, message);
504
505
  ex.initCause(cause);
505
506
  return ex;
506
507
  }
data/lib/puma/client.rb CHANGED
@@ -51,6 +51,14 @@ module Puma
51
51
  CHUNK_VALID_ENDING = Const::LINE_END
52
52
  CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
53
53
 
54
+ # The maximum number of bytes we'll buffer looking for a valid
55
+ # chunk header.
56
+ MAX_CHUNK_HEADER_SIZE = 4096
57
+
58
+ # The maximum amount of excess data the client sends
59
+ # using chunk size extensions before we abort the connection.
60
+ MAX_CHUNK_EXCESS = 16 * 1024
61
+
54
62
  # Content-Length header value validation
55
63
  CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
56
64
 
@@ -496,6 +504,7 @@ module Puma
496
504
  @chunked_body = true
497
505
  @partial_part_left = 0
498
506
  @prev_chunk = ""
507
+ @excess_cr = 0
499
508
 
500
509
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
501
510
  @body.unlink
@@ -577,6 +586,20 @@ module Puma
577
586
  end
578
587
  end
579
588
 
589
+ # Track the excess as a function of the size of the
590
+ # header vs the size of the actual data. Excess can
591
+ # go negative (and is expected to) when the body is
592
+ # significant.
593
+ # The additional of chunk_hex.size and 2 compensates
594
+ # for a client sending 1 byte in a chunked body over
595
+ # a long period of time, making sure that that client
596
+ # isn't accidentally eventually punished.
597
+ @excess_cr += (line.size - len - chunk_hex.size - 2)
598
+
599
+ if @excess_cr >= MAX_CHUNK_EXCESS
600
+ raise HttpParserError, "Maximum chunk excess detected"
601
+ end
602
+
580
603
  len += 2
581
604
 
582
605
  part = io.read(len)
@@ -604,6 +627,10 @@ module Puma
604
627
  @partial_part_left = len - part.size
605
628
  end
606
629
  else
630
+ if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
631
+ raise HttpParserError, "maximum size of chunk header exceeded"
632
+ end
633
+
607
634
  @prev_chunk = line
608
635
  return false
609
636
  end
data/lib/puma/cluster.rb CHANGED
@@ -85,9 +85,7 @@ module Puma
85
85
  @workers << WorkerHandle.new(idx, pid, @phase, @options)
86
86
  end
87
87
 
88
- if @options[:fork_worker] &&
89
- @workers.all? {|x| x.phase == @phase}
90
-
88
+ if @options[:fork_worker] && all_workers_in_phase?
91
89
  @fork_writer << "0\n"
92
90
  end
93
91
  end
@@ -148,10 +146,22 @@ module Puma
148
146
  idx
149
147
  end
150
148
 
149
+ def worker_at(idx)
150
+ @workers.find { |w| w.index == idx }
151
+ end
152
+
151
153
  def all_workers_booted?
152
154
  @workers.count { |w| !w.booted? } == 0
153
155
  end
154
156
 
157
+ def all_workers_in_phase?
158
+ @workers.all? { |w| w.phase == @phase }
159
+ end
160
+
161
+ def all_workers_idle_timed_out?
162
+ (@workers.map(&:pid) - idle_timed_out_worker_pids).empty?
163
+ end
164
+
155
165
  def check_workers
156
166
  return if @next_check >= Time.now
157
167
 
@@ -276,7 +286,7 @@ module Puma
276
286
 
277
287
  # @version 5.0.0
278
288
  def fork_worker!
279
- if (worker = @workers.find { |w| w.index == 0 })
289
+ if (worker = worker_at 0)
280
290
  worker.phase += 1
281
291
  end
282
292
  phased_restart(true)
@@ -338,6 +348,8 @@ module Puma
338
348
  def run
339
349
  @status = :run
340
350
 
351
+ @idle_workers = {}
352
+
341
353
  output_header "cluster"
342
354
 
343
355
  # This is aligned with the output from Runner, see Runner#output_header
@@ -411,6 +423,8 @@ module Puma
411
423
 
412
424
  @master_read, @worker_write = read, @wakeup
413
425
 
426
+ @options[:worker_write] = @worker_write
427
+
414
428
  @config.run_hooks(:before_fork, nil, @log_writer)
415
429
 
416
430
  spawn_workers
@@ -426,6 +440,11 @@ module Puma
426
440
 
427
441
  while @status == :run
428
442
  begin
443
+ if all_workers_idle_timed_out?
444
+ log "- All workers reached idle timeout"
445
+ break
446
+ end
447
+
429
448
  if @phased_restart
430
449
  start_phased_restart
431
450
  @phased_restart = false
@@ -446,7 +465,7 @@ module Puma
446
465
 
447
466
  if req == "b" || req == "f"
448
467
  pid, idx = result.split(':').map(&:to_i)
449
- w = @workers.find {|x| x.index == idx}
468
+ w = worker_at idx
450
469
  w.pid = pid if w.pid.nil?
451
470
  end
452
471
 
@@ -463,24 +482,37 @@ module Puma
463
482
  when "t"
464
483
  w.term unless w.term?
465
484
  when "p"
466
- w.ping!(result.sub(/^\d+/,'').chomp)
485
+ status = result.sub(/^\d+/,'').chomp
486
+ w.ping!(status)
467
487
  @events.fire(:ping!, w)
488
+
489
+ if in_phased_restart && workers_not_booted.positive? && w0 = worker_at(0)
490
+ w0.ping!(status)
491
+ @events.fire(:ping!, w0)
492
+ end
493
+
468
494
  if !booted && @workers.none? {|worker| worker.last_status.empty?}
469
495
  @events.fire_on_booted!
470
496
  debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
471
497
  booted = true
472
498
  end
499
+ when "i"
500
+ if @idle_workers[pid]
501
+ @idle_workers.delete pid
502
+ else
503
+ @idle_workers[pid] = true
504
+ end
473
505
  end
474
506
  else
475
507
  log "! Out-of-sync worker list, no #{pid} worker"
476
508
  end
477
509
  end
510
+
478
511
  if in_phased_restart && workers_not_booted.zero?
479
512
  @events.fire_on_booted!
480
513
  debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
481
514
  in_phased_restart = false
482
515
  end
483
-
484
516
  rescue Interrupt
485
517
  @status = :stop
486
518
  end
@@ -509,10 +541,28 @@ module Puma
509
541
  # loops thru @workers, removing workers that exited, and calling
510
542
  # `#term` if needed
511
543
  def wait_workers
544
+ # Reap all children, known workers or otherwise.
545
+ # If puma has PID 1, as it's common in containerized environments,
546
+ # then it's responsible for reaping orphaned processes, so we must reap
547
+ # all our dead children, regardless of whether they are workers we spawned
548
+ # or some reattached processes.
549
+ reaped_children = {}
550
+ loop do
551
+ begin
552
+ pid, status = Process.wait2(-1, Process::WNOHANG)
553
+ break unless pid
554
+ reaped_children[pid] = status
555
+ rescue Errno::ECHILD
556
+ break
557
+ end
558
+ end
559
+
512
560
  @workers.reject! do |w|
513
561
  next false if w.pid.nil?
514
562
  begin
515
- if Process.wait(w.pid, Process::WNOHANG)
563
+ # When `fork_worker` is enabled, some worker may not be direct children, but grand children.
564
+ # Because of this they won't be reaped by `Process.wait2(-1)`, so we need to check them individually)
565
+ if reaped_children.delete(w.pid) || (@options[:fork_worker] && Process.wait(w.pid, Process::WNOHANG))
516
566
  true
517
567
  else
518
568
  w.term if w.term?
@@ -529,6 +579,11 @@ module Puma
529
579
  end
530
580
  end
531
581
  end
582
+
583
+ # Log unknown children
584
+ reaped_children.each do |pid, status|
585
+ log "! reaped unknown child process pid=#{pid} status=#{status}"
586
+ end
532
587
  end
533
588
 
534
589
  # @version 5.0.0
@@ -536,14 +591,18 @@ module Puma
536
591
  @workers.each do |w|
537
592
  if !w.term? && w.ping_timeout <= Time.now
538
593
  details = if w.booted?
539
- "(worker failed to check in within #{@options[:worker_timeout]} seconds)"
594
+ "(Worker #{w.index} failed to check in within #{@options[:worker_timeout]} seconds)"
540
595
  else
541
- "(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
596
+ "(Worker #{w.index} failed to boot within #{@options[:worker_boot_timeout]} seconds)"
542
597
  end
543
598
  log "! Terminating timed out worker #{details}: #{w.pid}"
544
599
  w.kill
545
600
  end
546
601
  end
547
602
  end
603
+
604
+ def idle_timed_out_worker_pids
605
+ @idle_workers.keys
606
+ end
548
607
  end
549
608
  end
@@ -3,7 +3,7 @@
3
3
  require_relative 'rack/builder'
4
4
  require_relative 'plugin'
5
5
  require_relative 'const'
6
- # note that dsl is loaded at end of file, requires ConfigDefault constants
6
+ require_relative 'dsl'
7
7
 
8
8
  module Puma
9
9
  # A class used for storing "leveled" configuration options.
@@ -387,5 +387,3 @@ module Puma
387
387
  end
388
388
  end
389
389
  end
390
-
391
- require_relative 'dsl'
data/lib/puma/const.rb CHANGED
@@ -100,7 +100,7 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "6.4.0"
103
+ PUMA_VERSION = VERSION = "6.4.2"
104
104
  CODE_NAME = "The Eagle of Durango"
105
105
 
106
106
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
data/lib/puma/detect.rb CHANGED
@@ -12,15 +12,14 @@ module Puma
12
12
 
13
13
  IS_JRUBY = Object.const_defined? :JRUBY_VERSION
14
14
 
15
- IS_OSX = RUBY_PLATFORM.include? 'darwin'
15
+ IS_OSX = RUBY_DESCRIPTION.include? 'darwin'
16
16
 
17
- IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
18
- IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
17
+ IS_WINDOWS = RUBY_DESCRIPTION.match?(/mswin|ming|cygwin/)
19
18
 
20
19
  IS_LINUX = !(IS_OSX || IS_WINDOWS)
21
20
 
22
21
  # @version 5.2.0
23
- IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
22
+ IS_MRI = RUBY_ENGINE == 'ruby'
24
23
 
25
24
  def self.jruby?
26
25
  IS_JRUBY
data/lib/puma/dsl.rb CHANGED
@@ -729,11 +729,15 @@ module Puma
729
729
  process_hook :before_refork, key, block, 'on_refork'
730
730
  end
731
731
 
732
- # Code to run immediately before a thread starts. The worker does not
733
- # start new threads until this code finishes.
732
+ # Provide a block to be executed just before a thread is added to the thread
733
+ # pool. Be careful: while the block executes, thread creation is delayed, and
734
+ # probably a request will have to wait too! The new thread will not be added to
735
+ # the threadpool until the provided block returns.
734
736
  #
735
- # This hook is useful for doing something when a thread
736
- # starts.
737
+ # Return values are ignored.
738
+ # Raising an exception will log a warning.
739
+ #
740
+ # This hook is useful for doing something when the thread pool grows.
737
741
  #
738
742
  # This can be called multiple times to add several hooks.
739
743
  #
@@ -746,8 +750,15 @@ module Puma
746
750
  @options[:before_thread_start] << block
747
751
  end
748
752
 
749
- # Code to run immediately before a thread exits. The worker does not
750
- # accept new requests until this code finishes.
753
+ # Provide a block to be executed after a thread is trimmed from the thread
754
+ # pool. Be careful: while this block executes, Puma's main loop is
755
+ # blocked, so no new requests will be picked up.
756
+ #
757
+ # This hook only runs when a thread in the threadpool is trimmed by Puma.
758
+ # It does not run when a thread dies due to exceptions or any other cause.
759
+ #
760
+ # Return values are ignored.
761
+ # Raising an exception will log a warning.
751
762
  #
752
763
  # This hook is useful for cleaning up thread local resources when a thread
753
764
  # is trimmed.
@@ -1179,8 +1190,10 @@ module Puma
1179
1190
 
1180
1191
  def warn_if_in_single_mode(hook_name)
1181
1192
  return if @options[:silence_fork_callback_warning]
1182
-
1183
- if (@options[:workers] || 0) == 0
1193
+ # user_options (CLI) have precedence over config file
1194
+ workers_val = @config.options.user_options[:workers] || @options[:workers] ||
1195
+ @config.puma_default_options[:workers] || 0
1196
+ if workers_val == 0
1184
1197
  log_string =
1185
1198
  "Warning: You specified code to run in a `#{hook_name}` block, " \
1186
1199
  "but Puma is not configured to run in cluster mode (worker count > 0 ), " \
@@ -51,6 +51,8 @@ module Puma
51
51
  unless params['ca']
52
52
  log_writer.error "Please specify the SSL ca via 'ca='"
53
53
  end
54
+ # needed for Puma::MiniSSL::Socket#peercert, env['puma.peercert']
55
+ require 'openssl'
54
56
  end
55
57
 
56
58
  ctx.ca = params['ca'] if params['ca']
data/lib/puma/minissl.rb CHANGED
@@ -184,6 +184,11 @@ module Puma
184
184
  @socket.peeraddr
185
185
  end
186
186
 
187
+ # OpenSSL is loaded in `MiniSSL::ContextBuilder` when
188
+ # `MiniSSL::Context#verify_mode` is not `VERIFY_NONE`.
189
+ # When `VERIFY_NONE`, `MiniSSL::Engine#peercert` is nil, regardless of
190
+ # whether the client sends a cert.
191
+ # @return [OpenSSL::X509::Certificate, nil]
187
192
  # @!attribute [r] peercert
188
193
  def peercert
189
194
  return @peercert if @peercert
data/lib/puma/null_io.rb CHANGED
@@ -18,8 +18,22 @@ module Puma
18
18
 
19
19
  # Mimics IO#read with no data.
20
20
  #
21
- def read(count = nil, _buffer = nil)
22
- count && count > 0 ? nil : ""
21
+ def read(length = nil, buffer = nil)
22
+ if length.to_i < 0
23
+ raise ArgumentError, "(negative length #{length} given)"
24
+ end
25
+
26
+ buffer = if buffer.nil?
27
+ "".b
28
+ else
29
+ String.try_convert(buffer) or raise TypeError, "no implicit conversion of #{buffer.class} into String"
30
+ end
31
+ buffer.clear
32
+ if length.to_i > 0
33
+ nil
34
+ else
35
+ buffer
36
+ end
23
37
  end
24
38
 
25
39
  def rewind
Binary file
data/lib/puma/runner.rb CHANGED
@@ -70,7 +70,7 @@ module Puma
70
70
 
71
71
  app = Puma::App::Status.new @launcher, token
72
72
 
73
- # A Reactor is not created aand nio4r is not loaded when 'queue_requests: false'
73
+ # A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
74
74
  # Use `nil` for events, no hooks in control server
75
75
  control = Puma::Server.new app, nil,
76
76
  { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
data/lib/puma/server.rb CHANGED
@@ -81,6 +81,8 @@ module Puma
81
81
  UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
82
82
  end
83
83
 
84
+ @clustered = (@options.fetch :workers, 0) > 0
85
+ @worker_write = @options[:worker_write]
84
86
  @log_writer = @options.fetch :log_writer, LogWriter.stdio
85
87
  @early_hints = @options[:early_hints]
86
88
  @first_data_timeout = @options[:first_data_timeout]
@@ -117,6 +119,8 @@ module Puma
117
119
  @precheck_closing = true
118
120
 
119
121
  @requests_count = 0
122
+
123
+ @idle_timeout_reached = false
120
124
  end
121
125
 
122
126
  def inherit_binder(bind)
@@ -328,10 +332,26 @@ module Puma
328
332
  begin
329
333
  ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
330
334
  unless ios
331
- @status = :stop unless shutting_down?
335
+ unless shutting_down?
336
+ @idle_timeout_reached = true
337
+
338
+ if @clustered
339
+ @worker_write << "i#{Process.pid}\n" rescue nil
340
+ next
341
+ else
342
+ @log_writer.log "- Idle timeout reached"
343
+ @status = :stop
344
+ end
345
+ end
346
+
332
347
  break
333
348
  end
334
349
 
350
+ if @idle_timeout_reached && @clustered
351
+ @idle_timeout_reached = false
352
+ @worker_write << "i#{Process.pid}\n" rescue nil
353
+ end
354
+
335
355
  ios.first.each do |sock|
336
356
  if sock == check
337
357
  break if handle_check
@@ -367,6 +387,7 @@ module Puma
367
387
  @queue_requests = false
368
388
  @reactor.shutdown
369
389
  end
390
+
370
391
  graceful_shutdown if @status == :stop || @status == :restart
371
392
  rescue Exception => e
372
393
  @log_writer.unknown_error e, nil, "Exception handling servers"
@@ -56,11 +56,11 @@ module Puma
56
56
  end
57
57
 
58
58
  ALLOWED_FIELDS.each do |f|
59
- define_method f do
59
+ define_method f.to_sym do
60
60
  @options[f]
61
61
  end
62
62
 
63
- define_method "#{f}=" do |v|
63
+ define_method :"#{f}=" do |v|
64
64
  @options[f] = v
65
65
  end
66
66
  end
data/tools/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use this Dockerfile to create minimal reproductions of issues
2
2
 
3
- FROM ruby:3.1
3
+ FROM ruby:3.2
4
4
 
5
5
  # throw errors if Gemfile has been modified since Gemfile.lock
6
6
  RUN bundle config --global frozen 1
@@ -8,7 +8,7 @@ RUN bundle config --global frozen 1
8
8
  WORKDIR /usr/src/app
9
9
 
10
10
  COPY . .
11
- RUN gem install bundler
11
+
12
12
  RUN bundle install
13
13
  RUN bundle exec rake compile
14
14
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.4.0
4
+ version: 6.4.2
5
5
  platform: java
6
6
  authors:
7
7
  - Evan Phoenix
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 1980-01-01 00:00:00.000000000 Z
11
+ date: 2024-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement