Skip to content

Commit 82234cb

Browse files
authored
Merge pull request #2380 from ilyam8/feat-stdlib-shouldping
feat(stdlib): add OptionShouldPing to control ResetSession ping behavior
2 parents b50e086 + 2906a0d commit 82234cb

2 files changed

Lines changed: 65 additions & 6 deletions

File tree

stdlib/sql.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ func init() {
124124
// OptionOpenDB options for configuring the driver when opening a new db pool.
125125
type OptionOpenDB func(*connector)
126126

127+
// ShouldPingParams are passed to OptionShouldPing to decide whether to ping before reusing a connection.
128+
type ShouldPingParams struct {
129+
// Conn is the underlying pgx connection.
130+
Conn *pgx.Conn
131+
// IdleDuration is how long it has been since ResetSession last ran.
132+
IdleDuration time.Duration
133+
}
134+
135+
// OptionShouldPing controls whether stdlib should issue a liveness ping before reusing a connection.
136+
// If the function returns true, stdlib will ping.
137+
// If it returns false, stdlib will skip the ping.
138+
// If not provided, default is ping only when IdleDuration > 1s.
139+
func OptionShouldPing(f func(context.Context, ShouldPingParams) bool) OptionOpenDB {
140+
return func(dc *connector) { dc.ShouldPing = f }
141+
}
142+
127143
// OptionBeforeConnect provides a callback for before connect. It is passed a shallow copy of the ConnConfig that will
128144
// be used to connect, so only its immediate members should be modified. Used only if db is opened with *pgx.ConnConfig.
129145
func OptionBeforeConnect(bc func(context.Context, *pgx.ConnConfig) error) OptionOpenDB {
@@ -231,6 +247,7 @@ type connector struct {
231247
BeforeConnect func(context.Context, *pgx.ConnConfig) error // function to call before creation of every new connection
232248
AfterConnect func(context.Context, *pgx.Conn) error // function to call after creation of every new connection
233249
ResetSession func(context.Context, *pgx.Conn) error // function is called before a connection is reused
250+
ShouldPing func(context.Context, ShouldPingParams) bool // function to decide if stdlib should ping before reusing a connection
234251
driver *Driver
235252
}
236253

@@ -282,6 +299,7 @@ func (c connector) Connect(ctx context.Context) (driver.Conn, error) {
282299
driver: c.driver,
283300
connConfig: connConfig,
284301
resetSessionFunc: c.ResetSession,
302+
shouldPing: c.ShouldPing,
285303
psRefCounts: make(map[*pgconn.StatementDescription]int),
286304
}, nil
287305
}
@@ -389,7 +407,8 @@ type Conn struct {
389407
close func(context.Context) error
390408
driver *Driver
391409
connConfig pgx.ConnConfig
392-
resetSessionFunc func(context.Context, *pgx.Conn) error // Function is called before a connection is reused
410+
resetSessionFunc func(context.Context, *pgx.Conn) error // Function is called before a connection is reused
411+
shouldPing func(context.Context, ShouldPingParams) bool // Function to decide if stdlib should ping before reusing a connection
393412
lastResetSessionTime time.Time
394413

395414
// psRefCounts contains reference counts for prepared statements. Prepare uses the underlying pgx logic to generate
@@ -537,11 +556,23 @@ func (c *Conn) ResetSession(ctx context.Context) error {
537556
}
538557

539558
now := time.Now()
540-
if now.Sub(c.lastResetSessionTime) > time.Second {
559+
idle := now.Sub(c.lastResetSessionTime)
560+
561+
doPing := idle > time.Second // default behavior: ping only if idle > 1s
562+
563+
if c.shouldPing != nil {
564+
doPing = c.shouldPing(ctx, ShouldPingParams{
565+
Conn: c.conn,
566+
IdleDuration: idle,
567+
})
568+
}
569+
570+
if doPing {
541571
if err := c.conn.PgConn().Ping(ctx); err != nil {
542572
return driver.ErrBadConn
543573
}
544574
}
575+
545576
c.lastResetSessionTime = now
546577

547578
return c.resetSessionFunc(ctx, c.conn)

stdlib/sql_test.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@ import (
1515
"testing"
1616
"time"
1717

18+
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/require"
20+
1821
"github.com/jackc/pgx/v5"
1922
"github.com/jackc/pgx/v5/pgconn"
2023
"github.com/jackc/pgx/v5/pgtype"
2124
"github.com/jackc/pgx/v5/pgxpool"
2225
"github.com/jackc/pgx/v5/stdlib"
2326
"github.com/jackc/pgx/v5/tracelog"
24-
"github.com/stretchr/testify/assert"
25-
"github.com/stretchr/testify/require"
2627
)
2728

28-
func openDB(t testing.TB) *sql.DB {
29+
func openDB(t testing.TB, opts ...stdlib.OptionOpenDB) *sql.DB {
30+
t.Helper()
2931
config, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
3032
require.NoError(t, err)
31-
return stdlib.OpenDB(*config)
33+
return stdlib.OpenDB(*config, opts...)
3234
}
3335

3436
func closeDB(t testing.TB, db *sql.DB) {
@@ -1374,3 +1376,29 @@ func TestCheckIdleConn(t *testing.T) {
13741376

13751377
require.NotContains(t, pids, cPID)
13761378
}
1379+
1380+
func TestOptionShouldPing_HookCalledOnReuse(t *testing.T) {
1381+
hookCalled := false
1382+
1383+
db := openDB(t,
1384+
stdlib.OptionShouldPing(func(context.Context, stdlib.ShouldPingParams) bool {
1385+
hookCalled = true
1386+
// Return false to avoid relying on actual ping behavior.
1387+
return false
1388+
}),
1389+
)
1390+
defer closeDB(t, db)
1391+
1392+
// Ensure reuse (so ResetSession runs)
1393+
db.SetMaxOpenConns(1)
1394+
db.SetMaxIdleConns(1)
1395+
1396+
// Establish the connection
1397+
require.NoError(t, db.Ping())
1398+
1399+
// Reuse the connection -> should trigger ResetSession -> ShouldPing
1400+
_, err := db.Exec("select 1")
1401+
require.NoError(t, err)
1402+
1403+
require.True(t, hookCalled, "hook should be called on reuse")
1404+
}

0 commit comments

Comments
 (0)