kaspad/testing/integration/rpc_test.go
Michael Sutton 9ee409afaa
Fix RPC client memory/goroutine leak (#2122)
* Showcase the RPC client memory leak

* Fixes an RPC client goroutine leak by properly closing the underlying connection
2022-08-09 16:30:24 +03:00

116 lines
2.9 KiB
Go

package integration
import (
"github.com/kaspanet/kaspad/infrastructure/config"
"runtime"
"testing"
"time"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
)
const rpcTimeout = 10 * time.Second
type testRPCClient struct {
*rpcclient.RPCClient
}
func newTestRPCClient(rpcAddress string) (*testRPCClient, error) {
rpcClient, err := rpcclient.NewRPCClient(rpcAddress)
if err != nil {
return nil, err
}
rpcClient.SetTimeout(rpcTimeout)
return &testRPCClient{
RPCClient: rpcClient,
}, nil
}
func connectAndClose(rpcAddress string) error {
client, err := rpcclient.NewRPCClient(rpcAddress)
if err != nil {
return err
}
defer client.Close()
return nil
}
func TestRPCClientGoroutineLeak(t *testing.T) {
_, teardown := setupHarness(t, &harnessParams{
p2pAddress: p2pAddress1,
rpcAddress: rpcAddress1,
miningAddress: miningAddress1,
miningAddressPrivateKey: miningAddress1PrivateKey,
})
defer teardown()
numGoroutinesBefore := runtime.NumGoroutine()
for i := 1; i < 100; i++ {
err := connectAndClose(rpcAddress1)
if err != nil {
t.Fatalf("Failed to set up an RPC client: %s", err)
}
time.Sleep(10 * time.Millisecond)
if runtime.NumGoroutine() > numGoroutinesBefore+10 {
t.Fatalf("Number of goroutines is increasing for each RPC client open (%d -> %d), which indicates a memory leak",
numGoroutinesBefore, runtime.NumGoroutine())
}
}
}
func TestRPCMaxInboundConnections(t *testing.T) {
harness, teardown := setupHarness(t, &harnessParams{
p2pAddress: p2pAddress1,
rpcAddress: rpcAddress1,
miningAddress: miningAddress1,
miningAddressPrivateKey: miningAddress1PrivateKey,
})
defer teardown()
// Close the default RPC client so that it won't interfere with the test
err := harness.rpcClient.Close()
if err != nil {
t.Fatalf("Failed to close the default harness RPCClient: %s", err)
}
// Connect `RPCMaxInboundConnections` clients. We expect this to succeed immediately
rpcClients := []*testRPCClient{}
doneChan := make(chan error)
go func() {
for i := 0; i < config.DefaultMaxRPCClients; i++ {
rpcClient, err := newTestRPCClient(harness.rpcAddress)
if err != nil {
doneChan <- err
}
rpcClients = append(rpcClients, rpcClient)
}
doneChan <- nil
}()
select {
case err = <-doneChan:
if err != nil {
t.Fatalf("newTestRPCClient: %s", err)
}
case <-time.After(time.Second * 5):
t.Fatalf("Timeout for connecting %d RPC connections elapsed", config.DefaultMaxRPCClients)
}
// Try to connect another client. We expect this to fail
// We set a timeout to account for reconnection mechanisms
go func() {
rpcClient, err := newTestRPCClient(harness.rpcAddress)
if err != nil {
doneChan <- err
}
rpcClients = append(rpcClients, rpcClient)
doneChan <- nil
}()
select {
case err = <-doneChan:
if err == nil {
t.Fatalf("newTestRPCClient unexpectedly succeeded")
}
case <-time.After(time.Second * 15):
}
}