Handling basic unrecoverable errors

Fix #43
This commit is contained in:
Mickael Remond 2019-06-07 15:23:23 +02:00
parent d45dd6a44a
commit 2d8d4516fd
No known key found for this signature in database
GPG key ID: E6F6045D79965AA3
8 changed files with 65 additions and 18 deletions

View file

@ -18,7 +18,8 @@ func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f StreamFeatures, user
} }
} }
if !havePlain { if !havePlain {
return fmt.Errorf("PLAIN authentication is not supported by server: %v", f.Mechanisms.Mechanism) err := fmt.Errorf("PLAIN authentication is not supported by server: %v", f.Mechanisms.Mechanism)
return NewConnError(err, true)
} }
return authPlain(socket, decoder, user, password) return authPlain(socket, decoder, user, password)
@ -41,7 +42,8 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
case SASLSuccess: case SASLSuccess:
case SASLFailure: case SASLFailure:
// v.Any is type of sub-element in failure, which gives a description of what failed. // v.Any is type of sub-element in failure, which gives a description of what failed.
return errors.New("auth failure: " + v.Any.Local) err := errors.New("auth failure: " + v.Any.Local)
return NewConnError(err, true)
default: default:
return errors.New("expected SASL success or failure, got " + v.Name()) return errors.New("expected SASL success or failure, got " + v.Name())
} }

View file

