Skip to content

Commit 153f716

Browse files
authored
test(network): re-add Test_conn_startSending with deterministic ordering (#4432)
The test was removed on master 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
1 parent 30b9f82 commit 153f716

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

network/transport/grpc/connection_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ package grpc
2020

2121
import (
2222
"context"
23+
"github.com/nuts-foundation/nuts-node/test"
2324
"github.com/stretchr/testify/require"
25+
"google.golang.org/grpc/codes"
2426
"sync"
27+
"sync/atomic"
2528
"testing"
2629
"time"
2730

@@ -101,6 +104,33 @@ func Test_conn_registerStream(t *testing.T) {
101104
})
102105
}
103106

107+
func Test_conn_startSending(t *testing.T) {
108+
t.Run("disconnect does not panic", func(t *testing.T) {
109+
connection := createConnection(context.Background(), transport.Peer{}).(*conn)
110+
stream := newServerStream("foo", "", nil)
111+
112+
defer stream.cancelFunc()
113+
114+
p := &TestProtocol{}
115+
_ = connection.registerStream(p, stream)
116+
117+
assert.Equal(t, int32(2), connection.activeGoroutines) // startSending and startReceiving
118+
119+
// Disconnect before cancelling the stream: this guarantees the connection context is
120+
// cancelled before RecvMsg returns, so the receive loop drops the message instead of
121+
// racing to store the stream error as close status.
122+
connection.disconnect()
123+
stream.cancelFunc()
124+
125+
test.WaitFor(t, func() (bool, error) {
126+
return atomic.LoadInt32(&connection.activeGoroutines) == 0, nil
127+
}, 5*time.Second, "waiting for all goroutines to exit")
128+
129+
// A deliberate local disconnect must not record a close error. Default value is OK.
130+
assert.Equal(t, codes.OK, connection.status.Load().Code())
131+
})
132+
}
133+
104134
func TestConn_Send(t *testing.T) {
105135
t.Run("buffer overflow softlimit", func(t *testing.T) {
106136
connection := createConnection(context.Background(), transport.Peer{}).(*conn)

0 commit comments

Comments
 (0)