Skip to content

Commit e4d4f76

Browse files
authored
Fix memory leak in SQL helper when database is not available (#26607)
Explicitly close SQL connections when connectivity checks fail during their creation, to avoid leaking resources when a connection is created but it is not going to be used.
1 parent fb3bac9 commit e4d4f76

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

CHANGELOG.next.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
502502
- Fix metric grouping for windows/perfmon module {issue}23489[23489] {pull}23505[23505]
503503
- Major refactor of system/cpu and system/core metrics. {pull}25771[25771]
504504
- Fix GCP Project ID being ingested as `cloud.account.id` in `gcp.billing` module {issue}26357[26357] {pull}26412[26412]
505+
- Fix memory leak in SQL module when database is not available. {issue}25840[25840] {pull}26607[26607]
505506
- Fix aws metric tags with resourcegroupstaggingapi paginator. {issue}26385[26385] {pull}26443[26443]
506507

507508
*Packetbeat*

metricbeat/helper/sql/sql.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ func NewDBClient(driver, uri string, l *logp.Logger) (*DbClient, error) {
5252
}
5353
err = dbx.Ping()
5454
if err != nil {
55+
if closeErr := dbx.Close(); closeErr != nil {
56+
return nil, errors.Wrapf(err, "failed to close with %s, after connection test failed", closeErr)
57+
}
5558
return nil, errors.Wrap(err, "testing connection")
5659
}
5760

metricbeat/helper/sql/sql_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@
1818
package sql
1919

2020
import (
21+
"context"
22+
"database/sql"
23+
"database/sql/driver"
24+
"fmt"
2125
"math"
2226
"testing"
2327
"time"
2428

29+
"github.com/stretchr/testify/require"
30+
2531
"github.com/elastic/beats/v7/libbeat/common"
32+
"github.com/elastic/beats/v7/libbeat/tests/resources"
2633
)
2734

2835
type kv struct {
@@ -186,3 +193,55 @@ func TestToDotKeys(t *testing.T) {
186193
t.Fail()
187194
}
188195
}
196+
197+
func TestNewDBClient(t *testing.T) {
198+
t.Run("create and close", func(t *testing.T) {
199+
goroutines := resources.NewGoroutinesChecker()
200+
defer goroutines.Check(t)
201+
202+
client, err := NewDBClient("dummy", "localhost", nil)
203+
require.NoError(t, err)
204+
205+
err = client.Close()
206+
require.NoError(t, err)
207+
})
208+
209+
t.Run("unavailable", func(t *testing.T) {
210+
goroutines := resources.NewGoroutinesChecker()
211+
defer goroutines.Check(t)
212+
213+
_, err := NewDBClient("dummy", "unavailable", nil)
214+
require.Error(t, err)
215+
})
216+
}
217+
218+
func init() {
219+
sql.Register("dummy", dummyDriver{})
220+
}
221+
222+
type dummyDriver struct{}
223+
224+
func (dummyDriver) Open(name string) (driver.Conn, error) {
225+
if name == "error" {
226+
return nil, fmt.Errorf("error")
227+
}
228+
229+
return &dummyConnection{name: name}, nil
230+
}
231+
232+
type dummyConnection struct {
233+
name string
234+
}
235+
236+
// Ensure that this dummy connection implements the pinger interface, used by the helper.
237+
var _ driver.Pinger = &dummyConnection{}
238+
239+
func (*dummyConnection) Prepare(query string) (driver.Stmt, error) { return nil, nil }
240+
func (*dummyConnection) Close() error { return nil }
241+
func (*dummyConnection) Begin() (driver.Tx, error) { return nil, nil }
242+
func (c *dummyConnection) Ping(context.Context) error {
243+
if c.name == "unavailable" {
244+
return fmt.Errorf("database unavailable")
245+
}
246+
return nil
247+
}

0 commit comments

Comments
 (0)