puma 6.0.0 → 6.0.1

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: c8a1a6014d9e1130e8ba06d5f74d472216448df3dbce4c15c4c24c91fbfe5e30
4
- data.tar.gz: f1be3485e37cd230f6c35854aeb959ffeab1dac0162bf482799a4755222bc498
3
+ metadata.gz: 6eba1431a70cddf7528d8659b6f5a8adc5e80e39aa7b826cbcdf23ef1a86d3d0
4
+ data.tar.gz: 7312b2a285b904c623aff6b22f52bb7062749a80f648695c94a17a9fce9f10eb
5
5
  SHA512:
6
- metadata.gz: bbdd201a97e5dffccbbb8d8f336693746b87763313422a94ac21f73641ca712b9d2461051cc0bd02771b51af606ce595c2dd09bff0c716146cf9ac56e7ae51f4
7
- data.tar.gz: e378848b22d1139559edd6736a9164a5cd98064c6232f0b2cc108122f79c93429ec33b156baa17e6ff82e9b3c77dbddbd22b2a0525a16f749129cf6f70c006da
6
+ metadata.gz: 5b439f323ffb6f3161a3851d07ef82937e8d308ba5ea0786d418032889e66ebab586c8fb29b3ebd79b1f0d26d4a5ceffb3253e7a93da9fd630e73f81970e61a7
7
+ data.tar.gz: 12360aa97831e9b0996b443dc62b7efc2b5ca215a9a7314ad9722c88f88e0e890eb00bbbbcb47a4ea1e91a0c1f77718bae95f4c6c36d670a18886a716391ea12
data/History.md CHANGED
@@ -1,4 +1,13 @@
1
- ## 6.0.0 / 2022-10-XX
1
+ ## 6.0.1 / 2022-12-12
2
+
3
+ * Bugfixes
4
+ * Handle waking up a closed selector in Reactor#add ([#3005])
5
+ * Fixup response processing, enumerable bodies ([#3004], [#3000])
6
+ * Correctly close app body for all code paths ([#3002], [#2999])
7
+ * Refactor
8
+ * Add IOBuffer to Client, remove from ThreadPool thread instances ([#3013])
9
+
10
+ ## 6.0.0 / 2022-10-14
2
11
 
3
12
  * Breaking Changes
4
13
  * Dropping Ruby 2.2 and 2.3 support (now 2.4+) ([#2919])
@@ -9,6 +18,9 @@
9
18
  * Prefix all environment variables with `PUMA_` ([#2924], [#2853])
10
19
  * Removed some constants ([#2957], [#2958], [#2959], [#2960])
11
20
  * The following classes are now part of Puma's private API: `Client`, `Cluster::Worker`, `Cluster::Worker`, `HandleRequest`. ([#2988])
21
+ * Configuration constants like `DefaultRackup` removed ([#2928])
22
+ * Extracted `LogWriter` from `Events` ([#2798])
23
+ * Only accept the standard 8 HTTP methods, others rejected with 501. ([#2932])
12
24
 
13
25
  * Features
14
26
  * Increase throughput on large (100kb+) response bodies by 3-10x ([#2896], [#2892])
@@ -34,7 +46,6 @@
34
46
 
35
47
  * Refactor
36
48
  * log_writer.rb - add internal_write method ([#2888])
37
- * [WIP] Refactor: Split out LogWriter from Events (no logic change) ([#2798])
38
49
  * Extract prune_bundler code into it's own class. ([#2797])
39
50
  * Refactor Launcher#run to increase readability (no logic change) ([#2795])
40
51
  * Ruby 3.2 will have native IO#wait_* methods, don't require io/wait ([#2903])
@@ -1915,6 +1926,12 @@ be added back in a future date when a java Puma::MiniSSL is added.
1915
1926
  * Bugfixes
1916
1927
  * Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
1917
1928
 
1929
+ [#3005]:https://github.com/puma/puma/pull/3005 "PR by @JuanitoFatas, merged 2022-11-04"
1930
+ [#3013]:https://github.com/puma/puma/pull/3013 "PR by @MSP-Greg, merged 2022-11-13"
1931
+ [#3004]:https://github.com/puma/puma/pull/3004 "PR by @MSP-Greg, merged 2022-11-24"
1932
+ [#3000]:https://github.com/puma/puma/issues/3000 "Issue by @dentarg, closed 2022-11-24"
1933
+ [#3002]:https://github.com/puma/puma/pull/3002 "PR by @MSP-Greg, merged 2022-11-03"
1934
+ [#2999]:https://github.com/puma/puma/issues/2999 "Issue by @aymeric-ledorze, closed 2022-11-03"
1918
1935
  [#2919]:https://github.com/puma/puma/pull/2919 "PR by @MSP-Greg, merged 2022-08-30"
1919
1936
  [#2652]:https://github.com/puma/puma/issues/2652 "Issue by @Roguelazer, closed 2022-09-04"
1920
1937
  [#2653]:https://github.com/puma/puma/pull/2653 "PR by @Roguelazer, closed 2022-03-07"
@@ -1928,7 +1945,10 @@ be added back in a future date when a java Puma::MiniSSL is added.
1928
1945
  [#2958]:https://github.com/puma/puma/pull/2958 "PR by @JuanitoFatas, merged 2022-09-16"
1929
1946
  [#2959]:https://github.com/puma/puma/pull/2959 "PR by @JuanitoFatas, merged 2022-09-16"
1930
1947
  [#2960]:https://github.com/puma/puma/pull/2960 "PR by @JuanitoFatas, merged 2022-09-16"
1931
- [#2988]:https://github.com/puma/puma/issues/2988 "Issue by @MSP-Greg, merged 2022-10-12"
1948
+ [#2988]:https://github.com/puma/puma/pull/2988 "PR by @MSP-Greg, merged 2022-10-12"
1949
+ [#2928]:https://github.com/puma/puma/pull/2928 "PR by @nateberkopec, merged 2022-09-10"
1950
+ [#2798]:https://github.com/puma/puma/pull/2798 "PR by @johnnyshields, merged 2022-02-05"
1951
+ [#2932]:https://github.com/puma/puma/pull/2932 "PR by @mrzasa, merged 2022-09-12"
1932
1952
  [#2896]:https://github.com/puma/puma/pull/2896 "PR by @MSP-Greg, merged 2022-09-13"
1933
1953
  [#2892]:https://github.com/puma/puma/pull/2892 "PR by @guilleiguaran, closed 2022-09-13"
1934
1954
  [#2923]:https://github.com/puma/puma/pull/2923 "PR by @nateberkopec, merged 2022-09-09"
@@ -1949,11 +1969,9 @@ be added back in a future date when a java Puma::MiniSSL is added.
1949
1969
  [#2904]:https://github.com/puma/puma/pull/2904 "PR by @kares, merged 2022-08-27"
1950
1970
  [#2884]:https://github.com/puma/puma/pull/2884 "PR by @kares, merged 2022-05-30"
1951
1971
  [#2897]:https://github.com/puma/puma/pull/2897 "PR by @Edouard-chin, merged 2022-08-27"
1952
- [#2932]:https://github.com/puma/puma/pull/2932 "PR by @mrzasa, merged 2022-09-12"
1953
1972
  [#1441]:https://github.com/puma/puma/issues/1441 "Issue by @nirvdrum, closed 2022-09-12"
1954
1973
  [#2956]:https://github.com/puma/puma/pull/2956 "PR by @MSP-Greg, merged 2022-09-15"
1955
1974
  [#2888]:https://github.com/puma/puma/pull/2888 "PR by @MSP-Greg, merged 2022-06-01"
1956
- [#2798]:https://github.com/puma/puma/pull/2798 "PR by @johnnyshields, merged 2022-02-05"
1957
1975
  [#2797]:https://github.com/puma/puma/pull/2797 "PR by @johnnyshields, merged 2022-02-01"
1958
1976
  [#2795]:https://github.com/puma/puma/pull/2795 "PR by @johnnyshields, merged 2022-01-31"
1959
1977
  [#2903]:https://github.com/puma/puma/pull/2903 "PR by @MSP-Greg, merged 2022-08-27"
data/docs/nginx.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is a very common setup using an upstream. It was adapted from some Capistrano recipe I found on the Internet a while ago.
4
4
 
5
- ```
5
+ ```nginx
6
6
  upstream myapp {
7
7
  server unix:///myapp/tmp/puma.sock;
8
8
  }
data/lib/puma/client.rb CHANGED
@@ -9,6 +9,7 @@ class IO
9
9
  end
10
10
 
11
11
  require_relative 'detect'
12
+ require_relative 'io_buffer'
12
13
  require 'tempfile'
13
14
  require 'forwardable'
14
15
 
@@ -65,6 +66,7 @@ module Puma
65
66
  def initialize(io, env=nil)
66
67
  @io = io
67
68
  @to_io = io.to_io
69
+ @io_buffer = IOBuffer.new
68
70
  @proto_env = env
69
71
  @env = env ? env.dup : nil
70
72
 
@@ -96,7 +98,7 @@ module Puma
96
98
  end
97
99
 
98
100
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
99
- :tempfile
101
+ :tempfile, :io_buffer
100
102
 
101
103
  attr_writer :peerip
102
104
 
@@ -138,6 +140,7 @@ module Puma
138
140
 
139
141
  def reset(fast_check=true)
140
142
  @parser.reset
143
+ @io_buffer.reset
141
144
  @read_header = true
142
145
  @read_proxy = !!@expect_proxy_proto
143
146
  @env = @proto_env.dup
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.0.0".freeze
103
+ PUMA_VERSION = VERSION = "6.0.1".freeze
104
104
  CODE_NAME = "Sunflower".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
data/lib/puma/dsl.rb CHANGED
@@ -250,6 +250,7 @@ module Puma
250
250
  #
251
251
  # * Set the socket backlog depth with +backlog+, default is 1024.
252
252
  # * Set up an SSL certificate with +key+ & +cert+.
253
+ # * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
253
254
  # * Set whether to optimize for low latency instead of throughput with
254
255
  # +low_latency+, default is to not optimize for low latency. This is done
255
256
  # via +Socket::TCP_NODELAY+.
@@ -259,6 +260,8 @@ module Puma
259
260
  # bind 'unix:///var/run/puma.sock?backlog=512'
260
261
  # @example SSL cert
261
262
  # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
263
+ # @example SSL cert for mutual TLS (mTLS)
264
+ # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
262
265
  # @example Disable optimization for low latency
263
266
  # bind 'tcp://0.0.0.0:9292?low_latency=false'
264
267
  # @example Socket permissions
@@ -22,6 +22,16 @@ module Puma
22
22
  read
23
23
  end
24
24
 
25
+ # Read & Reset - returns contents and resets
26
+ # @return [String] StringIO contents
27
+ def read_and_reset
28
+ rewind
29
+ str = read
30
+ truncate 0
31
+ rewind
32
+ str
33
+ end
34
+
25
35
  alias_method :clear, :reset
26
36
 
27
37
  # before Ruby 2.5, `write` would only take one argument
data/lib/puma/reactor.rb CHANGED
@@ -50,7 +50,7 @@ module Puma
50
50
  @input << client
51
51
  @selector.wakeup
52
52
  true
53
- rescue ClosedQueueError
53
+ rescue ClosedQueueError, IOError # Ignore if selector is already closed
54
54
  false
55
55
  end
56
56
 
data/lib/puma/request.rb CHANGED
@@ -14,15 +14,18 @@ module Puma
14
14
  #
15
15
  module Request # :nodoc:
16
16
 
17
- # determines whether to write body to io_buffer first, or straight to socket
18
- # also fixes max size of chunked body read when bosy is an IO.
19
- BODY_LEN_MAX = 1_024 * 256
17
+ # Single element array body: smaller bodies are written to io_buffer first,
18
+ # then a single write from io_buffer. Larger sizes are written separately.
19
+ # Also fixes max size of chunked file body read.
20
+ BODY_LEN_MAX = 1_024 * 256
20
21
 
21
- # size divide for using copy_stream on body
22
+ # File body: smaller bodies are combined with io_buffer, then written to
23
+ # socket. Larger bodies are written separately using `copy_stream`
22
24
  IO_BODY_MAX = 1_024 * 64
23
25
 
24
- # max size for io_buffer, force write when exceeded
25
- IO_BUFFER_LEN_MAX = 1_024 * 1_024 * 4
26
+ # Array body: elements are collected in io_buffer. When io_buffer's size
27
+ # exceeds value, they are written to the socket.
28
+ IO_BUFFER_LEN_MAX = 1_024 * 512
26
29
 
27
30
  SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
28
31
 
@@ -41,13 +44,14 @@ module Puma
41
44
  #
42
45
  # Finally, it'll return +true+ on keep-alive connections.
43
46
  # @param client [Puma::Client]
44
- # @param io_buffer [Puma::IOBuffer]
45
47
  # @param requests [Integer]
46
48
  # @return [Boolean,:async]
47
49
  #
48
- def handle_request(client, io_buffer, requests)
50
+ def handle_request(client, requests)
49
51
  env = client.env
52
+ io_buffer = client.io_buffer
50
53
  socket = client.io # io may be a MiniSSL::Socket
54
+ app_body = nil
51
55
 
52
56
  return false if closed_socket?(socket)
53
57
 
@@ -85,14 +89,18 @@ module Puma
85
89
 
86
90
  begin
87
91
  if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
88
- status, headers, res_body = @thread_pool.with_force_shutdown do
92
+ status, headers, app_body = @thread_pool.with_force_shutdown do
89
93
  @app.call(env)
90
94
  end
91
95
  else
92
96
  @log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
93
- status, headers, res_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
97
+ status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
94
98
  end
95
99
 
100
+ # app_body needs to always be closed, hold value in case lowlevel_error
101
+ # is called
102
+ res_body = app_body
103
+
96
104
  return :async if client.hijacked
97
105
 
98
106
  status = status.to_i
@@ -114,60 +122,83 @@ module Puma
114
122
 
115
123
  status, headers, res_body = lowlevel_error(e, env, 500)
116
124
  end
117
- prepare_response(status, headers, res_body, io_buffer, requests, client)
125
+ prepare_response(status, headers, res_body, requests, client)
126
+ ensure
127
+ io_buffer.reset
128
+ uncork_socket client.io
129
+ app_body.close if app_body.respond_to? :close
130
+ client.tempfile&.unlink
131
+ after_reply = env[RACK_AFTER_REPLY] || []
132
+ begin
133
+ after_reply.each { |o| o.call }
134
+ rescue StandardError => e
135
+ @log_writer.debug_error e
136
+ end unless after_reply.empty?
118
137
  end
119
138
 
120
139
  # Assembles the headers and prepares the body for actually sending the
121
- # response via #fast_write_response.
140
+ # response via `#fast_write_response`.
122
141
  #
123
142
  # @param status [Integer] the status returned by the Rack application
124
143
  # @param headers [Hash] the headers returned by the Rack application
125
- # @param app_body [Array] the body returned by the Rack application or
126
- # a call to `lowlevel_error`
127
- # @param io_buffer [Puma::IOBuffer] modified in place
144
+ # @param res_body [Array] the body returned by the Rack application or
145
+ # a call to `Server#lowlevel_error`
128
146
  # @param requests [Integer] number of inline requests handled
129
147
  # @param client [Puma::Client]
130
- # @return [Boolean,:async]
131
- def prepare_response(status, headers, app_body, io_buffer, requests, client)
148
+ # @return [Boolean,:async] keep-alive status or `:async`
149
+ def prepare_response(status, headers, res_body, requests, client)
132
150
  env = client.env
133
- socket = client.io
134
-
135
- after_reply = env[RACK_AFTER_REPLY] || []
151
+ socket = client.io
152
+ io_buffer = client.io_buffer
136
153
 
137
154
  return false if closed_socket?(socket)
138
155
 
139
- resp_info = str_headers(env, status, headers, app_body, io_buffer, requests, client)
156
+ # Close the connection after a reasonable number of inline requests
157
+ # if the server is at capacity and the listener has a new connection ready.
158
+ # This allows Puma to service connections fairly when the number
159
+ # of concurrent connections exceeds the size of the threadpool.
160
+ force_keep_alive = requests < @max_fast_inline ||
161
+ @thread_pool.busy_threads < @max_threads ||
162
+ !client.listener.to_io.wait_readable(0)
163
+
164
+ resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
165
+
166
+ close_body = false
140
167
 
141
168
  # below converts app_body into body, dependent on app_body's characteristics, and
142
169
  # resp_info[:content_length] will be set if it can be determined
143
170
  if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
144
- if app_body.respond_to?(:to_ary)
145
- length = 0
146
- if array_body = app_body.to_ary
147
- body = array_body.map { |part| length += part.bytesize; part }
148
- elsif app_body.is_a?(::File) && app_body.respond_to?(:size)
149
- length = app_body.size
150
- elsif app_body.respond_to?(:each)
151
- body = []
152
- app_body.each { |part| length += part.bytesize; body << part }
153
- end
154
- resp_info[:content_length] = length
155
- elsif app_body.is_a?(File) && app_body.respond_to?(:size)
156
- resp_info[:content_length] = app_body.size
157
- body = app_body
158
- elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
159
- File.readable?(fn = app_body.to_path)
171
+ if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary)
172
+ body = array_body
173
+ resp_info[:content_length] = body.sum(&:bytesize)
174
+ elsif res_body.is_a?(File) && res_body.respond_to?(:size)
175
+ body = res_body
176
+ resp_info[:content_length] = body.size
177
+ elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
178
+ File.readable?(fn = res_body.to_path)
160
179
  body = File.open fn, 'rb'
161
180
  resp_info[:content_length] = body.size
181
+ close_body = true
162
182
  else
163
- body = app_body
183
+ body = res_body
164
184
  end
165
- elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
166
- File.readable?(fn = app_body.to_path)
185
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
186
+ File.readable?(fn = res_body.to_path)
167
187
  body = File.open fn, 'rb'
168
188
  resp_info[:content_length] = body.size
189
+ close_body = true
190
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
191
+ res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
192
+ # Sprockets::Asset
193
+ resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
194
+ if res_body.to_hash[:source] # use each to return @source
195
+ body = res_body
196
+ else # avoid each and use a File object
197
+ body = File.open fn, 'rb'
198
+ close_body = true
199
+ end
169
200
  else
170
- body = app_body
201
+ body = res_body
171
202
  end
172
203
 
173
204
  line_ending = LINE_END
@@ -175,8 +206,8 @@ module Puma
175
206
  content_length = resp_info[:content_length]
176
207
  keep_alive = resp_info[:keep_alive]
177
208
 
178
- if app_body && !app_body.respond_to?(:each)
179
- response_hijack = app_body
209
+ if res_body && !res_body.respond_to?(:each)
210
+ response_hijack = res_body
180
211
  else
181
212
  response_hijack = resp_info[:response_hijack]
182
213
  end
@@ -189,7 +220,8 @@ module Puma
189
220
  end
190
221
 
191
222
  io_buffer << LINE_END
192
- fast_write_str socket, io_buffer.to_s
223
+ fast_write_str socket, io_buffer.read_and_reset
224
+ socket.flush
193
225
  return keep_alive
194
226
  end
195
227
  if content_length
@@ -203,25 +235,14 @@ module Puma
203
235
  io_buffer << line_ending
204
236
 
205
237
  if response_hijack
206
- fast_write_str socket, io_buffer.to_s
238
+ fast_write_str socket, io_buffer.read_and_reset
207
239
  response_hijack.call socket
208
240
  return :async
209
241
  end
210
242
 
211
243
  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
244
+ body.close if close_body
212
245
  keep_alive
213
- ensure
214
- io_buffer.reset
215
- resp_info = nil
216
- uncork_socket socket
217
- app_body.close if app_body.respond_to? :close
218
- client.tempfile&.unlink
219
-
220
- begin
221
- after_reply.each { |o| o.call }
222
- rescue StandardError => e
223
- @log_writer.debug_error e
224
- end unless after_reply.empty?
225
246
  end
226
247
 
227
248
  # @param env [Hash] see Puma::Client#env, from request
@@ -237,10 +258,10 @@ module Puma
237
258
 
238
259
  # Used to write 'early hints', 'no body' responses, 'hijacked' responses,
239
260
  # and body segments (called by `fast_write_response`).
240
- # Writes a string to an io (normally `Client#io`) using `write_nonblock`.
261
+ # Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
241
262
  # Large strings may not be written in one pass, especially if `io` is a
242
263
  # `MiniSSL::Socket`.
243
- # @param io [#write_nonblock] the io to write to
264
+ # @param socket [#write_nonblock] the request/response socket
244
265
  # @param str [String] the string written to the io
245
266
  # @raise [ConnectionError]
246
267
  #
@@ -249,7 +270,7 @@ module Puma
249
270
  byte_size = str.bytesize
250
271
  while n < byte_size
251
272
  begin
252
- n += socket.syswrite(n.zero? ? str : str.byteslice(n..-1))
273
+ n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
253
274
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
254
275
  unless socket.wait_writable WRITE_TIMEOUT
255
276
  raise ConnectionError, SOCKET_WRITE_ERR_MSG
@@ -267,39 +288,41 @@ module Puma
267
288
  # @param socket [#write] the response socket
268
289
  # @param body [Enumerable, File] the body object
269
290
  # @param io_buffer [Puma::IOBuffer] contains headers
270
- # @param chunk [Boolean]
291
+ # @param chunked [Boolean]
292
+ # @paramn content_length [Integer
271
293
  # @raise [ConnectionError]
272
294
  #
273
295
  def fast_write_response(socket, body, io_buffer, chunked, content_length)
274
- if body.is_a?(::File) || body.respond_to?(:read) || body.respond_to?(:readpartial)
296
+ if body.is_a?(::File) && body.respond_to?(:read)
275
297
  if chunked # would this ever happen?
276
298
  while part = body.read(BODY_LEN_MAX)
277
299
  io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
278
300
  end
279
- io_buffer << CLOSE_CHUNKED
280
- fast_write_str socket, io_buffer.to_s
301
+ fast_write_str socket, CLOSE_CHUNKED
281
302
  else
282
303
  if content_length <= IO_BODY_MAX
283
- io_buffer.write body.sysread(content_length)
284
- fast_write_str socket, io_buffer.to_s
304
+ io_buffer.write body.read(content_length)
305
+ fast_write_str socket, io_buffer.read_and_reset
285
306
  else
286
- fast_write_str socket, io_buffer.to_s
307
+ fast_write_str socket, io_buffer.read_and_reset
287
308
  IO.copy_stream body, socket
288
309
  end
289
310
  end
290
- body.close
291
311
  elsif body.is_a?(::Array) && body.length == 1
292
- body_first = body.first
293
- if body_first.is_a?(::String) && body_first.bytesize >= BODY_LEN_MAX
312
+ body_first = nil
313
+ # using body_first = body.first causes issues?
314
+ body.each { |str| body_first ||= str }
315
+
316
+ if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
317
+ # smaller body, write to io_buffer first
318
+ io_buffer.write body_first
319
+ fast_write_str socket, io_buffer.read_and_reset
320
+ else
294
321
  # large body, write both header & body to socket
295
- fast_write_str socket, io_buffer.to_s
322
+ fast_write_str socket, io_buffer.read_and_reset
296
323
  fast_write_str socket, body_first
297
- else
298
- # smaller body, write to stream first
299
- io_buffer.write body_first
300
- fast_write_str socket, io_buffer.to_s
301
324
  end
302
- else
325
+ elsif body.is_a?(::Array)
303
326
  # for array bodies, flush io_buffer to socket when size is greater than
304
327
  # IO_BUFFER_LEN_MAX
305
328
  if chunked
@@ -307,8 +330,7 @@ module Puma
307
330
  next if (byte_size = part.bytesize).zero?
308
331
  io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
309
332
  if io_buffer.length > IO_BUFFER_LEN_MAX
310
- fast_write_str socket, io_buffer.to_s
311
- io_buffer.reset
333
+ fast_write_str socket, io_buffer.read_and_reset
312
334
  end
313
335
  end
314
336
  io_buffer.write CLOSE_CHUNKED
@@ -317,13 +339,31 @@ module Puma
317
339
  next if part.bytesize.zero?
318
340
  io_buffer.write part
319
341
  if io_buffer.length > IO_BUFFER_LEN_MAX
320
- fast_write_str socket, io_buffer.to_s
321
- io_buffer.reset
342
+ fast_write_str socket, io_buffer.read_and_reset
322
343
  end
323
344
  end
324
345
  end
325
- fast_write_str(socket, io_buffer.to_s) unless io_buffer.length.zero?
346
+ # may write last body part for non-chunked, also headers if array is empty
347
+ fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
348
+ else
349
+ # for enum bodies
350
+ fast_write_str socket, io_buffer.read_and_reset
351
+ if chunked
352
+ body.each do |part|
353
+ next if (byte_size = part.bytesize).zero?
354
+ fast_write_str socket, (byte_size.to_s(16) << LINE_END)
355
+ fast_write_str socket, part
356
+ fast_write_str socket, LINE_END
357
+ end
358
+ fast_write_str socket, CLOSE_CHUNKED
359
+ else
360
+ body.each do |part|
361
+ next if part.bytesize.zero?
362
+ fast_write_str socket, part
363
+ end
364
+ end
326
365
  end
366
+ socket.flush
327
367
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
328
368
  raise ConnectionError, SOCKET_WRITE_ERR_MSG
329
369
  rescue Errno::EPIPE, SystemCallError, IOError
@@ -496,12 +536,13 @@ module Puma
496
536
  # @param content_length [Integer,nil] content length if it can be determined from the
497
537
  # response body
498
538
  # @param io_buffer [Puma::IOBuffer] modified inn place
499
- # @param requests [Integer] number of inline requests handled
500
- # @param client [Puma::Client]
539
+ # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
540
+ # status and `@max_fast_inline`
501
541
  # @return [Hash] resp_info
502
542
  # @version 5.0.3
503
543
  #
504
- def str_headers(env, status, headers, res_body, io_buffer, requests, client)
544
+ def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
545
+
505
546
  line_ending = LINE_END
506
547
  colon = COLON
507
548
 
@@ -544,13 +585,8 @@ module Puma
544
585
  # if running without request queueing
545
586
  resp_info[:keep_alive] &&= @queue_requests
546
587
 
547
- # Close the connection after a reasonable number of inline requests
548
- # if the server is at capacity and the listener has a new connection ready.
549
- # This allows Puma to service connections fairly when the number
550
- # of concurrent connections exceeds the size of the threadpool.
551
- resp_info[:keep_alive] &&= requests < @max_fast_inline ||
552
- @thread_pool.busy_threads < @max_threads ||
553
- !client.listener.to_io.wait_readable(0)
588
+ # see prepare_response
589
+ resp_info[:keep_alive] &&= force_keep_alive
554
590
 
555
591
  resp_info[:response_hijack] = nil
556
592
 
@@ -560,7 +596,8 @@ module Puma
560
596
  case k.downcase
561
597
  when CONTENT_LENGTH2
562
598
  next if illegal_header_value?(vs)
563
- resp_info[:content_length] = vs
599
+ # nil.to_i is 0, nil&.to_i is nil
600
+ resp_info[:content_length] = vs&.to_i
564
601
  next
565
602
  when TRANSFER_ENCODING
566
603
  resp_info[:allow_chunked] = false
data/lib/puma/server.rb CHANGED
@@ -11,7 +11,6 @@ require_relative 'reactor'
11
11
  require_relative 'client'
12
12
  require_relative 'binder'
13
13
  require_relative 'util'
14
- require_relative 'io_buffer'
15
14
  require_relative 'request'
16
15
 
17
16
  require 'socket'
@@ -230,7 +229,7 @@ module Puma
230
229
 
231
230
  @status = :run
232
231
 
233
- @thread_pool = ThreadPool.new(thread_name, @options) { |a, b| process_client a, b }
232
+ @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
234
233
 
235
234
  if @queue_requests
236
235
  @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
@@ -401,7 +400,7 @@ module Puma
401
400
  # returning.
402
401
  #
403
402
  # Return true if one or more requests were processed.
404
- def process_client(client, buffer)
403
+ def process_client(client)
405
404
  # Advertise this server into the thread
406
405
  Thread.current[ThreadLocalKey] = self
407
406
 
@@ -427,15 +426,13 @@ module Puma
427
426
 
428
427
  while true
429
428
  @requests_count += 1
430
- case handle_request(client, buffer, requests + 1)
429
+ case handle_request(client, requests + 1)
431
430
  when false
432
431
  break
433
432
  when :async
434
433
  close_socket = false
435
434
  break
436
435
  when true
437
- buffer.reset
438
-
439
436
  ThreadPool.clean_thread_locals if clean_thread_locals
440
437
 
441
438
  requests += 1
@@ -469,7 +466,7 @@ module Puma
469
466
  # The ensure tries to close +client+ down
470
467
  requests > 0
471
468
  ensure
472
- buffer.reset
469
+ client.io_buffer.reset
473
470
 
474
471
  begin
475
472
  client.close if close_socket
@@ -45,7 +45,6 @@ module Puma
45
45
  @min = Integer(options[:min_threads])
46
46
  @max = Integer(options[:max_threads])
47
47
  @block = block
48
- @extra = [::Puma::IOBuffer]
49
48
  @out_of_band = options[:out_of_band]
50
49
  @clean_thread_locals = options[:clean_thread_locals]
51
50
  @reaping_time = options[:reaping_time]
@@ -112,8 +111,6 @@ module Puma
112
111
  not_empty = @not_empty
113
112
  not_full = @not_full
114
113
 
115
- extra = @extra.map { |i| i.new }
116
-
117
114
  while true
118
115
  work = nil
119
116
 
@@ -147,7 +144,7 @@ module Puma
147
144
  end
148
145
 
149
146
  begin
150
- @out_of_band_pending = true if block.call(work, *extra)
147
+ @out_of_band_pending = true if block.call(work)
151
148
  rescue Exception => e
152
149
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
153
150
  end
data/lib/puma.rb CHANGED
@@ -28,7 +28,7 @@ module Puma
28
28
  # not in minissl.rb
29
29
  HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
30
30
 
31
- HAS_UNIX_SOCKET = Object.const_defined? :UNIXSocket
31
+ HAS_UNIX_SOCKET = Object.const_defined?(:UNIXSocket) && !IS_WINDOWS
32
32
 
33
33
  if HAS_SSL
34
34
  require_relative 'puma/minissl'
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.0.0
4
+ version: 6.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix