From 102cb22ec9469a785a7eacc68d0de2b7cf3bb30d Mon Sep 17 00:00:00 2001 From: Steven van der Vegt Date: Mon, 27 Jul 2026 18:37:59 +0200 Subject: [PATCH] test(network): re-add Test_conn_startSending with deterministic ordering The test was removed in #2618 because it was flaky: it cancelled the stream before calling disconnect(), racing the receive goroutine (which stores the stream error as close status) against disconnect() cancelling the connection context. Disconnecting first guarantees the context is cancelled before RecvMsg returns, making the test deterministic while restoring the goroutine-exit and no-panic coverage. Verified with -race -count=100. Assisted-by: AI --- network/transport/grpc/connection_test.go | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/network/transport/grpc/connection_test.go b/network/transport/grpc/connection_test.go index 101fda5b0f..1ca7652176 100644 --- a/network/transport/grpc/connection_test.go +++ b/network/transport/grpc/connection_test.go @@ -20,8 +20,11 @@ package grpc import ( "context" + "github.com/nuts-foundation/nuts-node/test" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" "sync" + "sync/atomic" "testing" "time" @@ -101,6 +104,33 @@ func Test_conn_registerStream(t *testing.T) { }) } +func Test_conn_startSending(t *testing.T) { + t.Run("disconnect does not panic", func(t *testing.T) { + connection := createConnection(context.Background(), transport.Peer{}).(*conn) + stream := newServerStream("foo", "", nil) + + defer stream.cancelFunc() + + p := &TestProtocol{} + _ = connection.registerStream(p, stream) + + assert.Equal(t, int32(2), connection.activeGoroutines) // startSending and startReceiving + + // Disconnect before cancelling the stream: this guarantees the connection context is + // cancelled before RecvMsg returns, so the receive loop drops the message instead of + // racing to store the stream error as close status. + connection.disconnect() + stream.cancelFunc() + + test.WaitFor(t, func() (bool, error) { + return atomic.LoadInt32(&connection.activeGoroutines) == 0, nil + }, 5*time.Second, "waiting for all goroutines to exit") + + // A deliberate local disconnect must not record a close error. Default value is OK. + assert.Equal(t, codes.OK, connection.status.Load().Code()) + }) +} + func TestConn_Send(t *testing.T) { t.Run("buffer overflow softlimit", func(t *testing.T) { connection := createConnection(context.Background(), transport.Peer{}).(*conn)