Fix WaitForNotification when it times out
This commit is contained in:
+46
-18
@@ -91,6 +91,8 @@ func (e ProtocolError) Error() string {
|
|||||||
return string(e)
|
return string(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var NotificationTimeoutError = errors.New("Notification Timeout")
|
||||||
|
|
||||||
// Connect establishes a connection with a PostgreSQL server using parameters. One
|
// Connect establishes a connection with a PostgreSQL server using parameters. One
|
||||||
// of parameters.Socket or parameters.Host must be specified. parameters.User
|
// of parameters.Socket or parameters.Host must be specified. parameters.User
|
||||||
// will default to the OS user name. Other parameters fields are optional.
|
// will default to the OS user name. Other parameters fields are optional.
|
||||||
@@ -542,35 +544,61 @@ func (c *Connection) Listen(channel string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForNotification waits for a PostgreSQL notification for up to timeout
|
// WaitForNotification waits for a PostgreSQL notification for up to timeout.
|
||||||
func (c *Connection) WaitForNotification(timeout time.Duration) (notification *Notification, err error) {
|
// If the timeout occurs it returns pgx.NotificationTimeoutError
|
||||||
err = c.conn.SetReadDeadline(time.Now().Add(timeout))
|
func (c *Connection) WaitForNotification(timeout time.Duration) (*Notification, error) {
|
||||||
if err != nil {
|
if len(c.notifications) > 0 {
|
||||||
return
|
notification := c.notifications[0]
|
||||||
|
c.notifications = c.notifications[1:]
|
||||||
|
return notification, nil
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
var zeroTime time.Time
|
var zeroTime time.Time
|
||||||
e := c.conn.SetReadDeadline(zeroTime)
|
stopTime := time.Now().Add(timeout)
|
||||||
if err == nil && e != nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if len(c.notifications) > 0 {
|
// Use SetReadDeadline to implement the timeout. SetReadDeadline will
|
||||||
notification = c.notifications[0]
|
// cause operations to fail with a *net.OpError that has a Timeout()
|
||||||
c.notifications = c.notifications[1:]
|
// of true. Because the normal pgx rxMsg path considers any error to
|
||||||
return
|
// have potentially corrupted the state of the connection, it dies
|
||||||
|
// on any errors. So to avoid timeout errors in rxMsg we set the
|
||||||
|
// 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
|
||||||
|
// is available then we turn off the read deadline before the rxMsg.
|
||||||
|
err := c.conn.SetReadDeadline(stopTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until there is a byte available before continuing onto the normal msg reading path
|
||||||
|
_, err = c.reader.Peek(1)
|
||||||
|
if err != nil {
|
||||||
|
c.conn.SetReadDeadline(zeroTime) // we can only return one error and we already have one -- so ignore possiple error from SetReadDeadline
|
||||||
|
if err, ok := err.(*net.OpError); ok && err.Timeout() {
|
||||||
|
return nil, NotificationTimeoutError
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.conn.SetReadDeadline(zeroTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var t byte
|
var t byte
|
||||||
var r *MessageReader
|
var r *MessageReader
|
||||||
if t, r, err = c.rxMsg(); err == nil {
|
if t, r, err = c.rxMsg(); err == nil {
|
||||||
if err = c.processContextFreeMsg(t, r); err != nil {
|
if err = c.processContextFreeMsg(t, r); err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.notifications) > 0 {
|
||||||
|
notification := c.notifications[0]
|
||||||
|
c.notifications = c.notifications[1:]
|
||||||
|
return notification, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/JackC/pgx"
|
"github.com/JackC/pgx"
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -702,12 +701,22 @@ func TestListenNotify(t *testing.T) {
|
|||||||
|
|
||||||
// when timeout occurs
|
// when timeout occurs
|
||||||
notification, err = listener.WaitForNotification(time.Millisecond)
|
notification, err = listener.WaitForNotification(time.Millisecond)
|
||||||
if _, ok := err.(*net.OpError); !ok {
|
if err != pgx.NotificationTimeoutError {
|
||||||
t.Errorf("WaitForNotification returned the wrong kind of error: %v", err)
|
t.Errorf("WaitForNotification returned the wrong kind of error: %v", err)
|
||||||
}
|
}
|
||||||
if notification != nil {
|
if notification != nil {
|
||||||
t.Errorf("WaitForNotification returned an unexpected notification: %v", notification)
|
t.Errorf("WaitForNotification returned an unexpected notification: %v", notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listener can listen again after a timeout
|
||||||
|
mustExecute(t, notifier, "notify chat")
|
||||||
|
notification, err = listener.WaitForNotification(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error on WaitForNotification: %v", err)
|
||||||
|
}
|
||||||
|
if notification.Channel != "chat" {
|
||||||
|
t.Errorf("Did not receive notification on expected channel: %v", notification.Channel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFatalRxError(t *testing.T) {
|
func TestFatalRxError(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user