@ -78,17 +78,18 @@ Setting up the client / Checking the parameters
func NewClient(config Config) (c *Client, err error) { func NewClient(config Config) (c *Client, err error) {
// TODO: If option address is nil, use the Jid domain to compose the address // TODO: If option address is nil, use the Jid domain to compose the address
if config.Address, err = checkAddress(config.Address); err != nil { if config.Address, err = checkAddress(config.Address); err != nil {
return return nil, NewConnError(err, true)
} }
if config.Password == "" { if config.Password == "" {
err = errors.New("missing password") err = errors.New("missing password")
return return nil, NewConnError(err, true)
} }
// Parse JID // Parse JID
if config.parsedJid, err = NewJid(c.config.Jid); err != nil { if config.parsedJid, err = NewJid(config.Jid); err != nil {
return err = errors.New("missing jid")
return nil, NewConnError(err, true)
} }
c = new(Client) c = new(Client)

View file

@ -1,8 +1,9 @@
package xmpp // import "gosrc.io/xmpp" package xmpp // import "gosrc.io/xmpp"
import ( import (
"log"
"time" "time"
"golang.org/x/xerrors"
) )
type PostConnect func(c *Client) type PostConnect func(c *Client)
@ -29,7 +30,7 @@ func NewClientManager(client *Client, pc PostConnect) *ClientManager {
} }
// Start launch the connection loop // Start launch the connection loop
func (cm *ClientManager) Start() { func (cm *ClientManager) Start() error {
cm.Client.Handler = func(e Event) { cm.Client.Handler = func(e Event) {
switch e.State { switch e.State {
case StateConnected: case StateConnected:
@ -41,7 +42,8 @@ func (cm *ClientManager) Start() {
cm.connect() cm.connect()
} }
} }
cm.connect()
return cm.connect()
} }
// Stop cancels pending operations and terminates existing XMPP client. // Stop cancels pending operations and terminates existing XMPP client.
@ -52,17 +54,23 @@ func (cm *ClientManager) Stop() {
} }
// connect manages the reconnection loop and apply the define backoff to avoid overloading the server. // connect manages the reconnection loop and apply the define backoff to avoid overloading the server.
func (cm *ClientManager) connect() { func (cm *ClientManager) connect() error {
var backoff Backoff // TODO: Group backoff calculation features with connection manager? var backoff Backoff // TODO: Group backoff calculation features with connection manager?
for { for {
var err error var err error
// 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 // 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. // 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 {
log.Printf("Connection error: %v\n", err) var actualErr ConnError
if xerrors.As(err, &actualErr) {
if actualErr.Permanent {
return xerrors.Errorf("unrecoverable connect error %w", actualErr)
}
}
backoff.Wait() backoff.Wait()
} else { } else {
break break
@ -72,6 +80,7 @@ func (cm *ClientManager) connect() {
if cm.PostConnect != nil { if cm.PostConnect != nil {
cm.PostConnect(cm.Client) cm.PostConnect(cm.Client)
} }
return nil
} }
// Client Metrics // Client Metrics

View file

@ -23,13 +23,16 @@ func main() {
client, err := xmpp.NewClient(config) client, err := xmpp.NewClient(config)
if err != nil { if err != nil {
log.Fatal("Error: ", err) log.Fatalf("%+v", err)
} }
cm := xmpp.NewClientManager(client, nil) cm := xmpp.NewClientManager(client, nil)
cm.Start() err = cm.Start()
// connection can be stopped with cm.Stop() // connection can be stopped with cm.Stop()
// connection state can be checked by reading cm.Client.CurrentState // connection state can be checked by reading cm.Client.CurrentState
if err != nil {
log.Fatal(err)
}
// Iterator to receive packets coming from our XMPP connection // Iterator to receive packets coming from our XMPP connection
for packet := range client.Recv() { for packet := range client.Recv() {

33
conn_error.go Normal file
View file

@ -0,0 +1,33 @@
package xmpp // import "gosrc.io/xmpp"
import (
"fmt"
"golang.org/x/xerrors"
)
type ConnError struct {
frame xerrors.Frame
err error
// Permanent will be true if error is not recoverable
Permanent bool
}
func NewConnError(err error, permanent bool) ConnError {
return ConnError{err: err, frame: xerrors.Caller(1), Permanent: permanent}
}
func (e ConnError) Format(s fmt.State, verb rune) {
xerrors.FormatError(e, s, verb)
}
func (e ConnError) FormatError(p xerrors.Printer) error {
e.frame.Format(p)
return e.err
}
func (e ConnError) Error() string {
return fmt.Sprint(e)
}
func (e ConnError) Unwrap() error { return e.err }

1
go.mod
View file

@ -6,4 +6,5 @@ require (
github.com/google/go-cmp v0.2.0 github.com/google/go-cmp v0.2.0
github.com/processone/mpg123 v1.0.0 github.com/processone/mpg123 v1.0.0
github.com/processone/soundcloud v1.0.0 github.com/processone/soundcloud v1.0.0
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522
) )

6
go.sum
View file

@ -1,12 +1,10 @@
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/processone/mpg123 v0.0.0-20160212185547-a17074c7ddc0 h1:7Sb9wi06laajixrlpOJACEtAsnbDYlH8/bRTfLKAiF4=
github.com/processone/mpg123 v0.0.0-20160212185547-a17074c7ddc0/go.mod h1:e9Ud3cx6fZEPqmEYDqjIsRvgFRPvXWhb0H1Cmu+LUtk=
github.com/processone/mpg123 v1.0.0 h1:o2WOyGZRM255or1Zc/LtF/jARn51B+9aQl72Qace0GA= github.com/processone/mpg123 v1.0.0 h1:o2WOyGZRM255or1Zc/LtF/jARn51B+9aQl72Qace0GA=
github.com/processone/mpg123 v1.0.0/go.mod h1:X/FeL+h8vD1bYsG9tIWV3M2c4qNTZOficyvPVBP08go= github.com/processone/mpg123 v1.0.0/go.mod h1:X/FeL+h8vD1bYsG9tIWV3M2c4qNTZOficyvPVBP08go=
github.com/processone/soundcloud v0.0.0-20160217145628-430ee371ebce h1:lDqQi4xGLeS5mZYpVeyle5num0mWdyy37QmsQiFSX1Y=
github.com/processone/soundcloud v0.0.0-20160217145628-430ee371ebce/go.mod h1:QfHw5V3JsrdJoYH4aD2+lr18v97eA6wyHaV0Zi+FuOI=
github.com/processone/soundcloud v1.0.0 h1:/+i6+Yveb7Y6IFGDSkesYI+HddblzcRTQClazzVHxoE= github.com/processone/soundcloud v1.0.0 h1:/+i6+Yveb7Y6IFGDSkesYI+HddblzcRTQClazzVHxoE=
github.com/processone/soundcloud v1.0.0/go.mod h1:kDLeWpkRtN3C8kIReQdxoiRi92P9xR6yW6qLOJnNWfY= github.com/processone/soundcloud v1.0.0/go.mod h1:kDLeWpkRtN3C8kIReQdxoiRi92P9xR6yW6qLOJnNWfY=
golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM= golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM=
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522 h1:bhOzK9QyoD0ogCnFro1m2mz41+Ib0oOhfJnBp5MR4K4=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -43,7 +43,7 @@ func NewSession(conn net.Conn, o Config) (net.Conn, *Session, error) {
} }
if !s.TlsEnabled && !o.Insecure { if !s.TlsEnabled && !o.Insecure {
return nil, nil, errors.New("failed to negotiate TLS") return nil, nil, NewConnError(errors.New("failed to negotiate TLS session"), true)
} }
// auth // auth