From d6d127f50f9225bf51ef6ce0abce9bad87efaae3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 5 May 2024 22:15:39 +0000 Subject: [PATCH 5/5] port test/unit/test_ccc.rb to Perl 5 We'll fold this into integration.t to reduce startup time penalties and get the benefit of a stable language to reduce maintenance overhead. --- t/integration.ru | 4 ++ t/integration.t | 33 ++++++++++++++++ test/unit/test_ccc.rb | 92 ------------------------------------------- 3 files changed, 37 insertions(+), 92 deletions(-) delete mode 100644 test/unit/test_ccc.rb diff --git a/t/integration.ru b/t/integration.ru index a6b022a..aaed608 100644 --- a/t/integration.ru +++ b/t/integration.ru @@ -87,6 +87,7 @@ def rack_input_tests(env) [ 200, h, [ dig.hexdigest ] ] end +$nr_aborts = 0 run(lambda do |env| case env['REQUEST_METHOD'] when 'GET' @@ -101,7 +102,10 @@ def rack_input_tests(env) when '/early_hints_rack2'; early_hints(env, "r\n2") when '/early_hints_rack3'; early_hints(env, %w(r 3)) when '/broken_app'; raise RuntimeError, 'hello' + when '/aborted'; $nr_aborts += 1; [ 200, {}, [] ] + when '/nr_aborts'; [ 200, { 'nr-aborts' => "#$nr_aborts" }, [] ] when '/nil'; nil + when '/read_fifo'; [ 200, {}, [ File.read(env['HTTP_READ_FIFO']) ] ] else '/'; [ 200, {}, [ env_dump(env) ] ] end # case PATH_INFO (GET) when 'POST' diff --git a/t/integration.t b/t/integration.t index 3b1d6df..2d448cd 100644 --- a/t/integration.t +++ b/t/integration.t @@ -405,6 +405,39 @@ EOM my $wpid = readline($fifo_fh); like($wpid, qr/\Apid=\d+\z/a , 'new worker ready'); $ck_early_hints->('ccc on'); + + $c = tcp_start $srv, 'GET /env_dump HTTP/1.0'; + vec(my $rvec = '', fileno($c), 1) = 1; + select($rvec, undef, undef, 10) or BAIL_OUT 'timed out env_dump'; + ($status, $hdr) = slurp_hdr($c); + like $status, qr!\AHTTP/1\.[01] 200!, 'got part of first response'; + ok $hdr, 'got all headers'; + + # start a slow TCP request + my $rfifo = "$tmpdir/rfifo"; + mkfifo_die $rfifo; + $c = tcp_start $srv, "GET /read_fifo HTTP/1.0\r\nRead-FIFO: $rfifo"; + tcp_start $srv, 'GET /aborted HTTP/1.0' for (1..100); + write_file '>', $rfifo, 'TFIN'; + ($status, $hdr) = slurp_hdr($c); + like $status, qr!\AHTTP/1\.[01] 200!, 'got part of first response'; + $bdy = <$c>; + is $bdy, 'TFIN', 'got slow response from TCP socket'; + + # slow Unix socket request + $c = unix_start $u1, "GET /read_fifo HTTP/1.0\r\nRead-FIFO: $rfifo"; + vec($rvec = '', fileno($c), 1) = 1; + select($rvec, undef, undef, 10) or BAIL_OUT 'timed out Unix CCC'; + unix_start $u1, 'GET /aborted HTTP/1.0' for (1..100); + write_file '>', $rfifo, 'UFIN'; + ($status, $hdr) = slurp_hdr($c); + like $status, qr!\AHTTP/1\.[01] 200!, 'got part of first response'; + $bdy = <$c>; + is $bdy, 'UFIN', 'got slow response from Unix socket'; + + ($status, $hdr, $bdy) = do_req $srv, 'GET /nr_aborts HTTP/1.0'; + like "@$hdr", qr/nr-aborts: 0\b/, + 'aborted connections unseen by Rack app'; } if ('max_header_len internal API') { diff --git a/test/unit/test_ccc.rb b/test/unit/test_ccc.rb deleted file mode 100644 index a0a2bff..0000000 --- a/test/unit/test_ccc.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: false -require 'socket' -require 'unicorn' -require 'io/wait' -require 'tempfile' -require 'test/unit' -require './test/test_helper' - -class TestCccTCPI < Test::Unit::TestCase - def test_ccc_tcpi - start_pid = $$ - host = '127.0.0.1' - srv = TCPServer.new(host, 0) - port = srv.addr[1] - err = Tempfile.new('unicorn_ccc') - rd, wr = IO.pipe - sleep_pipe = IO.pipe - pid = fork do - sleep_pipe[1].close - reqs = 0 - rd.close - worker_pid = nil - app = lambda do |env| - worker_pid ||= begin - at_exit { wr.write(reqs.to_s) if worker_pid == $$ } - $$ - end - reqs += 1 - - # will wake up when writer closes - sleep_pipe[0].read if env['PATH_INFO'] == '/sleep' - - [ 200, {'content-length'=>'0', 'content-type'=>'text/plain'}, [] ] - end - ENV['UNICORN_FD'] = srv.fileno.to_s - opts = { - listeners: [ "#{host}:#{port}" ], - stderr_path: err.path, - check_client_connection: true, - } - uni = Unicorn::HttpServer.new(app, opts) - uni.start.join - end - wr.close - - # make sure the server is running, at least - client = tcp_socket(host, port) - client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") - assert client.wait(10), 'never got response from server' - res = client.read - assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response' - assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready' - client.close - - # start a slow request... - sleeper = tcp_socket(host, port) - sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n") - - # and a bunch of aborted ones - nr = 100 - nr.times do |i| - client = tcp_socket(host, port) - client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \ - "Host: example.com\r\n\r\n") - client.close - end - sleep_pipe[1].close # wake up the reader in the worker - res = sleeper.read - assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first sleeper response' - assert_match %r{\r\n\r\n\z}, res, 'got end of sleeper response' - sleeper.close - kpid = pid - pid = nil - Process.kill(:QUIT, kpid) - _, status = Process.waitpid2(kpid) - assert status.success? - reqs = rd.read.to_i - warn "server got #{reqs} requests with #{nr} CCC aborted\n" if $DEBUG - assert_operator reqs, :<, nr - assert_operator reqs, :>=, 2, 'first 2 requests got through, at least' - ensure - return if start_pid != $$ - srv.close if srv - if pid - Process.kill(:QUIT, pid) - _, status = Process.waitpid2(pid) - assert status.success? - end - err.close! if err - rd.close if rd - end -end