IO/copy stream (#2923)

* Proof of Concept: Use `IO.copy_stream` to serve files

Ref: https://puma/puma/issues/2697

```
$ benchmarks/wrk/big_response.sh
Puma starting in single mode...
* Puma version: 5.5.0 (ruby 3.0.2-p107) ("Zawgyi")
*  Min threads: 4
*  Max threads: 4
*  Environment: development
*          PID: 17879
* Listening on http://0.0.0.0:9292
Use Ctrl-C to stop
Running 1m test @ http://localhost:9292
  2 threads and 4 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.37ms    5.89ms  48.28ms   94.46%
    Req/Sec     0.88k   148.97     1.07k    82.08%
  Latency Distribution
     50%    2.21ms
     75%    2.78ms
     90%    4.09ms
     99%   35.75ms
  105651 requests in 1.00m, 108.24GB read
Requests/sec:   1758.39
Transfer/sec:      1.80GB
- Gracefully stopping, waiting for requests to finish
```

```
$ benchmarks/wrk/big_file.sh
Puma starting in single mode...
* Puma version: 5.5.0 (ruby 3.0.2-p107) ("Zawgyi")
*  Min threads: 4
*  Max threads: 4
*  Environment: development
*          PID: 18034
* Listening on http://0.0.0.0:9292
Use Ctrl-C to stop
Running 1m test @ http://localhost:9292
  2 threads and 4 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.06ms    1.09ms  20.98ms   97.94%
    Req/Sec     1.85k   150.69     2.03k    89.92%
  Latency Distribution
     50%    0.94ms
     75%    1.03ms
     90%    1.21ms
     99%    4.91ms
  221380 requests in 1.00m, 226.81GB read
Requests/sec:   3689.18
Transfer/sec:      3.78GB
- Gracefully stopping, waiting for requests to finish
```

* Ruby 2.2 compat

* test_puma_server.rb - fixup test_file_body

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
Co-authored-by: MSP-Greg <Greg.mpls@gmail.com>
This commit is contained in:
Nate Berkopec 2022-09-09 16:30:46 +09:00 committed by GitHub
parent e2ef83b4ee
commit 5f3f489ee8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 13 deletions

6
benchmarks/wrk/big_file.sh Executable file
View File

@ -0,0 +1,6 @@
bundle exec bin/puma -t 4 test/rackup/big_file.ru &
PID1=$!
sleep 5
wrk -c 4 -d 60 --latency http://localhost:9292
kill $PID1

View File

@ -150,21 +150,26 @@ module Puma
end
begin
res_body.each do |part|
next if part.bytesize.zero?
if chunked
fast_write io, (part.bytesize.to_s(16) << line_ending)
fast_write io, part # part may have different encoding
fast_write io, line_ending
else
fast_write io, part
if !chunked && content_length && res_body.is_a?(::File)
IO.copy_stream(res_body, io)
io.flush
else
res_body.each do |part|
next if part.bytesize.zero?
if chunked
fast_write io, (part.bytesize.to_s(16) << line_ending)
fast_write io, part # part may have different encoding
fast_write io, line_ending
else
fast_write io, part
end
io.flush
end
io.flush
end
if chunked
fast_write io, CLOSE_CHUNKED
io.flush
if chunked
fast_write io, CLOSE_CHUNKED
io.flush
end
end
rescue SystemCallError, IOError
raise ConnectionError, "Connection error detected during write"

7
test/rackup/big_file.ru Normal file
View File

@ -0,0 +1,7 @@
static_file_path = File.join(Dir.tmpdir, "puma-static.txt")
File.write(static_file_path, "Hello World" * 100_000)
run lambda { |env|
f = File.open(static_file_path)
[200, {"Content-Type" => "text/plain", "Content-Length" => f.size.to_s}, f]
}

View File

@ -132,6 +132,19 @@ class TestPumaServer < Minitest::Test
assert_equal "Hello World", data.split("\n").last
end
def test_file_body
random_bytes = SecureRandom.random_bytes(4096 * 32)
path = Tempfile.open { |f| f.path }
File.binwrite path, random_bytes
server_run { |env| [200, {}, File.open(path, 'rb')] }
data = send_http_and_read "GET / HTTP/1.0\r\nHost: [::ffff:127.0.0.1]:9292\r\n\r\n"
assert_equal random_bytes, data.split("\r\n", 3).last
ensure
File.delete(path) if File.exist?(path)
end
def test_proper_stringio_body
data = nil