Skip to content

Commit c1c74da

Browse files
feat(pgxpool): acquire ping timeout
this commit adds ping timeout in acquire loop for when the connection ping results in timeout instead of an error
1 parent 61d3c96 commit c1c74da

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

pgxpool/pool.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ type Pool struct {
9797
maxConnLifetimeJitter time.Duration
9898
maxConnIdleTime time.Duration
9999
healthCheckPeriod time.Duration
100+
pingTimeout time.Duration
100101

101102
healthCheckChan chan struct{}
102103

@@ -166,6 +167,10 @@ type Config struct {
166167
// MaxConnIdleTime is the duration after which an idle connection will be automatically closed by the health check.
167168
MaxConnIdleTime time.Duration
168169

170+
// PingTimeout is the maximum amount of time to wait for a connection to pong before considering it as unhealthy and
171+
// destroying it. If zero, the default is no timeout.
172+
PingTimeout time.Duration
173+
169174
// MaxConns is the maximum size of the pool. The default is the greater of 4 or runtime.NumCPU().
170175
MaxConns int32
171176

@@ -238,6 +243,7 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) {
238243
maxConnLifetime: config.MaxConnLifetime,
239244
maxConnLifetimeJitter: config.MaxConnLifetimeJitter,
240245
maxConnIdleTime: config.MaxConnIdleTime,
246+
pingTimeout: config.PingTimeout,
241247
healthCheckPeriod: config.HealthCheckPeriod,
242248
healthCheckChan: make(chan struct{}, 1),
243249
closeChan: make(chan struct{}),
@@ -601,7 +607,14 @@ func (p *Pool) Acquire(ctx context.Context) (c *Conn, err error) {
601607

602608
shouldPingParams := ShouldPingParams{Conn: cr.conn, IdleDuration: res.IdleDuration()}
603609
if p.shouldPing(ctx, shouldPingParams) {
604-
err := cr.conn.Ping(ctx)
610+
pingCtx := ctx
611+
if p.pingTimeout > 0 {
612+
var cancel context.CancelFunc
613+
pingCtx, cancel = context.WithTimeout(ctx, p.pingTimeout)
614+
defer cancel()
615+
}
616+
617+
err := cr.conn.Ping(pingCtx)
605618
if err != nil {
606619
res.Destroy()
607620
continue

pgxpool/pool_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,3 +1281,54 @@ func TestPoolSendBatchBatchCloseTwice(t *testing.T) {
12811281
assert.NoError(t, err)
12821282
}
12831283
}
1284+
1285+
func TestPoolAcquirePingTimeout(t *testing.T) {
1286+
t.Parallel()
1287+
1288+
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
1289+
defer cancel()
1290+
1291+
config, err := pgxpool.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
1292+
require.NoError(t, err)
1293+
1294+
// Set a very short ping timeout to force timeout during ping and destruction of the connection
1295+
config.PingTimeout = 1 * time.Nanosecond
1296+
1297+
var conID *uint32
1298+
// Only ping the connection with the original PID to force creation of a new connection
1299+
config.ShouldPing = func(_ context.Context, params pgxpool.ShouldPingParams) bool {
1300+
if conID != nil && params.Conn.PgConn().PID() == *conID {
1301+
return true
1302+
}
1303+
1304+
return false
1305+
}
1306+
// Limit to a single connection to ensure the same connection is reused
1307+
config.MinConns = 1
1308+
config.MaxConns = 1
1309+
1310+
pool, err := pgxpool.NewWithConfig(ctx, config)
1311+
require.NoError(t, err)
1312+
defer pool.Close()
1313+
1314+
c, err := pool.Acquire(ctx)
1315+
require.NoError(t, err)
1316+
require.EqualValues(t, 1, pool.Stat().TotalConns())
1317+
originalPID := c.Conn().PgConn().PID()
1318+
conID = &originalPID
1319+
1320+
c.Release()
1321+
require.EqualValues(t, 1, pool.Stat().TotalConns())
1322+
1323+
c, err = pool.Acquire(ctx)
1324+
require.NoError(t, err)
1325+
require.EqualValues(t, 1, pool.Stat().TotalConns())
1326+
newPID := c.Conn().PgConn().PID()
1327+
1328+
c.Release()
1329+
1330+
require.EqualValues(t, 1, pool.Stat().TotalConns())
1331+
assert.Nil(t, ctx.Err())
1332+
assert.NotEqualValues(t, originalPID, newPID,
1333+
"Expected new connection due to ping timeout, but got same connection")
1334+
}

0 commit comments

Comments
 (0)