...

Source file src/github.com/cybertec-postgresql/pgwatch/v6/internal/testutil/blackhole.go

Documentation: github.com/cybertec-postgresql/pgwatch/v6/internal/testutil

     1  package testutil
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"sync"
     7  	"testing"
     8  )
     9  
    10  // BlackholeListener starts a TCP listener that accepts connections but
    11  // never reads, writes, or closes them. Each accepted connection runs in
    12  // its own goroutine that blocks until the listener is shut down.
    13  //
    14  // This simulates a half-open TCP connection: the client sees an
    15  // established TCP session, but no application-layer response ever arrives.
    16  // The kernel keeps the connection open until the local read deadline (if
    17  // any) fires or the peer gives up. Callers pair this with a context-bound
    18  // round-trip to convert such stalls into bounded client-side failures.
    19  //
    20  // The address is registered for cleanup via t.Cleanup, so a single
    21  // BlackholeListener(t) call is leak-free under normal test runs. The
    22  // returned close func is idempotent and may also be invoked explicitly by
    23  // the test for early teardown.
    24  //
    25  // Concurrency: the accept loop and any number of accepted connections run
    26  // concurrently. Accepted goroutines block until ctx is cancelled; the close
    27  // func waits for them via wg.Wait before returning.
    28  func BlackholeListener(t *testing.T) (string, func()) {
    29  	t.Helper()
    30  
    31  	ln, err := net.Listen("tcp", "127.0.0.1:0")
    32  	if err != nil {
    33  		t.Fatalf("blackhole: listen: %v", err)
    34  	}
    35  
    36  	ctx, cancel := context.WithCancel(context.Background())
    37  	var wg sync.WaitGroup
    38  
    39  	// Accept loop. Stops as soon as the listener is closed.
    40  	go func() {
    41  		for {
    42  			conn, err := ln.Accept()
    43  			if err != nil {
    44  				return
    45  			}
    46  			wg.Add(1)
    47  			go func(net.Conn) {
    48  				defer wg.Done()
    49  				// Hold the connection until the listener is closed.
    50  				// Intentionally no read/write/close — we are simulating
    51  				// a peer that silently dropped packets.
    52  				<-ctx.Done()
    53  			}(conn)
    54  		}
    55  	}()
    56  
    57  	c := func() {
    58  		cancel()
    59  		_ = ln.Close()
    60  		wg.Wait()
    61  	}
    62  	t.Cleanup(c)
    63  	return ln.Addr().String(), c
    64  }
    65