Skip to content

Commit dd9f27b

Browse files
authored
Merge pull request #1629 from ydb-platform/fix-close
* Fixed goroutine leak on closing `database/sql` driver
2 parents e7acaa3 + 4bf75c2 commit dd9f27b

9 files changed

+38
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Fixed goroutine leak on closing `database/sql` driver
12
* "No endpoints" is retriable error now
23

34
## v3.99.3

driver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ func (d *Driver) Close(ctx context.Context) (finalErr error) {
164164
d.ctxCancel()
165165

166166
defer func() {
167-
for _, f := range d.onClose {
168-
f(d)
167+
for _, onClose := range d.onClose {
168+
onClose(d)
169169
}
170170
}()
171171

internal/repeater/repeater.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,10 @@ func (r *repeater) wakeUp(e Event) (err error) {
163163
}
164164

165165
func (r *repeater) worker(ctx context.Context, tick clockwork.Ticker) {
166-
defer close(r.stopped)
167-
defer tick.Stop()
166+
defer func() {
167+
close(r.stopped)
168+
tick.Stop()
169+
}()
168170

169171
// force returns backoff with delays [500ms...32s]
170172
force := backoff.New(

internal/xsql/connector.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type (
4343
LegacyOpts []legacy.Option
4444
Options []propose.Option
4545
disableServerBalancer bool
46-
onCLose []func(*Connector)
46+
onClose []func(*Connector)
4747

4848
clock clockwork.Clock
4949
idleThreshold time.Duration
@@ -204,7 +204,7 @@ func (c *Connector) Close() error {
204204
default:
205205
close(c.done)
206206

207-
for _, onClose := range c.onCLose {
207+
for _, onClose := range c.onClose {
208208
onClose(c)
209209
}
210210

internal/xsql/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (opt traceRetryOption) Apply(c *Connector) error {
7676
}
7777

7878
func (onClose onCloseOption) Apply(c *Connector) error {
79-
c.onCLose = append(c.onCLose, onClose)
79+
c.onClose = append(c.onClose, onClose)
8080

8181
return nil
8282
}

sql.go

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql"
1212
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/legacy"
1313
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql/propose"
14-
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync"
1514
"github.com/ydb-platform/ydb-go-sdk/v3/table"
1615
"github.com/ydb-platform/ydb-go-sdk/v3/table/options"
1716
"github.com/ydb-platform/ydb-go-sdk/v3/trace"
@@ -32,31 +31,13 @@ func withConnectorOptions(opts ...ConnectorOption) Option {
3231
}
3332
}
3433

35-
type sqlDriver struct {
36-
connectors xsync.Map[*xsql.Connector, *Driver]
37-
}
34+
type sqlDriver struct{}
3835

3936
var (
4037
_ driver.Driver = &sqlDriver{}
4138
_ driver.DriverContext = &sqlDriver{}
4239
)
4340

44-
func (d *sqlDriver) Close() error {
45-
var errs []error
46-
d.connectors.Range(func(c *xsql.Connector, _ *Driver) bool {
47-
if err := c.Close(); err != nil {
48-
errs = append(errs, err)
49-
}
50-
51-
return true
52-
})
53-
if len(errs) > 0 {
54-
return xerrors.NewWithIssues("ydb legacy driver close failed", errs...)
55-
}
56-
57-
return nil
58-
}
59-
6041
// Open returns a new Driver to the ydb.
6142
func (d *sqlDriver) Open(string) (driver.Conn, error) {
6243
return nil, xsql.ErrUnsupported
@@ -68,15 +49,16 @@ func (d *sqlDriver) OpenConnector(dataSourceName string) (driver.Connector, erro
6849
return nil, xerrors.WithStackTrace(fmt.Errorf("failed to connect by data source name '%s': %w", dataSourceName, err))
6950
}
7051

71-
return Connector(db, db.databaseSQLOptions...)
72-
}
73-
74-
func (d *sqlDriver) attach(c *xsql.Connector, parent *Driver) {
75-
d.connectors.Set(c, parent)
76-
}
52+
c, err := Connector(db, append(db.databaseSQLOptions,
53+
xsql.WithOnClose(func(connector *xsql.Connector) {
54+
_ = db.Close(context.Background())
55+
}),
56+
)...)
57+
if err != nil {
58+
return nil, xerrors.WithStackTrace(fmt.Errorf("failed to create connector: %w", err))
59+
}
7760

78-
func (d *sqlDriver) detach(c *xsql.Connector) {
79-
d.connectors.Delete(c)
61+
return c, nil
8062
}
8163

8264
type QueryMode int
@@ -242,15 +224,13 @@ func Connector(parent *Driver, opts ...ConnectorOption) (SQLConnector, error) {
242224
parent.databaseSQLOptions,
243225
opts...,
244226
),
245-
xsql.WithOnClose(d.detach),
246227
xsql.WithTraceRetry(parent.config.TraceRetry()),
247228
xsql.WithRetryBudget(parent.config.RetryBudget()),
248229
)...,
249230
)
250231
if err != nil {
251232
return nil, xerrors.WithStackTrace(err)
252233
}
253-
d.attach(c, parent)
254234

255235
return c, nil
256236
}

tests/integration/basic_example_database_sql_bindings_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
)
2727

2828
func TestBasicExampleDatabaseSqlBindings(t *testing.T) {
29+
defer simpleDetectGoroutineLeak(t)
30+
2931
folder := t.Name()
3032

3133
ctx, cancel := context.WithTimeout(xtest.Context(t), 42*time.Second)

tests/integration/basic_example_database_sql_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
)
2727

2828
func TestBasicExampleDatabaseSql(t *testing.T) {
29+
defer simpleDetectGoroutineLeak(t)
30+
2931
folder := t.Name()
3032

3133
ctx, cancel := context.WithTimeout(xtest.Context(t), 42*time.Second)

tests/integration/helpers_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"os"
1212
"path"
13+
"runtime"
1314
"strings"
1415
"testing"
1516
"text/template"
@@ -468,3 +469,16 @@ func driverEngine(db *sql.DB) (engine xsql.Engine) {
468469

469470
return engine
470471
}
472+
473+
func simpleDetectGoroutineLeak(t *testing.T) {
474+
// 1) testing.go => main.main()
475+
// 2) current test
476+
const expectedGoroutinesCount = 2
477+
if num := runtime.NumGoroutine(); num > expectedGoroutinesCount {
478+
bb := make([]byte, 2<<32)
479+
if n := runtime.Stack(bb, true); n < len(bb) {
480+
bb = bb[:n]
481+
}
482+
t.Error(fmt.Sprintf("unexpected goroutines:\n%s\n", string(bb[runtime.Stack(bb, false)+1:])))
483+
}
484+
}

0 commit comments

Comments
 (0)