report read limit violations on EventDisconnected via ErrReadLimit sentinel
This commit is contained in:
+8
-5
@@ -19,6 +19,7 @@ import (
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
var ErrConnectionUnavailable = errors.New("connection unavailable")
|
var ErrConnectionUnavailable = errors.New("connection unavailable")
|
||||||
|
var ErrReadLimit = transport.ErrReadLimit
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Config
|
// Config
|
||||||
@@ -249,6 +250,8 @@ func (w *session) serve(
|
|||||||
stopKeepalive, inactive, heartbeat := w.startKeepalive()
|
stopKeepalive, inactive, heartbeat := w.startKeepalive()
|
||||||
|
|
||||||
// run session loop
|
// run session loop
|
||||||
|
var disconnectErr error
|
||||||
|
|
||||||
session:
|
session:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -257,14 +260,13 @@ session:
|
|||||||
|
|
||||||
case data, ok := <-conn.Incoming():
|
case data, ok := <-conn.Incoming():
|
||||||
if !ok {
|
if !ok {
|
||||||
var reason error
|
|
||||||
select {
|
select {
|
||||||
case reason = <-conn.Errors():
|
case disconnectErr = <-conn.Errors():
|
||||||
default:
|
default:
|
||||||
reason = fmt.Errorf("unknown")
|
disconnectErr = fmt.Errorf("unknown")
|
||||||
}
|
}
|
||||||
if w.logger != nil {
|
if w.logger != nil {
|
||||||
w.logger.Info("websocket: closed", "reason", reason)
|
w.logger.Info("websocket: closed", "reason", disconnectErr)
|
||||||
}
|
}
|
||||||
break session
|
break session
|
||||||
}
|
}
|
||||||
@@ -297,7 +299,8 @@ session:
|
|||||||
|
|
||||||
// tear down connection
|
// tear down connection
|
||||||
w.conn.Store(nil)
|
w.conn.Store(nil)
|
||||||
pool.Events <- PoolEvent{URL: w.url, Kind: EventDisconnected, At: time.Now()}
|
pool.Events <- PoolEvent{
|
||||||
|
URL: w.url, Kind: EventDisconnected, Err: disconnectErr, At: time.Now()}
|
||||||
|
|
||||||
if w.logger != nil {
|
if w.logger != nil {
|
||||||
w.logger.Info("disconnected")
|
w.logger.Info("disconnected")
|
||||||
|
|||||||
@@ -80,6 +80,37 @@ func TestSession(t *testing.T) {
|
|||||||
}, "expected EventConnected")
|
}, "expected EventConnected")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("EventDisconnected carries ErrReadLimit when read limit exceeded", func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
mockSocket := honeybeetest.NewMockSocket()
|
||||||
|
mockSocket.ReadMessageFunc = func() (int, []byte, error) {
|
||||||
|
return 0, nil, websocket.ErrReadLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
config, _ := NewSessionConfig(
|
||||||
|
WithRetryDisabled(), WithReconnectDelay(0))
|
||||||
|
w := makeSession(t, mockSocket, config, ctx, cancel)
|
||||||
|
_, events, pool := makeSessionContext(t)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Go(func() {
|
||||||
|
w.Start(pool)
|
||||||
|
})
|
||||||
|
|
||||||
|
honeybeetest.Eventually(t, func() bool {
|
||||||
|
select {
|
||||||
|
case e := <-events:
|
||||||
|
return e.Kind == EventDisconnected &&
|
||||||
|
e.Err != nil &&
|
||||||
|
errors.Is(e.Err, ErrReadLimit)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, "expected EventDisconnected with Err matching ErrReadLimit")
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("dial failure exhausted - session exits cleanly, no connected/disconnected events", func(t *testing.T) {
|
t.Run("dial failure exhausted - session exits cleanly, no connected/disconnected events", func(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var (
|
|||||||
ErrPeerClosedClean = errors.New("peer closed connection cleanly")
|
ErrPeerClosedClean = errors.New("peer closed connection cleanly")
|
||||||
ErrPeerClosedUnexpected = errors.New("peer closed connection unexpectedly")
|
ErrPeerClosedUnexpected = errors.New("peer closed connection unexpectedly")
|
||||||
ErrReadError = errors.New("read error")
|
ErrReadError = errors.New("read error")
|
||||||
|
ErrReadLimit = errors.New("read limit exceeded")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@@ -350,7 +351,12 @@ func (c *Connection) classifyCloseError(err error) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
classifiedError = fmt.Errorf("%w: %w", ErrReadError, err)
|
if errors.Is(err, websocket.ErrReadLimit) {
|
||||||
|
classifiedError = fmt.Errorf("%w: %w: %v",
|
||||||
|
ErrReadError, ErrReadLimit, err)
|
||||||
|
} else {
|
||||||
|
classifiedError = fmt.Errorf("%w: %w", ErrReadError, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return classifiedError
|
return classifiedError
|
||||||
|
|||||||
@@ -225,6 +225,51 @@ func TestConnection_Errors(t *testing.T) {
|
|||||||
}, "expected read error")
|
}, "expected read error")
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("read limit exceeded", func(t *testing.T) {
|
||||||
|
mockSocket := honeybeetest.NewMockSocket()
|
||||||
|
mockSocket.ReadMessageFunc = func() (int, []byte, error) {
|
||||||
|
return 0, nil, websocket.ErrReadLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := NewConnection(context.Background(), mockSocket, nil, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
honeybeetest.Eventually(t, func() bool {
|
||||||
|
select {
|
||||||
|
case err := <-conn.Errors():
|
||||||
|
return errors.Is(err, ErrReadLimit) &&
|
||||||
|
errors.Is(err, ErrReadError) &&
|
||||||
|
!errors.Is(err, websocket.ErrReadLimit)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, "expected read limit error classified as ErrReadLimit")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("generic read error is not a read limit error", func(t *testing.T) {
|
||||||
|
mockSocket := honeybeetest.NewMockSocket()
|
||||||
|
mockSocket.ReadMessageFunc = func() (int, []byte, error) {
|
||||||
|
return 0, nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := NewConnection(context.Background(), mockSocket, nil, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
honeybeetest.Eventually(t, func() bool {
|
||||||
|
select {
|
||||||
|
case err := <-conn.Errors():
|
||||||
|
return errors.Is(err, ErrReadError) &&
|
||||||
|
!errors.Is(err, ErrReadLimit)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, "expected generic read error without ErrReadLimit")
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnection_Heartbeat(t *testing.T) {
|
func TestConnection_Heartbeat(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user