PAGI-Server
view release on metacpan or search on metacpan
t/http2/18-transport-leak.t view on Meta::CPAN
on_frame_recv => $overrides{on_frame_recv} // sub { 0 },
on_data_chunk_recv => $overrides{on_data_chunk_recv} // sub { 0 },
on_stream_close => $overrides{on_stream_close} // sub { 0 },
},
);
}
sub pump {
my ($client, $client_sock, $cond) = @_;
for (1 .. 100) {
$loop->loop_once(0.02);
my $buf = '';
$client_sock->sysread($buf, 65536);
$client->mem_recv($buf) if length($buf);
my $out = $client->mem_send;
$client_sock->syswrite($out) if length($out);
last if $cond && $cond->();
}
}
subtest 'h2 transport handle (and its stream-state cycle) is collected after the request' => sub {
# The app weak-probes its OWN transport handle. This is race-free: the app
# is guaranteed to run, so the probe is always captured while the handle is
# live (unlike polling $conn->{h2_streams} from outside, which misses a fast
# request that is created and torn down within a single loop tick).
my ($saw_handle, $probe);
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $receive->();
my $t = $scope->{'pagi.transport'};
$saw_handle = $t ? 1 : 0;
weaken($probe = $t); # weak: survives the request only if leaked
await $send->({
type => 'http.response.start',
status => 200,
headers => [['content-type', 'text/plain']],
});
# Stream a few chunks so the handle is genuinely exercised (send_queue
# populated, the cycle fully formed).
for my $i (1 .. 3) {
await $send->({ type => 'http.response.body', body => "chunk$i", more => 1 });
}
await $send->({ type => 'http.response.body', body => 'final', more => 0 });
};
my ($conn, $stream_io, $client_sock, $server) = create_h2c_connection(app => $app);
my $stream_closed = 0;
my $client = create_client(on_stream_close => sub { $stream_closed = 1; return 0 });
# h2c handshake.
$client->send_connection_preface;
$client_sock->syswrite($client->mem_send);
pump($client, $client_sock);
$client->submit_request(
method => 'GET',
path => '/streaming',
scheme => 'http',
authority => 'localhost',
);
$client_sock->syswrite($client->mem_send);
# Pump until the stream completes and closes (request fully handled).
pump($client, $client_sock, sub { $stream_closed });
ok($stream_closed, 'stream completed and closed');
ok($saw_handle, 'transport handle was attached to the h2 scope');
# Drive deferred teardown (loop->later) AND adopted-future cleanup so the
# scope/coroutine that transiently hold the handle are released. Break early
# once the probe is collected.
for (1 .. 200) {
last unless defined $probe;
$loop->loop_once(0.01);
}
is($probe, undef,
'transport handle (and its $ss cycle) collected after teardown; no leak');
$stream_io->close_now;
$loop->remove($server);
};
done_testing;
( run in 0.616 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )