imap: reconnect with exponential backoff

waits an increasing amount of time before attempting a reconnect.
Wait is capped at 16s. Prevents many reconnect attemps in a short time period.

Fixes commit 05ad96a30c ("imap: improve reconnect stability") that
improved the reliability of the reconnect mechanism but did not
implement controls to prevent the triggering of too many reconnects
within a short period of time.

Fixes: 05ad96a30c ("imap: improve reconnect stability")
Signed-off-by: Koni Marti <koni.marti@gmail.com>
This commit is contained in:
Koni Marti 2022-02-12 23:08:18 +01:00 committed by Robin Jarry
parent bb0f180140
commit 11a4d5b71c

View file

@ -3,6 +3,7 @@ package imap
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"math"
"net" "net"
"net/url" "net/url"
"strconv" "strconv"
@ -12,6 +13,7 @@ import (
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
sortthread "github.com/emersion/go-imap-sortthread" sortthread "github.com/emersion/go-imap-sortthread"
"github.com/emersion/go-imap/client" "github.com/emersion/go-imap/client"
"github.com/pkg/errors"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/lib"
@ -58,6 +60,7 @@ type IMAPWorker struct {
seqMap []uint32 seqMap []uint32
done chan struct{} done chan struct{}
autoReconnect bool autoReconnect bool
retries int
} }
func NewIMAPWorker(worker *types.Worker) (types.Backend, error) { func NewIMAPWorker(worker *types.Worker) (types.Backend, error) {
@ -85,7 +88,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
} }
}() }()
checkConn := func() { checkConn := func(wait time.Duration) {
time.Sleep(wait)
w.stopConnectionObserver() w.stopConnectionObserver()
w.startConnectionObserver() w.startConnectionObserver()
} }
@ -178,7 +182,7 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
if w.client != nil && w.client.State() == imap.SelectedState { if w.client != nil && w.client.State() == imap.SelectedState {
if !w.autoReconnect { if !w.autoReconnect {
w.autoReconnect = true w.autoReconnect = true
checkConn() checkConn(0)
} }
reterr = fmt.Errorf("Already connected") reterr = fmt.Errorf("Already connected")
break break
@ -206,8 +210,10 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
} }
c, err := w.connect() c, err := w.connect()
if err != nil { if err != nil {
checkConn() wait, msg := w.exponentialBackoff()
reterr = err go checkConn(wait)
w.retries++
reterr = errors.Wrap(err, msg)
break break
} }
@ -312,6 +318,22 @@ func (w *IMAPWorker) handleImapUpdate(update client.Update) {
} }
} }
func (w *IMAPWorker) exponentialBackoff() (time.Duration, string) {
maxWait := 16
if w.retries > 0 {
backoff := int(math.Pow(2.0, float64(w.retries)))
if backoff > maxWait {
backoff = maxWait
}
waitStr := fmt.Sprintf("%ds", backoff)
wait, err := time.ParseDuration(waitStr)
if err == nil {
return wait, fmt.Sprintf("wait %s before reconnect", waitStr)
}
}
return 0 * time.Second, ""
}
func (w *IMAPWorker) startConnectionObserver() { func (w *IMAPWorker) startConnectionObserver() {
go func() { go func() {
select { select {
@ -413,6 +435,8 @@ func (w *IMAPWorker) connect() (*client.Client, error) {
return nil, err return nil, err
} }
w.retries = 0
return c, nil return c, nil
} }