imap: attempt automatic reconnection on error

Attempt to reconnect to the server when there is an unexpected
disconnection or network error.

Use the Client.LoggedOut() channel which is closed when the connection
is closed.

This patch is rather flaky and is certainly bugged. However, it is
a start.

Signed-off-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
Robin Jarry 2021-12-06 23:53:39 +01:00
parent 6f8c167e27
commit c605ada3dd
1 changed files with 41 additions and 3 deletions

View File

@ -56,6 +56,10 @@ type IMAPWorker struct {
worker *types.Worker
// Map of sequence numbers to UIDs, index 0 is seq number 1
seqMap []uint32
// automatic reconnect
loggingOut bool
loggedOut <-chan struct{}
reconnecting bool
}
func NewIMAPWorker(worker *types.Worker) (types.Backend, error) {
@ -69,7 +73,10 @@ func NewIMAPWorker(worker *types.Worker) (types.Backend, error) {
func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
if w.client != nil && w.client.State() == imap.SelectedState {
close(w.idleStop)
if w.idleStop != nil {
close(w.idleStop)
w.idleStop = nil
}
if err := <-w.idleDone; err != nil {
w.worker.PostMessage(&types.Error{Error: err}, nil)
}
@ -155,21 +162,27 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
}
}
case *types.Connect:
if w.client != nil && w.client.State() == imap.SelectedState {
if w.client != nil && w.client.State() == imap.SelectedState && !w.reconnecting {
return fmt.Errorf("Already connected")
}
w.loggingOut = false
c, err := w.connect()
if err != nil {
if !w.reconnecting {
go w.tryReconnect()
}
return err
}
c.Updates = w.updates
w.loggedOut = c.LoggedOut()
w.client = &imapClient{c, sortthread.NewThreadClient(c), sortthread.NewSortClient(c)}
w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
case *types.Disconnect:
w.reconnecting = false
if w.client == nil || w.client.State() != imap.SelectedState {
return fmt.Errorf("Not connected")
}
w.loggingOut = true
if err := w.client.Logout(); err != nil {
return err
}
@ -378,6 +391,25 @@ func (w *IMAPWorker) setKeepaliveParameters(conn *net.TCPConn) error {
return err
}
func (w *IMAPWorker) tryReconnect() {
w.reconnecting = true
for w.reconnecting {
w.worker.Logger.Printf("IMAP reconnection in 2 seconds...\n")
time.Sleep(2 * time.Second)
c := make(chan types.WorkerMessage)
w.worker.PostAction(&types.Connect{}, func(m types.WorkerMessage) {
c <- m
})
result := <-c
switch result.(type) {
case *types.Done:
w.reconnecting = false
case *types.Error:
w.worker.Logger.Printf("IMAP connection failed\n")
}
}
}
func (w *IMAPWorker) Run() {
for {
select {
@ -395,6 +427,12 @@ func (w *IMAPWorker) Run() {
}
case update := <-w.updates:
w.handleImapUpdate(update)
case <-w.loggedOut:
w.loggedOut = nil
if !w.loggingOut && !w.reconnecting {
w.reconnecting = true
go w.tryReconnect()
}
}
}
}