Do not reconnect on "connection replaced" stream errors

Fix #45
This commit is contained in:
Mickael Remond 2019-06-08 11:15:51 +02:00
parent 3689448c90
commit b7461ae97f
No known key found for this signature in database
GPG key ID: E6F6045D79965AA3
3 changed files with 27 additions and 4 deletions

View file

@ -21,6 +21,7 @@ const (
StateDisconnected ConnState = iota StateDisconnected ConnState = iota
StateConnected StateConnected
StateSessionEstablished StateSessionEstablished
StateStreamError
) )
// Event is a structure use to convey event changes related to client state. This // Event is a structure use to convey event changes related to client state. This
@ -28,6 +29,7 @@ const (
type Event struct { type Event struct {
State ConnState State ConnState
Description string Description string
StreamError string
} }
// EventHandler is use to pass events about state of the connection to // EventHandler is use to pass events about state of the connection to
@ -49,6 +51,13 @@ func (em EventManager) updateState(state ConnState) {
} }
} }
func (em EventManager) streamError(error, desc string) {
em.CurrentState = StateStreamError
if em.Handler != nil {
em.Handler(Event{State: em.CurrentState, StreamError: error, Description: desc})
}
}
// Client // Client
// ============================================================================ // ============================================================================
@ -164,8 +173,17 @@ func (c *Client) recv() (err error) {
c.updateState(StateDisconnected) c.updateState(StateDisconnected)
return err return err
} }
// Handle stream errors
switch packet := val.(type) {
case StreamError:
c.RecvChannel <- val
close(c.RecvChannel)
c.streamError(packet.Error.Local, packet.Text)
return errors.New("stream error: " + packet.Error.Local)
}
c.RecvChannel <- val c.RecvChannel <- val
val = nil
} }
} }

View file

@ -40,6 +40,12 @@ func (cm *ClientManager) Start() error {
case StateDisconnected: case StateDisconnected:
// Reconnect on disconnection // Reconnect on disconnection
cm.connect() cm.connect()
case StateStreamError:
cm.Client.Disconnect()
// Only try reconnecting if we have not been kicked by another session to avoid connection loop.
if e.StreamError != "conflict" {
cm.connect()
}
} }
} }
@ -62,8 +68,6 @@ func (cm *ClientManager) connect() error {
// TODO: Make it possible to define logger to log disconnect and reconnection attempts // TODO: Make it possible to define logger to log disconnect and reconnection attempts
cm.Metrics = initMetrics() cm.Metrics = initMetrics()
// TODO: Test for non recoverable errors (invalid username and password) and return an error
// to start caller. We do not want to retry on non recoverable errors.
if cm.Client.Session, err = cm.Client.Connect(); err != nil { if cm.Client.Session, err = cm.Client.Connect(); err != nil {
var actualErr ConnError var actualErr ConnError
if xerrors.As(err, &actualErr) { if xerrors.As(err, &actualErr) {
@ -72,7 +76,7 @@ func (cm *ClientManager) connect() error {
} }
} }
backoff.Wait() backoff.Wait()
} else { } else { // We are connected, we can leave the retry loop
break break
} }
} }

View file

@ -37,6 +37,7 @@ func (streamFeatureDecoder) decode(p *xml.Decoder, se xml.StartElement) (StreamF
type StreamError struct { type StreamError struct {
XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
Error xml.Name `xml:",any"` Error xml.Name `xml:",any"`
Text string `xml:"urn:ietf:params:xml:ns:xmpp-streams text"`
} }
func (StreamError) Name() string { func (StreamError) Name() string {