WaitForNotification detects lost connections quicker
Ping server every 15 seconds while waiting if no traffic has occurred.
This commit is contained in:
@@ -35,6 +35,7 @@ type ConnConfig struct {
|
|||||||
// goroutines.
|
// goroutines.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
conn net.Conn // the underlying TCP or unix domain socket connection
|
conn net.Conn // the underlying TCP or unix domain socket connection
|
||||||
|
lastActivityTime time.Time // the last time the connection was used
|
||||||
reader *bufio.Reader // buffered reader to improve read performance
|
reader *bufio.Reader // buffered reader to improve read performance
|
||||||
wbuf [1024]byte
|
wbuf [1024]byte
|
||||||
Pid int32 // backend pid
|
Pid int32 // backend pid
|
||||||
@@ -150,6 +151,7 @@ func Connect(config ConnConfig) (c *Conn, err error) {
|
|||||||
c.RuntimeParams = make(map[string]string)
|
c.RuntimeParams = make(map[string]string)
|
||||||
c.preparedStatements = make(map[string]*PreparedStatement)
|
c.preparedStatements = make(map[string]*PreparedStatement)
|
||||||
c.alive = true
|
c.alive = true
|
||||||
|
c.lastActivityTime = time.Now()
|
||||||
|
|
||||||
if config.TLSConfig != nil {
|
if config.TLSConfig != nil {
|
||||||
c.logger.Debug("Starting TLS handshake")
|
c.logger.Debug("Starting TLS handshake")
|
||||||
@@ -385,15 +387,57 @@ func (c *Conn) Listen(channel string) (err error) {
|
|||||||
// WaitForNotification waits for a PostgreSQL notification for up to timeout.
|
// WaitForNotification waits for a PostgreSQL notification for up to timeout.
|
||||||
// If the timeout occurs it returns pgx.ErrNotificationTimeout
|
// If the timeout occurs it returns pgx.ErrNotificationTimeout
|
||||||
func (c *Conn) WaitForNotification(timeout time.Duration) (*Notification, error) {
|
func (c *Conn) WaitForNotification(timeout time.Duration) (*Notification, error) {
|
||||||
|
// Return already received notification immediately
|
||||||
if len(c.notifications) > 0 {
|
if len(c.notifications) > 0 {
|
||||||
notification := c.notifications[0]
|
notification := c.notifications[0]
|
||||||
c.notifications = c.notifications[1:]
|
c.notifications = c.notifications[1:]
|
||||||
return notification, nil
|
return notification, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var zeroTime time.Time
|
|
||||||
stopTime := time.Now().Add(timeout)
|
stopTime := time.Now().Add(timeout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if now.After(stopTime) {
|
||||||
|
return nil, ErrNotificationTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there has been no activity on this connection for a while send a nop message just to ensure
|
||||||
|
// the connection is alive
|
||||||
|
nextEnsureAliveTime := c.lastActivityTime.Add(15 * time.Second)
|
||||||
|
if nextEnsureAliveTime.Before(now) {
|
||||||
|
// If the server can't respond to a nop in 15 seconds, assume it's dead
|
||||||
|
err := c.conn.SetReadDeadline(now.Add(15 * time.Second))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Exec("--;")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lastActivityTime = now
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadline time.Time
|
||||||
|
if stopTime.Before(nextEnsureAliveTime) {
|
||||||
|
deadline = stopTime
|
||||||
|
} else {
|
||||||
|
deadline = nextEnsureAliveTime
|
||||||
|
}
|
||||||
|
|
||||||
|
notification, err := c.waitForNotification(deadline)
|
||||||
|
if err != ErrNotificationTimeout {
|
||||||
|
return notification, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) waitForNotification(deadline time.Time) (*Notification, error) {
|
||||||
|
var zeroTime time.Time
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Use SetReadDeadline to implement the timeout. SetReadDeadline will
|
// Use SetReadDeadline to implement the timeout. SetReadDeadline will
|
||||||
// cause operations to fail with a *net.OpError that has a Timeout()
|
// cause operations to fail with a *net.OpError that has a Timeout()
|
||||||
@@ -403,7 +447,7 @@ func (c *Conn) WaitForNotification(timeout time.Duration) (*Notification, error)
|
|||||||
// deadline and peek into the reader. If a timeout error occurs there
|
// deadline and peek into the reader. If a timeout error occurs there
|
||||||
// we don't break the pgx connection. If the Peek returns that data
|
// we don't break the pgx connection. If the Peek returns that data
|
||||||
// is available then we turn off the read deadline before the rxMsg.
|
// is available then we turn off the read deadline before the rxMsg.
|
||||||
err := c.conn.SetReadDeadline(stopTime)
|
err := c.conn.SetReadDeadline(deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -594,6 +638,7 @@ func (c *Conn) sendPreparedQuery(ps *PreparedStatement, arguments ...interface{}
|
|||||||
// arguments should be referenced positionally from the sql string as $1, $2, etc.
|
// arguments should be referenced positionally from the sql string as $1, $2, etc.
|
||||||
func (c *Conn) Exec(sql string, arguments ...interface{}) (commandTag CommandTag, err error) {
|
func (c *Conn) Exec(sql string, arguments ...interface{}) (commandTag CommandTag, err error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
c.lastActivityTime = startTime
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -667,6 +712,8 @@ func (c *Conn) rxMsg() (t byte, r *msgReader, err error) {
|
|||||||
c.die(err)
|
c.die(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.lastActivityTime = time.Now()
|
||||||
|
|
||||||
return t, &c.mr, err
|
return t, &c.mr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -357,7 +357,8 @@ func (rows *Rows) Values() ([]interface{}, error) {
|
|||||||
// be returned in an error state. So it is allowed to ignore the error returned
|
// be returned in an error state. So it is allowed to ignore the error returned
|
||||||
// from Query and handle it in *Rows.
|
// from Query and handle it in *Rows.
|
||||||
func (c *Conn) Query(sql string, args ...interface{}) (*Rows, error) {
|
func (c *Conn) Query(sql string, args ...interface{}) (*Rows, error) {
|
||||||
rows := &Rows{conn: c, startTime: time.Now(), sql: sql, args: args, logger: c.logger}
|
c.lastActivityTime = time.Now()
|
||||||
|
rows := &Rows{conn: c, startTime: c.lastActivityTime, sql: sql, args: args, logger: c.logger}
|
||||||
|
|
||||||
ps, ok := c.preparedStatements[sql]
|
ps, ok := c.preparedStatements[sql]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user