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 )