puma 6.3.1 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcbde9283993550beb848a4f777bf9d33a1e163f5356cfeeb40be3eede72e7d0
4
- data.tar.gz: 5394fdd8307e5a1fd40c7cf560d01565a6a9d69fd0fa0006ff6b28e483e627aa
3
+ metadata.gz: e58946300296f4d215a5859fcadb101b4fcec6d9012b38fd0af10c5fdb3ce0c2
4
+ data.tar.gz: 397fa109025cbc87d7466be97ed91e44789d522db2c4b89d1ef5f4e40db9f772
5
5
  SHA512:
6
- metadata.gz: 473b3047986b69763e01fb3b3f7fc1137070cb041ae235e520216e568860295a3b0ed105e4c52ee1d3aa05353d1b247e0782a1aa7bde840a540200f21d84320b
7
- data.tar.gz: 69e625526fcc0b7216a419326e172114ed0ba4c7842722c1f896ed4d2fd559e4b58ea31f95a7ca52d37f11b5930433cfa6faab3f510996a39b1de3d3ff46d2a7
6
+ metadata.gz: a0c6a4e6c10522de9a9c7d5eed57d5205a6d4acfe18e9d7c310044d5793a2a67a266c3b69f8ed4c5385559149dc858dbcf1b4307c86459b9723ea49303de5685
7
+ data.tar.gz: cf7a60331c499a1387414ca2d6955edbc239242a62ea06941bf912714ee4dcb9295a5a1bd9c3561fd396c54a2e04e5a0aa2194ef17b37accb78e049e4cb1ca6b
data/History.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 6.4.0 / 2023-09-21
2
+
3
+ * Features
4
+ * on_thread_exit hook ([#2920])
5
+ * on_thread_start_hook ([#3195])
6
+ * Shutdown on idle ([#3209], [#2580])
7
+ * New error message when control server port taken ([#3204])
8
+
9
+ * Refactor
10
+ * Remove `Forwardable` dependency ([#3191], #3190)
11
+ * Update URLMap Regexp usage for Ruby v3.3 ([#3165])
12
+
13
+ * Bugfixes
14
+ * Bring the cert_pem: parameter into parity with the cert: parameter to ssl_bind. ([#3174])
15
+ * Fix using control server with IPv6 host ([#3181])
16
+ * control_cli.rb - add require_relative 'log_writer' ([#3187])
17
+ * Fix cases where fallback Rack response wasn't sent to the client ([#3094])
18
+
1
19
  ## 6.3.1 / 2023-08-18
2
20
 
3
21
  * Security
@@ -15,7 +33,7 @@
15
33
  * Handle malformed request path ([#3155], [#3148])
16
34
  * Misc lib file fixes - trapping additional errors, CI helper ([#3129])
17
35
  * Fixup req form data file upload with "r\n" line endings ([#3137])
18
- * Restore rack 1.6 compatibility Restore rack 1.6 compatibility ([#3156])
36
+ * Restore rack 1.6 compatibility ([#3156])
19
37
 
20
38
  * Refactor
21
39
  * const.rb - Update Puma::HTTP_STATUS_CODES ([#3162])
@@ -126,6 +144,16 @@
126
144
  * Ruby 3.2 will have native IO#wait_* methods, don't require io/wait ([#2903])
127
145
  * Various internal API refactorings ([#2942], [#2921], [#2922], [#2955])
128
146
 
147
+ ## 5.6.7 / 2023-08-18
148
+
149
+ * Security
150
+ * 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
+
152
+ ## 5.6.6 / 2023-06-21
153
+
154
+ * Bugfix
155
+ * Prevent loading with rack 3 ([#3166])
156
+
129
157
  ## 5.6.5 / 2022-08-23
130
158
 
131
159
  * Feature
@@ -2001,6 +2029,17 @@ be added back in a future date when a java Puma::MiniSSL is added.
2001
2029
  * Bugfixes
2002
2030
  * Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
2003
2031
 
2032
+ [#2920]:https://github.com/puma/puma/pull/2920 "PR by @biinari, merged 2023-07-11"
2033
+ [#3195]:https://github.com/puma/puma/pull/3195 "PR by @binarygit, merged 2023-08-15"
2034
+ [#3209]:https://github.com/puma/puma/pull/3209 "PR by @joshuay03, merged 2023-09-04"
2035
+ [#2580]:https://github.com/puma/puma/issues/2580 "Issue by @schuetzm, closed 2023-09-04"
2036
+ [#3204]:https://github.com/puma/puma/pull/3204 "PR by @dhavalsingh, merged 2023-08-25"
2037
+ [#3191]:https://github.com/puma/puma/pull/3191 "PR by @MSP-Greg, merged 2023-08-31"
2038
+ [#3165]:https://github.com/puma/puma/pull/3165 "PR by @fallwith, merged 2023-06-06"
2039
+ [#3174]:https://github.com/puma/puma/pull/3174 "PR by @copiousfreetime, merged 2023-06-11"
2040
+ [#3181]:https://github.com/puma/puma/pull/3181 "PR by @MSP-Greg, merged 2023-06-23"
2041
+ [#3187]:https://github.com/puma/puma/pull/3187 "PR by @MSP-Greg, merged 2023-06-30"
2042
+ [#3094]:https://github.com/puma/puma/pull/3094 "PR by @Vuta, merged 2023-07-23"
2004
2043
  [#3106]:https://github.com/puma/puma/pull/3106 "PR by @MSP-Greg, merged 2023-05-29"
2005
2044
  [#3014]:https://github.com/puma/puma/issues/3014 "Issue by @kyledrake, closed 2023-05-29"
2006
2045
  [#3161]:https://github.com/puma/puma/pull/3161 "PR by @MSP-Greg, merged 2023-05-27"
@@ -2099,6 +2138,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
2099
2138
  [#2921]:https://github.com/puma/puma/issues/2921 "Issue by @MSP-Greg, closed 2022-09-15"
2100
2139
  [#2922]:https://github.com/puma/puma/issues/2922 "Issue by @MSP-Greg, closed 2022-09-10"
2101
2140
  [#2955]:https://github.com/puma/puma/pull/2955 "PR by @cafedomancer, merged 2022-09-15"
2141
+ [#3166]:https://github.com/puma/puma/pull/3166 "PR by @JoeDupuis, merged 2023-06-08"
2102
2142
  [#2868]:https://github.com/puma/puma/pull/2868 "PR by @MSP-Greg, merged 2022-06-02"
2103
2143
  [#2866]:https://github.com/puma/puma/issues/2866 "Issue by @slondr, closed 2022-06-02"
2104
2144
  [#2883]:https://github.com/puma/puma/pull/2883 "PR by @MSP-Greg, merged 2022-06-02"
@@ -2125,7 +2165,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
2125
2165
  [#2794]:https://github.com/puma/puma/pull/2794 "PR by @johnnyshields, merged 2022-01-10"
2126
2166
  [#2759]:https://github.com/puma/puma/pull/2759 "PR by @ob-stripe, merged 2021-12-11"
2127
2167
  [#2731]:https://github.com/puma/puma/pull/2731 "PR by @baelter, merged 2021-11-02"
2128
- [#2341]:https://github.com/puma/puma/issues/2341 "Issue by @cjlarose, opened 2020-08-18"
2168
+ [#2341]:https://github.com/puma/puma/issues/2341 "Issue by @cjlarose, closed 2023-07-23"
2129
2169
  [#2728]:https://github.com/puma/puma/pull/2728 "PR by @dalibor, merged 2021-10-31"
2130
2170
  [#2733]:https://github.com/puma/puma/pull/2733 "PR by @ob-stripe, merged 2021-12-12"
2131
2171
  [#2807]:https://github.com/puma/puma/pull/2807 "PR by @MSP-Greg, merged 2022-01-25"
data/README.md CHANGED
@@ -12,11 +12,15 @@ Puma is a **simple, fast, multi-threaded, and highly parallel HTTP 1.1 server fo
12
12
 
13
13
  ## Built For Speed &amp; Parallelism
14
14
 
15
- Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request using a thread pool. Each request is served in a separate thread, so truly parallel Ruby implementations (JRuby, Rubinius) will use all available CPU cores.
15
+ Puma is a server for [Rack](https://github.com/rack/rack)-powered HTTP applications written in Ruby. It is:
16
+ * **Multi-threaded**. Each request is served in a separate thread. This helps you serve more requests per second with less memory use.
17
+ * **Multi-process**. "Pre-forks" in cluster mode, using less memory per-process thanks to copy-on-write memory.
18
+ * **Standalone**. With SSL support, zero-downtime rolling restarts and a built-in request bufferer, you can deploy Puma without any reverse proxy.
19
+ * **Battle-tested**. Our HTTP parser is inherited from Mongrel and has over 15 years of production use. Puma is currently the most popular Ruby webserver, and is the default server for Ruby on Rails.
16
20
 
17
21
  Originally designed as a server for [Rubinius](https://github.com/rubinius/rubinius), Puma also works well with Ruby (MRI) and JRuby.
18
22
 
19
- On MRI, there is a Global VM Lock (GVL) that ensures only one thread can run Ruby code at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing IO waiting to be done in parallel.
23
+ On MRI, there is a Global VM Lock (GVL) that ensures only one thread can run Ruby code at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing IO waiting to be done in parallel. Truly parallel Ruby implementations (TruffleRuby, JRuby) don't have this limitation.
20
24
 
21
25
  ## Quick Start
22
26
 
@@ -114,6 +118,8 @@ $ WEB_CONCURRENCY=3 puma -t 8:32
114
118
 
115
119
  Note that threads are still used in clustered 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.
116
120
 
121
+ 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).
122
+
117
123
  In clustered 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).
118
124
 
119
125
  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:
@@ -211,29 +217,32 @@ Puma supports the [`localhost`] gem for self-signed certificates. This is partic
211
217
 
212
218
  Puma automatically configures SSL when the [`localhost`] gem is loaded in a `development` environment:
213
219
 
220
+ Add the gem to your Gemfile:
214
221
  ```ruby
215
- # Add the gem to your Gemfile
216
222
  group(:development) do
217
223
  gem 'localhost'
218
224
  end
225
+ ```
219
226
 
220
- # And require it implicitly using bundler
227
+ And require it implicitly using bundler:
228
+ ```ruby
221
229
  require "bundler"
222
230
  Bundler.require(:default, ENV["RACK_ENV"].to_sym)
231
+ ```
223
232
 
224
- # Alternatively, you can require the gem in config.ru:
225
- require './app'
233
+ Alternatively, you can require the gem in your configuration file, either `config/puma/development.rb`, `config/puma.rb`, or set via the `-C` cli option:
234
+ ```ruby
226
235
  require 'localhost'
227
- run Sinatra::Application
236
+ # configuration methods (from Puma::DSL) as needed
228
237
  ```
229
238
 
230
239
  Additionally, Puma must be listening to an SSL socket:
231
240
 
232
241
  ```shell
233
- $ puma -b 'ssl://localhost:9292' config.ru
242
+ $ puma -b 'ssl://localhost:9292' -C config/use_local_host.rb
234
243
 
235
244
  # The following options allow you to reach Puma over HTTP as well:
236
- $ puma -b ssl://localhost:9292 -b tcp://localhost:9393 config.ru
245
+ $ puma -b ssl://localhost:9292 -b tcp://localhost:9393 -C config/use_local_host.rb
237
246
  ```
238
247
 
239
248
  [`localhost`]: https://github.com/socketry/localhost
data/docs/kubernetes.md CHANGED
@@ -64,3 +64,15 @@ There is a subtle race condition between step 2 and 3: The replication controlle
64
64
  The way Kubernetes works this way, rather than handling step 2 synchronously, is due to the CAP theorem: in a distributed system there is no way to guarantee that any message will arrive promptly. In particular, waiting for all Service controllers to report back might get stuck for an indefinite time if one of them has already been terminated or if there has been a net split. A way to work around this is to add a sleep to the pre-stop hook of the same time as the `terminationGracePeriodSeconds` time. This will allow the Puma process to keep serving new requests during the entire grace period, although it will no longer receive new requests after all Service controllers have propagated the removal of the pod from their endpoint lists. Then, after `terminationGracePeriodSeconds`, the pod receives `SIGKILL` and closes down. If your process can't handle SIGKILL properly, for example because it needs to release locks in different services, you can also sleep for a shorter period (and/or increase `terminationGracePeriodSeconds`) as long as the time slept is longer than the time that your Service controllers take to propagate the pod removal. The downside of this workaround is that all pods will take at minimum the amount of time slept to shut down and this will increase the time required for your rolling deploy.
65
65
 
66
66
  More discussions and links to relevant articles can be found in https://github.com/puma/puma/issues/2343.
67
+
68
+ ## Workers Per Pod, and Other Config Issues
69
+
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
+
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.
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
+ * 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
+ * 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.
76
+ * If multithreaded, allocate 1 CPU per worker. If single threaded, allocate 0.75 cpus per worker. Most web applications spend about 25% of their time in I/O - but when you're running multi-threaded, your Puma process will have higher CPU usage and should be able to fully saturate a CPU core.
77
+ * Most Puma processes will use about ~512MB-1GB per worker, and about 1GB for the master process. However, you probably shouldn't bother with setting memory limits lower than around 2GB per process, because most places you are deploying will have 2GB of RAM per CPU. A sensible memory limit for a Puma configuration of 4 child workers might be something like 8 GB (1 GB for the master, 7GB for the 4 children).
78
+
@@ -36,6 +36,12 @@ void raise_file_error(const char* caller, const char *filename) {
36
36
  rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
37
37
  }
38
38
 
39
+ NORETURN(void raise_param_error(const char* caller, const char *param));
40
+
41
+ void raise_param_error(const char* caller, const char *param) {
42
+ rb_raise(eError, "%s: error with parameter '%s': %s", caller, param, ERR_error_string(ERR_get_error(), NULL));
43
+ }
44
+
39
45
  void engine_free(void *ptr) {
40
46
  ms_conn *conn = ptr;
41
47
  ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
@@ -226,7 +232,7 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
226
232
  VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
227
233
  verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
228
234
  BIO *bio;
229
- X509 *x509;
235
+ X509 *x509 = NULL;
230
236
  EVP_PKEY *pkey;
231
237
  pem_password_cb *password_cb = NULL;
232
238
  const char *password = NULL;
@@ -298,16 +304,65 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
298
304
  }
299
305
 
300
306
  if (!NIL_P(cert_pem)) {
307
+ X509 *ca = NULL;
308
+ unsigned long err;
309
+
301
310
  bio = BIO_new(BIO_s_mem());
302
311
  BIO_puts(bio, RSTRING_PTR(cert_pem));
312
+
313
+ /**
314
+ * Much of this pulled as a simplified version of the `use_certificate_chain_file` method
315
+ * from openssl's `ssl_rsa.c` file.
316
+ */
317
+
318
+ /* first read the cert as the first item in the pem file */
303
319
  x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
320
+ if (NULL == x509) {
321
+ BIO_free_all(bio);
322
+ raise_param_error("PEM_read_bio_X509", "cert_pem");
323
+ }
304
324
 
305
- if (SSL_CTX_use_certificate(ctx, x509) != 1) {
306
- BIO_free(bio);
307
- raise_file_error("SSL_CTX_use_certificate", RSTRING_PTR(cert_pem));
325
+ /* Add the cert to the context */
326
+ /* 1 is success - otherwise check the error codes */
327
+ if (1 != SSL_CTX_use_certificate(ctx, x509)) {
328
+ BIO_free_all(bio);
329
+ raise_param_error("SSL_CTX_use_certificate", "cert_pem");
330
+ }
331
+
332
+ X509_free(x509); /* no longer need our reference */
333
+
334
+ /* Now lets load up the rest of the certificate chain */
335
+ /* 1 is success 0 is error */
336
+ if (0 == SSL_CTX_clear_chain_certs(ctx)) {
337
+ BIO_free_all(bio);
338
+ raise_param_error("SSL_CTX_clear_chain_certs","cert_pem");
339
+ }
340
+
341
+ while (1) {
342
+ ca = PEM_read_bio_X509(bio, NULL, NULL, NULL);
343
+
344
+ if (NULL == ca) {
345
+ break;
346
+ }
347
+
348
+ if (0 == SSL_CTX_add0_chain_cert(ctx, ca)) {
349
+ BIO_free_all(bio);
350
+ raise_param_error("SSL_CTX_add0_chain_cert","cert_pem");
351
+ }
352
+ /* don't free ca - its now owned by the context */
353
+ }
354
+
355
+ /* ca is NULL - so its either the end of the file or an error */
356
+ err = ERR_peek_last_error();
357
+
358
+ /* If its the end of the file - then we are done, in any case free the bio */
359
+ BIO_free_all(bio);
360
+
361
+ if ((ERR_GET_LIB(err) == ERR_LIB_PEM) && (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
362
+ ERR_clear_error();
363
+ } else {
364
+ raise_param_error("PEM_read_bio_X509","cert_pem");
308
365
  }
309
- X509_free(x509);
310
- BIO_free(bio);
311
366
  }
312
367
 
313
368
  if (!NIL_P(key_pem)) {
data/lib/puma/binder.rb CHANGED
@@ -330,7 +330,7 @@ module Puma
330
330
  return
331
331
  end
332
332
 
333
- host = host[1..-2] if host and host[0..0] == '['
333
+ host = host[1..-2] if host&.start_with? '['
334
334
  tcp_server = TCPServer.new(host, port)
335
335
 
336
336
  if optimize_for_latency
@@ -364,7 +364,7 @@ module Puma
364
364
  return
365
365
  end
366
366
 
367
- host = host[1..-2] if host[0..0] == '['
367
+ host = host[1..-2] if host&.start_with? '['
368
368
  s = TCPServer.new(host, port)
369
369
  if optimize_for_latency
370
370
  s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
data/lib/puma/cli.rb CHANGED
@@ -144,6 +144,10 @@ module Puma
144
144
  $LOAD_PATH.unshift(*arg.split(':'))
145
145
  end
146
146
 
147
+ o.on "--idle-timeout SECONDS", "Number of seconds until the next request before automatic shutdown" do |arg|
148
+ user_config.idle_timeout arg
149
+ end
150
+
147
151
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
148
152
  "Use -b for more advanced options" do |arg|
149
153
  user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
data/lib/puma/client.rb CHANGED
@@ -11,7 +11,6 @@ end
11
11
  require_relative 'detect'
12
12
  require_relative 'io_buffer'
13
13
  require 'tempfile'
14
- require 'forwardable'
15
14
 
16
15
  if Puma::IS_JRUBY
17
16
  # We have to work around some OpenSSL buffer/io-readiness bugs
@@ -62,7 +61,6 @@ module Puma
62
61
  EmptyBody = NullIO.new
63
62
 
64
63
  include Puma::Const
65
- extend Forwardable
66
64
 
67
65
  def initialize(io, env=nil)
68
66
  @io = io
@@ -111,7 +109,10 @@ module Puma
111
109
 
112
110
  attr_accessor :remote_addr_header, :listener
113
111
 
114
- def_delegators :@io, :closed?
112
+ # Remove in Puma 7?
113
+ def closed?
114
+ @to_io.closed?
115
+ end
115
116
 
116
117
  # Test to see if io meets a bare minimum of functioning, @to_io needs to be
117
118
  # used for MiniSSL::Socket
@@ -133,8 +133,10 @@ module Puma
133
133
  debug: false,
134
134
  early_hints: nil,
135
135
  environment: 'development'.freeze,
136
- # Number of seconds to wait until we get the first data for the request
136
+ # Number of seconds to wait until we get the first data for the request.
137
137
  first_data_timeout: 30,
138
+ # Number of seconds to wait until the next request before shutting down.
139
+ idle_timeout: nil,
138
140
  io_selector_backend: :auto,
139
141
  log_requests: false,
140
142
  logger: STDOUT,
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 = "6.3.1"
104
- CODE_NAME = "Mugi No Toki Itaru"
103
+ PUMA_VERSION = VERSION = "6.4.0"
104
+ CODE_NAME = "The Eagle of Durango"
105
105
 
106
106
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
107
107
 
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'optparse'
4
- require_relative 'state_file'
5
4
  require_relative 'const'
6
5
  require_relative 'detect'
7
- require_relative 'configuration'
8
6
  require 'uri'
9
7
  require 'socket'
10
8
 
@@ -126,6 +124,9 @@ module Puma
126
124
  end
127
125
 
128
126
  if @config_file
127
+ require_relative 'configuration'
128
+ require_relative 'log_writer'
129
+
129
130
  config = Puma::Configuration.new({ config_files: [@config_file] }, {})
130
131
  config.load
131
132
  @state ||= config.options[:state]
@@ -149,6 +150,8 @@ module Puma
149
150
  raise "State file not found: #{@state}"
150
151
  end
151
152
 
153
+ require_relative 'state_file'
154
+
152
155
  sf = Puma::StateFile.new
153
156
  sf.load @state
154
157
 
@@ -164,22 +167,26 @@ module Puma
164
167
  def send_request
165
168
  uri = URI.parse @control_url
166
169
 
170
+ host = uri.host
171
+
167
172
  # create server object by scheme
168
173
  server =
169
174
  case uri.scheme
170
175
  when 'ssl'
171
176
  require 'openssl'
177
+ host = host[1..-2] if host&.start_with? '['
172
178
  OpenSSL::SSL::SSLSocket.new(
173
- TCPSocket.new(uri.host, uri.port),
179
+ TCPSocket.new(host, uri.port),
174
180
  OpenSSL::SSL::SSLContext.new)
175
181
  .tap { |ssl| ssl.sync_close = true } # default is false
176
182
  .tap(&:connect)
177
183
  when 'tcp'
178
- TCPSocket.new uri.host, uri.port
184
+ host = host[1..-2] if host&.start_with? '['
185
+ TCPSocket.new host, uri.port
179
186
  when 'unix'
180
187
  # check for abstract UNIXSocket
181
188
  UNIXSocket.new(@control_url.start_with?('unix://@') ?
182
- "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
189
+ "\0#{host}#{uri.path}" : "#{host}#{uri.path}")
183
190
  else
184
191
  raise "Invalid scheme: #{uri.scheme}"
185
192
  end
data/lib/puma/dsl.rb CHANGED
@@ -315,16 +315,22 @@ module Puma
315
315
  bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
316
316
  end
317
317
 
318
+ # Define how long the tcp socket stays open, if no data has been received.
319
+ # @see Puma::Server.new
320
+ def first_data_timeout(seconds)
321
+ @options[:first_data_timeout] = Integer(seconds)
322
+ end
323
+
318
324
  # Define how long persistent connections can be idle before Puma closes them.
319
325
  # @see Puma::Server.new
320
326
  def persistent_timeout(seconds)
321
327
  @options[:persistent_timeout] = Integer(seconds)
322
328
  end
323
329
 
324
- # Define how long the tcp socket stays open, if no data has been received.
330
+ # If a new request is not received within this number of seconds, begin shutting down.
325
331
  # @see Puma::Server.new
326
- def first_data_timeout(seconds)
327
- @options[:first_data_timeout] = Integer(seconds)
332
+ def idle_timeout(seconds)
333
+ @options[:idle_timeout] = Integer(seconds)
328
334
  end
329
335
 
330
336
  # Work around leaky apps that leave garbage in Thread locals
@@ -510,6 +516,12 @@ module Puma
510
516
  # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
511
517
  # and/or `:timeout` keys, each with integer values.
512
518
  #
519
+ # The `cert:` options hash parameter can be the path to a certificate
520
+ # file including all intermediate certificates in PEM format.
521
+ #
522
+ # The `cert_pem:` options hash parameter can be String containing the
523
+ # cerificate and all intermediate certificates in PEM format.
524
+ #
513
525
  # @example
514
526
  # ssl_bind '127.0.0.1', '9292', {
515
527
  # cert: path_to_cert,
@@ -717,6 +729,40 @@ module Puma
717
729
  process_hook :before_refork, key, block, 'on_refork'
718
730
  end
719
731
 
732
+ # Code to run immediately before a thread starts. The worker does not
733
+ # start new threads until this code finishes.
734
+ #
735
+ # This hook is useful for doing something when a thread
736
+ # starts.
737
+ #
738
+ # This can be called multiple times to add several hooks.
739
+ #
740
+ # @example
741
+ # on_thread_start do
742
+ # puts 'On thread start...'
743
+ # end
744
+ def on_thread_start(&block)
745
+ @options[:before_thread_start] ||= []
746
+ @options[:before_thread_start] << block
747
+ end
748
+
749
+ # Code to run immediately before a thread exits. The worker does not
750
+ # accept new requests until this code finishes.
751
+ #
752
+ # This hook is useful for cleaning up thread local resources when a thread
753
+ # is trimmed.
754
+ #
755
+ # This can be called multiple times to add several hooks.
756
+ #
757
+ # @example
758
+ # on_thread_exit do
759
+ # puts 'On thread exit...'
760
+ # end
761
+ def on_thread_exit(&block)
762
+ @options[:before_thread_exit] ||= []
763
+ @options[:before_thread_exit] << block
764
+ end
765
+
720
766
  # Code to run out-of-band when the worker is idle.
721
767
  # These hooks run immediately after a request has finished
722
768
  # processing and there are no busy threads on the worker.
@@ -848,7 +894,8 @@ module Puma
848
894
  # not a request timeout, it is to protect against a hung or dead process.
849
895
  # Setting this value will not protect against slow requests.
850
896
  #
851
- # The minimum value is 6 seconds, the default value is 60 seconds.
897
+ # This value must be greater than worker_check_interval.
898
+ # The default value is 60 seconds.
852
899
  #
853
900
  # @note Cluster mode only.
854
901
  # @example
@@ -34,7 +34,7 @@ module Puma::Rack
34
34
  end
35
35
 
36
36
  location = location.chomp('/')
37
- match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
37
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
38
38
 
39
39
  [host, location, match, app]
40
40
  }.sort_by do |(host, location, _, _)|
data/lib/puma/runner.rb CHANGED
@@ -75,7 +75,11 @@ module Puma
75
75
  control = Puma::Server.new app, nil,
76
76
  { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
77
77
 
78
- control.binder.parse [str], nil, 'Starting control server'
78
+ begin
79
+ control.binder.parse [str], nil, 'Starting control server'
80
+ rescue Errno::EADDRINUSE, Errno::EACCES => e
81
+ raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
82
+ end
79
83
 
80
84
  control.run thread_name: 'ctl'
81
85
  @control = control
data/lib/puma/server.rb CHANGED
@@ -15,7 +15,6 @@ require_relative 'request'
15
15
 
16
16
  require 'socket'
17
17
  require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
- require 'forwardable'
19
18
 
20
19
  module Puma
21
20
 
@@ -32,7 +31,6 @@ module Puma
32
31
  class Server
33
32
  include Puma::Const
34
33
  include Request
35
- extend Forwardable
36
34
 
37
35
  attr_reader :thread
38
36
  attr_reader :log_writer
@@ -48,9 +46,6 @@ module Puma
48
46
  attr_accessor :app
49
47
  attr_accessor :binder
50
48
 
51
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
52
- :add_unix_listener, :connected_ports
53
-
54
49
  THREAD_LOCAL_KEY = :puma_server
55
50
 
56
51
  # Create a server for the rack app +app+.
@@ -86,15 +81,16 @@ module Puma
86
81
  UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
87
82
  end
88
83
 
89
- @log_writer = @options.fetch :log_writer, LogWriter.stdio
90
- @early_hints = @options[:early_hints]
91
- @first_data_timeout = @options[:first_data_timeout]
92
- @min_threads = @options[:min_threads]
93
- @max_threads = @options[:max_threads]
94
- @persistent_timeout = @options[:persistent_timeout]
95
- @queue_requests = @options[:queue_requests]
96
- @max_fast_inline = @options[:max_fast_inline]
97
- @io_selector_backend = @options[:io_selector_backend]
84
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
85
+ @early_hints = @options[:early_hints]
86
+ @first_data_timeout = @options[:first_data_timeout]
87
+ @persistent_timeout = @options[:persistent_timeout]
88
+ @idle_timeout = @options[:idle_timeout]
89
+ @min_threads = @options[:min_threads]
90
+ @max_threads = @options[:max_threads]
91
+ @queue_requests = @options[:queue_requests]
92
+ @max_fast_inline = @options[:max_fast_inline]
93
+ @io_selector_backend = @options[:io_selector_backend]
98
94
  @http_content_length_limit = @options[:http_content_length_limit]
99
95
 
100
96
  # make this a hash, since we prefer `key?` over `include?`
@@ -330,8 +326,12 @@ module Puma
330
326
 
331
327
  while @status == :run || (drain && shutting_down?)
332
328
  begin
333
- ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
334
- break unless ios
329
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
330
+ unless ios
331
+ @status = :stop unless shutting_down?
332
+ break
333
+ end
334
+
335
335
  ios.first.each do |sock|
336
336
  if sock == check
337
337
  break if handle_check
@@ -476,7 +476,7 @@ module Puma
476
476
  end
477
477
  true
478
478
  rescue StandardError => e
479
- client_error(e, client)
479
+ client_error(e, client, requests)
480
480
  # The ensure tries to close +client+ down
481
481
  requests > 0
482
482
  ensure
@@ -504,22 +504,22 @@ module Puma
504
504
  # :nocov:
505
505
 
506
506
  # Handle various error types thrown by Client I/O operations.
507
- def client_error(e, client)
507
+ def client_error(e, client, requests = 1)
508
508
  # Swallow, do not log
509
509
  return if [ConnectionError, EOFError].include?(e.class)
510
510
 
511
- lowlevel_error(e, client.env)
512
511
  case e
513
512
  when MiniSSL::SSLError
513
+ lowlevel_error(e, client.env)
514
514
  @log_writer.ssl_error e, client.io
515
515
  when HttpParserError
516
- client.write_error(400)
516
+ response_to_error(client, requests, e, 400)
517
517
  @log_writer.parse_error e, client
518
518
  when HttpParserError501
519
- client.write_error(501)
519
+ response_to_error(client, requests, e, 501)
520
520
  @log_writer.parse_error e, client
521
521
  else
522
- client.write_error(500)
522
+ response_to_error(client, requests, e, 500)
523
523
  @log_writer.unknown_error e, nil, "Read"
524
524
  end
525
525
  end
@@ -541,10 +541,17 @@ module Puma
541
541
  backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
542
542
  [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
543
543
  else
544
- [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
544
+ [status, {}, [""]]
545
545
  end
546
546
  end
547
547
 
548
+ def response_to_error(client, requests, err, status_code)
549
+ status, headers, res_body = lowlevel_error(err, client.env, status_code)
550
+ prepare_response(status, headers, res_body, requests, client)
551
+ client.write_error(status_code)
552
+ end
553
+ private :response_to_error
554
+
548
555
  # Wait for all outstanding requests to finish.
549
556
  #
550
557
  def graceful_shutdown
@@ -623,5 +630,27 @@ module Puma
623
630
  def stats
624
631
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
625
632
  end
633
+
634
+ # below are 'delegations' to binder
635
+ # remove in Puma 7?
636
+
637
+
638
+ def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
639
+ @binder.add_tcp_listener host, port, optimize_for_latency, backlog
640
+ end
641
+
642
+ def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
643
+ backlog = 1024)
644
+ @binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
645
+ end
646
+
647
+ def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
648
+ @binder.add_unix_listener path, umask, mode, backlog
649
+ end
650
+
651
+ # @!attribute [r] connected_ports
652
+ def connected_ports
653
+ @binder.connected_ports
654
+ end
626
655
  end
627
656
  end
@@ -51,6 +51,8 @@ module Puma
51
51
  @block = block
52
52
  @out_of_band = options[:out_of_band]
53
53
  @clean_thread_locals = options[:clean_thread_locals]
54
+ @before_thread_start = options[:before_thread_start]
55
+ @before_thread_exit = options[:before_thread_exit]
54
56
  @reaping_time = options[:reaping_time]
55
57
  @auto_trim_time = options[:auto_trim_time]
56
58
 
@@ -107,6 +109,7 @@ module Puma
107
109
  def spawn_thread
108
110
  @spawned += 1
109
111
 
112
+ trigger_before_thread_start_hooks
110
113
  th = Thread.new(@spawned) do |spawned|
111
114
  Puma.set_thread_name '%s tp %03i' % [@name, spawned]
112
115
  todo = @todo
@@ -125,6 +128,7 @@ module Puma
125
128
  @spawned -= 1
126
129
  @workers.delete th
127
130
  not_full.signal
131
+ trigger_before_thread_exit_hooks
128
132
  Thread.exit
129
133
  end
130
134
 
@@ -162,6 +166,36 @@ module Puma
162
166
 
163
167
  private :spawn_thread
164
168
 
169
+ def trigger_before_thread_start_hooks
170
+ return unless @before_thread_start&.any?
171
+
172
+ @before_thread_start.each do |b|
173
+ begin
174
+ b.call
175
+ rescue Exception => e
176
+ STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
177
+ end
178
+ end
179
+ nil
180
+ end
181
+
182
+ private :trigger_before_thread_start_hooks
183
+
184
+ def trigger_before_thread_exit_hooks
185
+ return unless @before_thread_exit&.any?
186
+
187
+ @before_thread_exit.each do |b|
188
+ begin
189
+ b.call
190
+ rescue Exception => e
191
+ STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
192
+ end
193
+ end
194
+ nil
195
+ end
196
+
197
+ private :trigger_before_thread_exit_hooks
198
+
165
199
  # @version 5.0.0
166
200
  def trigger_out_of_band_hook
167
201
  return false unless @out_of_band&.any?
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: 6.3.1
4
+ version: 6.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix