Skip to content

Commit 3ea2f57

Browse files
committed
Deprecate CheckConn in favor of Ping
1 parent 26c79eb commit 3ea2f57

File tree

4 files changed

+67
-20
lines changed

4 files changed

+67
-20
lines changed

conn.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -382,11 +382,9 @@ func quoteIdentifier(s string) string {
382382
return `"` + strings.ReplaceAll(s, `"`, `""`) + `"`
383383
}
384384

385-
// Ping executes an empty sql statement against the *Conn
386-
// If the sql returns without error, the database Ping is considered successful, otherwise, the error is returned.
385+
// Ping delegates to the underlying *pgconn.PgConn.Ping.
387386
func (c *Conn) Ping(ctx context.Context) error {
388-
_, err := c.Exec(ctx, ";")
389-
return err
387+
return c.pgConn.Ping(ctx)
390388
}
391389

392390
// PgConn returns the underlying *pgconn.PgConn. This is an escape hatch method that allows lower level access to the

pgconn/pgconn.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,27 +1669,35 @@ func (pgConn *PgConn) EscapeString(s string) (string, error) {
16691669
return strings.Replace(s, "'", "''", -1), nil
16701670
}
16711671

1672-
// CheckConn checks the underlying connection without writing any bytes. This is currently implemented by reading and
1673-
// buffering until the read would block or an error occurs. This can be used to check if the server has closed the
1674-
// connection. If this is done immediately before sending a query it reduces the chances a query will be sent that fails
1672+
// CheckConn checks the underlying connection without writing any bytes. This is currently implemented by doing a read
1673+
// with a very short deadline. This can be useful because a TCP connection can be broken such that a write will appear
1674+
// to succeed even though it will never actually reach the server. Reading immediately before a write will detect this
1675+
// condition. If this is done immediately before sending a query it reduces the chances a query will be sent that fails
16751676
// without the client knowing whether the server received it or not.
1677+
//
1678+
// Deprecated: CheckConn is deprecated in favor of Ping. CheckConn cannot detect all types of broken connections where
1679+
// the write would still appear to succeed. Prefer Ping unless on a high latency connection.
16761680
func (pgConn *PgConn) CheckConn() error {
1677-
// if err := pgConn.lock(); err != nil {
1678-
// return err
1679-
// }
1680-
// defer pgConn.unlock()
1681+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
1682+
defer cancel()
16811683

1682-
rr := pgConn.ExecParams(context.Background(), "select 1", nil, nil, nil, nil)
1683-
_, err := rr.Close()
1684-
return err
1684+
_, err := pgConn.ReceiveMessage(ctx)
1685+
if err != nil {
1686+
if !Timeout(err) {
1687+
return err
1688+
}
1689+
}
16851690

1686-
// err := pgConn.conn.BufferReadUntilBlock()
1687-
// if err != nil && !errors.Is(err, nbconn.ErrWouldBlock) {
1688-
// return err
1689-
// }
16901691
return nil
16911692
}
16921693

1694+
// Ping pings the server. This can be useful because a TCP connection can be broken such that a write will appear to
1695+
// succeed even though it will never actually reach the server. Pinging immediately before sending a query reduces the
1696+
// chances a query will be sent that fails without the client knowing whether the server received it or not.
1697+
func (pgConn *PgConn) Ping(ctx context.Context) error {
1698+
return pgConn.Exec(ctx, "-- ping").Close()
1699+
}
1700+
16931701
// makeCommandTag makes a CommandTag. It does not retain a reference to buf or buf's underlying memory.
16941702
func (pgConn *PgConn) makeCommandTag(buf []byte) CommandTag {
16951703
return CommandTag{s: string(buf)}

pgconn/pgconn_test.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2469,7 +2469,7 @@ func TestConnCheckConn(t *testing.T) {
24692469
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
24702470
defer cancel()
24712471

2472-
// Intentionally using TCP connection for more predictable close behavior. (Not sure if Unix domain sockets would behave subtlely different.)
2472+
// Intentionally using TCP connection for more predictable close behavior. (Not sure if Unix domain sockets would behave subtly different.)
24732473

24742474
connString := os.Getenv("PGX_TEST_TCP_CONN_STRING")
24752475
if connString == "" {
@@ -2501,6 +2501,47 @@ func TestConnCheckConn(t *testing.T) {
25012501
require.Error(t, err)
25022502
}
25032503

2504+
func TestConnPing(t *testing.T) {
2505+
t.Parallel()
2506+
2507+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
2508+
defer cancel()
2509+
2510+
// Intentionally using TCP connection for more predictable close behavior. (Not sure if Unix domain sockets would behave subtly different.)
2511+
2512+
connString := os.Getenv("PGX_TEST_TCP_CONN_STRING")
2513+
if connString == "" {
2514+
t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_TCP_CONN_STRING")
2515+
}
2516+
2517+
c1, err := pgconn.Connect(ctx, connString)
2518+
require.NoError(t, err)
2519+
defer c1.Close(ctx)
2520+
2521+
if c1.ParameterStatus("crdb_version") != "" {
2522+
t.Skip("Server does not support pg_terminate_backend() (https://github.com/cockroachdb/cockroach/issues/35897)")
2523+
}
2524+
2525+
err = c1.Exec(ctx, "set log_statement = 'all'").Close()
2526+
require.NoError(t, err)
2527+
2528+
err = c1.Ping(ctx)
2529+
require.NoError(t, err)
2530+
2531+
c2, err := pgconn.Connect(ctx, connString)
2532+
require.NoError(t, err)
2533+
defer c2.Close(ctx)
2534+
2535+
_, err = c2.Exec(ctx, fmt.Sprintf("select pg_terminate_backend(%d)", c1.PID())).ReadAll()
2536+
require.NoError(t, err)
2537+
2538+
// Give a little time for the signal to actually kill the backend.
2539+
time.Sleep(500 * time.Millisecond)
2540+
2541+
err = c1.Ping(ctx)
2542+
require.Error(t, err)
2543+
}
2544+
25042545
func TestPipelinePrepare(t *testing.T) {
25052546
t.Parallel()
25062547

pgxpool/pool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ func (p *Pool) Acquire(ctx context.Context) (*Conn, error) {
504504
cr := res.Value()
505505

506506
if res.IdleDuration() > time.Second {
507-
err := cr.conn.PgConn().CheckConn()
507+
err := cr.conn.Ping(ctx)
508508
if err != nil {
509509
res.Destroy()
510510
continue

0 commit comments

Comments
 (0)