PAGI-Server
view release on metacpan or search on metacpan
t/http2/18-transport-leak.t view on Meta::CPAN
);
$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;
t/http2/20-sse-transport.t view on Meta::CPAN
ok($hit_high, 'on_high_water fired when the per-stream queue exceeded the high mark');
ok($hit_drain, 'on_drain fired once nghttp2 drained the queue below the low mark');
$stream_io->close_now;
$loop->remove($server);
};
subtest 'SSE-over-h2 transport handle (and its $ss cycle) is collected at teardown' => sub {
# The app weak-probes its OWN transport handle (race-free: the app always
# runs), sends a few events, then returns. With the app coroutine complete
# the scope is released, so the handle is held only by $ss->{transport_state}.
# A client RST_STREAM drives _h2_on_close, which must delete that ref and
# break the cycle -- otherwise the stream state leaks for the life of the
# process (one per SSE request).
my ($saw_handle, $probe);
my $app = async sub {
my ($scope, $receive, $send) = @_;
await $receive->();
t/sse-close.t view on Meta::CPAN
my ($app) = @_;
my $server = PAGI::Server->new(
app => $app, host => '127.0.0.1', port => 0, quiet => 1, shutdown_timeout => 1,
);
$loop->add($server);
$server->listen->get;
return $server;
}
# Open an SSE GET, read raw bytes until the server closes (EOF) or a deadline,
# then drain the loop so the app coroutine finishes. Returns (wire, saw_eof).
sub sse_get {
my ($port) = @_;
my $sock = IO::Socket::INET->new(
PeerAddr => '127.0.0.1', PeerPort => $port, Proto => 'tcp', Timeout => 5,
) or return ('', 0);
print $sock "GET / HTTP/1.1\r\nHost: 127.0.0.1:$port\r\nAccept: text/event-stream\r\n\r\n";
$sock->blocking(0);
my $wire = '';
my $eof = 0;
my $deadline = time + 5;
while (time < $deadline) {
my $buf;
my $n = sysread($sock, $buf, 4096);
if (defined $n && $n > 0) { $wire .= $buf }
elsif (defined $n && $n == 0) { $eof = 1; last } # server closed the stream
$loop->loop_once(0.05);
}
close $sock;
$loop->loop_once(0.05) for 1 .. 20; # let the app coroutine run to completion
return ($wire, $eof);
}
subtest 'sse.close ends the stream; send-after-close raises' => sub {
my ($close_ok, $post_close_raised) = (0, 0);
my $app = async sub {
my ($scope, $receive, $send) = @_;
die "expected sse scope" unless ($scope->{type} // '') eq 'sse';
( run in 2.608 seconds using v1.01-cache-2.11-cpan-7fcb06a456a )