Mitigate Apple Mail re-sync (both bodies and meta info)

This commit is contained in:
Michal Horejsek
2020-12-22 12:06:19 +01:00
parent 5117672388
commit 516ca018d3
24 changed files with 239 additions and 225 deletions

View File

@ -30,6 +30,8 @@ type ChangeNotifier interface {
DeleteMessage(address, mailboxName string, sequenceNumber uint32)
MailboxCreated(address, mailboxName string)
MailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint32)
CanDelete(mailboxID string) (bool, func())
}
// SetChangeNotifier sets notifier to be called once mailbox or message changes.

View File

@ -39,8 +39,6 @@ type eventLoop struct {
stopCh chan struct{}
notifyStopCh chan struct{}
isRunning bool // The whole event loop is running.
isTickerPaused bool // The periodic loop is paused (but the event loop itself is still running).
hasInternet bool
pollCounter int
@ -60,7 +58,6 @@ func newEventLoop(cache *Cache, store *Store, user BridgeUser, events listener.L
currentEventID: cache.getEventID(user.ID()),
pollCh: make(chan chan struct{}),
isRunning: false,
isTickerPaused: false,
log: eventLog,
@ -135,8 +132,6 @@ func (loop *eventLoop) start() {
loop.log.WithField("lastEventID", loop.currentEventID).Warn("Subscription stopped")
}()
loop.hasInternet = true
go loop.pollNow()
loop.loop()
@ -154,10 +149,6 @@ func (loop *eventLoop) loop() {
close(loop.notifyStopCh)
return
case <-t.C:
if loop.isTickerPaused {
loop.log.Trace("Event loop paused, skipping")
continue
}
// Randomise periodic calls within range pollInterval ± pollSpread to reduces potential load spikes on API.
time.Sleep(time.Duration(rand.Intn(2*int(pollIntervalSpread.Milliseconds()))) * time.Millisecond)
case eventProcessedCh = <-loop.pollCh:
@ -220,8 +211,6 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
defer func() {
if errors.Cause(err) == pmapi.ErrAPINotReachable {
l.Warn("Internet unavailable")
loop.events.Emit(bridgeEvents.InternetOffEvent, "")
loop.hasInternet = false
err = nil
}
@ -233,7 +222,6 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
if errors.Cause(err) == pmapi.ErrUpgradeApplication {
l.Warn("Need to upgrade application")
loop.events.Emit(bridgeEvents.UpgradeApplicationEvent, "")
err = nil
}
@ -267,11 +255,6 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
l = l.WithField("newEventID", event.EventID)
if !loop.hasInternet {
loop.events.Emit(bridgeEvents.InternetOnEvent, "")
loop.hasInternet = true
}
if err = loop.processEvent(event); err != nil {
return false, errors.Wrap(err, "failed to process event")
}
@ -464,6 +447,7 @@ func (loop *eventLoop) processMessages(eventLog *logrus.Entry, messages []*pmapi
updateMessage(msgLog, msg, message.Updated)
loop.removeLabelFromMessageWait(message.Updated.LabelIDsRemoved)
if err = loop.store.createOrUpdateMessageEvent(msg); err != nil {
return errors.Wrap(err, "failed to update message in DB")
}
@ -471,6 +455,7 @@ func (loop *eventLoop) processMessages(eventLog *logrus.Entry, messages []*pmapi
case pmapi.EventDelete:
msgLog.Debug("Processing EventDelete for message")
loop.removeMessageWait(message.ID)
if err = loop.store.deleteMessageEvent(message.ID); err != nil {
return errors.Wrap(err, "failed to delete message from DB")
}
@ -480,6 +465,40 @@ func (loop *eventLoop) processMessages(eventLog *logrus.Entry, messages []*pmapi
return err
}
// removeMessageWait waits for notifier to be ready to accept delete
// operations for given message. It's no-op if message does not exist.
func (loop *eventLoop) removeMessageWait(msgID string) {
msg, err := loop.store.getMessageFromDB(msgID)
if err != nil {
return
}
loop.removeLabelFromMessageWait(msg.LabelIDs)
}
// removeLabelFromMessageWait waits for notifier to be ready to accept
// delete operations for given labels.
func (loop *eventLoop) removeLabelFromMessageWait(labelIDs []string) {
if len(labelIDs) == 0 || loop.store.notifier == nil {
return
}
for {
wasWaiting := false
for _, labelID := range labelIDs {
canDelete, wait := loop.store.notifier.CanDelete(labelID)
if !canDelete {
wasWaiting = true
wait()
}
}
// If we had to wait for some label, we need to check again
// all labels in case something changed in the meantime.
if !wasWaiting {
return
}
}
}
func updateMessage(msgLog *logrus.Entry, message *pmapi.Message, updates *pmapi.EventMessageUpdated) { //nolint[funlen]
msgLog.Debug("Updating message")

View File

@ -266,6 +266,21 @@ func (m *MockChangeNotifier) EXPECT() *MockChangeNotifierMockRecorder {
return m.recorder
}
// CanDelete mocks base method
func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CanDelete", arg0)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(func())
return ret0, ret1
}
// CanDelete indicates an expected call of CanDelete
func (mr *MockChangeNotifierMockRecorder) CanDelete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDelete", reflect.TypeOf((*MockChangeNotifier)(nil).CanDelete), arg0)
}
// DeleteMessage mocks base method
func (m *MockChangeNotifier) DeleteMessage(arg0, arg1 string, arg2 uint32) {
m.ctrl.T.Helper()

View File

@ -350,18 +350,6 @@ func (store *Store) addAddress(address, addressID string, labels []*pmapi.Label)
return
}
// PauseEventLoop sets whether the ticker is periodically polling or not.
func (store *Store) PauseEventLoop(pause bool) {
store.lock.Lock()
defer store.lock.Unlock()
store.log.WithField("pause", pause).Info("Pausing event loop")
if store.eventLoop != nil {
store.eventLoop.isTickerPaused = pause
}
}
// Close stops the event loop and closes the database to free the file.
func (store *Store) Close() error {
store.lock.Lock()