Skip to content

Commit 8540d8a

Browse files
author
Garipov Ainar
committed
Avoid goroutine leak on error in query with context
Ensure the cancellation watcher is closed if the query results in error. Closes #617.
1 parent 2704adc commit 8540d8a

File tree

2 files changed

+34
-0
lines changed

2 files changed

+34
-0
lines changed

conn_go18.go

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.Na
1919
finish := cn.watchCancel(ctx)
2020
r, err := cn.query(query, list)
2121
if err != nil {
22+
if finish != nil {
23+
finish()
24+
}
2225
return nil, err
2326
}
2427
r.finish = finish

go18_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package pq
55
import (
66
"context"
77
"database/sql"
8+
"runtime"
89
"testing"
910
"time"
1011
)
@@ -155,6 +156,36 @@ func TestContextCancelQuery(t *testing.T) {
155156
}
156157
}
157158

159+
// TestIssue617 tests that a failed query in QueryContext doesn't lead to a
160+
// goroutine leak.
161+
func TestIssue617(t *testing.T) {
162+
db := openTestConn(t)
163+
defer db.Close()
164+
165+
const N = 10
166+
167+
numGoroutineStart := runtime.NumGoroutine()
168+
for i := 0; i < N; i++ {
169+
func() {
170+
ctx, cancel := context.WithCancel(context.Background())
171+
defer cancel()
172+
_, err := db.QueryContext(ctx, `SELECT * FROM DOESNOTEXIST`)
173+
pqErr, _ := err.(*Error)
174+
// Expecting "pq: relation \"doesnotexist\" does not exist" error.
175+
if err == nil || pqErr == nil || pqErr.Code != "42P01" {
176+
t.Fatalf("expected undefined table error, got %v", err)
177+
}
178+
}()
179+
}
180+
numGoroutineFinish := runtime.NumGoroutine()
181+
182+
// We use N/2 and not N because the GC and other actors may increase or
183+
// decrease the number of goroutines.
184+
if numGoroutineFinish-numGoroutineStart >= N/2 {
185+
t.Errorf("goroutine leak detected, was %d, now %d", numGoroutineStart, numGoroutineFinish)
186+
}
187+
}
188+
158189
func TestContextCancelBegin(t *testing.T) {
159190
db := openTestConn(t)
160191
defer db.Close()

0 commit comments

Comments
 (0)