puma 6.6.0 → 7.0.0.pre1

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