| #!perl -w |
| # Copyright (C) all contributors <meta@public-inbox.org> |
| # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> |
| # |
| # Ensure buffering behavior in -httpd doesn't cause runaway memory use |
| # or data corruption |
| use strict; |
| use v5.10.1; |
| use POSIX qw(setsid); |
| use PublicInbox::TestCommon; |
| |
| my $git_dir = $ENV{GIANT_GIT_DIR}; |
| plan 'skip_all' => 'GIANT_GIT_DIR not defined' unless $git_dir; |
| require_mods(qw(BSD::Resource Plack::Util Plack::Builder |
| HTTP::Date HTTP::Status HTTP::Tiny)); |
| my $psgi = "./t/git-http-backend.psgi"; |
| my ($tmpdir, $for_destroy) = tmpdir(); |
| my $err = "$tmpdir/stderr.log"; |
| my $out = "$tmpdir/stdout.log"; |
| my $sock = tcp_server(); |
| my ($host, $port) = tcp_host_port($sock); |
| my $td; |
| my $http = HTTP::Tiny->new; |
| |
| my $get_maxrss = sub { |
| my $res = $http->get("http://$host:$port/"); |
| is($res->{status}, 200, 'success reading maxrss'); |
| my $buf = $res->{content}; |
| like($buf, qr/\A\d+\n\z/, 'got memory response'); |
| ok(int($buf) > 0, 'got non-zero memory response'); |
| int($buf); |
| }; |
| |
| { |
| my $cmd = [ '-httpd', '-W0', "--stdout=$out", "--stderr=$err", $psgi ]; |
| $td = start_script($cmd, undef, { 3 => $sock }); |
| } |
| my $mem_a = $get_maxrss->(); |
| |
| SKIP: { |
| my $max = 0; |
| my $pack; |
| my $glob = "$git_dir/objects/pack/pack-*.pack"; |
| foreach my $f (glob($glob)) { |
| my $n = -s $f; |
| if ($n > $max) { |
| $max = $n; |
| $pack = $f; |
| } |
| } |
| skip "no packs found in $git_dir" unless defined $pack; |
| if ($pack !~ m!(/objects/pack/pack-[a-f0-9]{40,64}.pack)\z!) { |
| skip "bad pack name: $pack"; |
| } |
| my $s = tcp_connect($sock); |
| print $s "GET $1 HTTP/1.1\r\nHost: $host:$port\r\n\r\n" or xbail $!; |
| my $hdr = do { local $/ = "\r\n\r\n"; readline($s) }; |
| like $hdr, qr!\AHTTP/1\.1\s+200\b!, 'got 200 success for pack'; |
| like $hdr, qr/^content-length:\s*$max\r\n/ims, |
| 'got expected Content-Length for pack'; |
| |
| # don't read the body |
| for my $i (1..3) { |
| sleep 1; |
| my $diff = $get_maxrss->() - $mem_a; |
| note "${diff}K memory increase after $i seconds"; |
| ok($diff < 1024, 'no bloating caused by slow dumb client'); |
| } |
| } |
| |
| SKIP: { # make sure Last-Modified + If-Modified-Since works with curl |
| my $nr = 6; |
| skip 'no description', $nr unless -f "$git_dir/description"; |
| my $mtime = (stat(_))[9]; |
| my $curl = require_cmd('curl', 1) or skip 'curl(1) not found', $nr; |
| my $url = "http://$host:$port/description"; |
| my $dst = "$tmpdir/desc"; |
| is(xsys($curl, qw(-RsSf), '-o', $dst, $url), 0, 'curl -R'); |
| is((stat($dst))[9], $mtime, 'curl used remote mtime'); |
| is(xsys($curl, qw(-sSf), '-z', $dst, '-o', "$dst.2", $url), 0, |
| 'curl -z noop'); |
| ok(!-e "$dst.2", 'no modification, nothing retrieved'); |
| utime(0, 0, $dst) or die "utime failed: $!"; |
| is(xsys($curl, qw(-sSfR), '-z', $dst, '-o', "$dst.2", $url), 0, |
| 'curl -z updates'); |
| ok(-e "$dst.2", 'faked modification, got new file retrieved'); |
| } |
| |
| { |
| my $c = fork; |
| if ($c == 0) { |
| setsid(); |
| exec qw(git clone -q --mirror), "http://$host:$port/", |
| "$tmpdir/mirror.git"; |
| die "Failed start git clone: $!\n"; |
| } |
| select(undef, undef, undef, 0.1); |
| foreach my $i (1..10) { |
| is(1, kill('STOP', -$c), 'signaled clone STOP'); |
| sleep 1; |
| ok(kill('CONT', -$c), 'continued clone'); |
| my $diff = $get_maxrss->() - $mem_a; |
| note "${diff}K memory increase after $i seconds"; |
| ok($diff < 2048, 'no bloating caused by slow smart client'); |
| } |
| ok(kill('CONT', -$c), 'continued clone'); |
| is($c, waitpid($c, 0), 'reaped wayward slow clone'); |
| is($?, 0, 'clone did not error out'); |
| note 'clone done, fsck-ing clone result...'; |
| is(0, system("git", "--git-dir=$tmpdir/mirror.git", |
| qw(fsck --no-progress)), |
| 'fsck did not report corruption'); |
| |
| my $diff = $get_maxrss->() - $mem_a; |
| note "${diff}K memory increase after smart clone"; |
| ok($diff < 2048, 'no bloating caused by slow smart client'); |
| } |
| |
| { |
| ok($td->kill, 'killed httpd'); |
| $td->join; |
| } |
| |
| done_testing(); |