Async-Redis

 view release on metacpan or  search on metacpan

t/93-socket-cleanup/close-with-watchers.t  view on Meta::CPAN

        my $blocking_future = $r->blpop('nonexistent:key:for:close:test', 1);

        # Get the socket fileno before disconnect
        my $fileno_before = fileno($r->{socket});
        ok(defined $fileno_before, "socket has valid fileno before disconnect: $fileno_before");

        # Now disconnect while the BLPOP is still waiting
        # This is where the problem occurs - close() is called while
        # Future::IO still has a read watcher on this fileno
        $r->disconnect;

        # The socket should be gone now
        ok(!defined $r->{socket}, 'socket is undef after disconnect');

        # Wait a moment for any async cleanup to propagate
        run { Future::IO->sleep(0.1) };

        # Check for warnings related to fileno issues
        # Different backends may produce different warnings:
        # - "Bad file descriptor"
        # - "Invalid argument"
        # - Warnings about removing non-existent watchers
        my @fileno_warnings = grep {
            /fileno|file descriptor|invalid|bad fd|not registered/i
        } @warnings;

        # This is what we're testing - ideally there should be NO warnings
        # If there are warnings, it indicates the close() happened before
        # the poll loop could unregister its watchers
        if (@fileno_warnings) {
            diag "Captured fileno-related warnings (this indicates the bug):";
            diag "  $_" for @fileno_warnings;
        }

        # For now, just note any warnings - this test documents the behavior
        # After the fix, we expect zero warnings
        note "Total warnings captured: " . scalar(@warnings);
        for my $w (@warnings) {
            note "  Warning: $w";
        }

        # The blocking future should be cancelled/failed
        ok($blocking_future->is_ready, 'blocking future is ready after disconnect');
        ok($blocking_future->is_cancelled || $blocking_future->is_failed,
           'blocking future is cancelled or failed');

        pass('disconnect completed without crashing');
    };

    subtest 'rapid connect/disconnect cycles stress test' => sub {
        my @warnings;
        local $SIG{__WARN__} = sub { push @warnings, @_ };

        # Rapidly connect and disconnect, sometimes with pending operations
        for my $i (1..10) {
            my $r = Async::Redis->new(
                host => $ENV{REDIS_HOST} // 'localhost',
            );
            run { $r->connect };

            # On odd iterations, start an operation before disconnect
            if ($i % 2) {
                my $f = $r->ping;  # Start a command
                $r->disconnect;   # Disconnect while it's pending
            } else {
                $r->disconnect;   # Clean disconnect
            }
        }

        # Give async cleanup time to complete
        run { Future::IO->sleep(0.2) };

        my @fileno_warnings = grep {
            /fileno|file descriptor|invalid|bad fd|not registered/i
        } @warnings;

        if (@fileno_warnings) {
            diag "Stress test captured fileno warnings:";
            diag "  $_" for @fileno_warnings;
        }

        # Document the behavior - after fix, expect zero warnings
        is(scalar(@fileno_warnings), 0,
           'no fileno-related warnings during rapid connect/disconnect')
            or diag "Got " . scalar(@fileno_warnings) . " warnings";

        pass('stress test completed without crashing');
    };

    subtest 'disconnect during concurrent commands' => sub {
        my @warnings;
        local $SIG{__WARN__} = sub { push @warnings, @_ };

        my $r = Async::Redis->new(
            host => $ENV{REDIS_HOST} // 'localhost',
        );
        run { $r->connect };

        # Fire off multiple concurrent commands
        my @futures;
        for my $i (1..5) {
            push @futures, $r->set("close:test:$i", "value:$i");
        }

        # Disconnect immediately while commands are in flight
        $r->disconnect;

        # All futures should be ready (completed, failed, or cancelled)
        run { Future::IO->sleep(0.1) };

        for my $i (0..$#futures) {
            ok($futures[$i]->is_ready, "future $i is ready after disconnect");
        }

        my @fileno_warnings = grep {
            /fileno|file descriptor|invalid|bad fd|not registered/i
        } @warnings;

        is(scalar(@fileno_warnings), 0,
           'no fileno-related warnings during concurrent disconnect')
            or do {



( run in 2.870 seconds using v1.01-cache-2.11-cpan-b9db842bd85 )