fix(GODT-2626): Handle rare crash due to missing address update ch

Ensure that we can handle the rare case that can cause a crash if for
whichever reason we end up with an Address Delete and Message
Create/Update in the same event object.
This commit is contained in:
Leander Beernaert
2023-05-16 10:41:36 +02:00
parent 4e3ad4f7fa
commit d8ccc6c05d
2 changed files with 76 additions and 5 deletions

View File

@ -217,7 +217,7 @@ func (user *User) handleCreateAddressEvent(ctx context.Context, event proton.Add
// If the address is enabled, we need to hook it up to the update channels.
switch user.vault.AddressMode() {
case vault.CombinedMode:
primAddr, err := getAddrIdx(user.apiAddrs, 0)
primAddr, err := getPrimaryAddr(user.apiAddrs)
if err != nil {
return fmt.Errorf("failed to get primary address: %w", err)
}
@ -276,7 +276,7 @@ func (user *User) handleUpdateAddressEvent(_ context.Context, event proton.Addre
case oldAddr.Status != proton.AddressStatusEnabled && event.Address.Status == proton.AddressStatusEnabled:
switch user.vault.AddressMode() {
case vault.CombinedMode:
primAddr, err := getAddrIdx(user.apiAddrs, 0)
primAddr, err := getPrimaryAddr(user.apiAddrs)
if err != nil {
return fmt.Errorf("failed to get primary address: %w", err)
}
@ -628,7 +628,14 @@ func (user *User) handleCreateMessageEvent(ctx context.Context, message proton.M
}
update = imap.NewMessagesCreated(false, res.update)
user.updateCh[full.AddressID].Enqueue(update)
didPublish, err := safePublishMessageUpdate(user, full.AddressID, update)
if err != nil {
return err
}
if !didPublish {
update = nil
}
return nil
}); err != nil {
@ -674,7 +681,14 @@ func (user *User) handleUpdateMessageEvent(_ context.Context, message proton.Mes
flags,
)
user.updateCh[message.AddressID].Enqueue(update)
didPublish, err := safePublishMessageUpdate(user, message.AddressID, update)
if err != nil {
return nil, err
}
if !didPublish {
return nil, nil
}
return []imap.Update{update}, nil
}, user.apiLabelsLock, user.updateChLock)
@ -743,13 +757,24 @@ func (user *User) handleUpdateDraftEvent(ctx context.Context, event proton.Messa
true, // Is the message doesn't exist, silently create it.
)
user.updateCh[full.AddressID].Enqueue(update)
didPublish, err := safePublishMessageUpdate(user, full.AddressID, update)
if err != nil {
return err
}
if !didPublish {
update = nil
}
return nil
}); err != nil {
return nil, err
}
if update == nil {
return nil, nil
}
return []imap.Update{update}, nil
}, user.apiUserLock, user.apiAddrsLock, user.apiLabelsLock, user.updateChLock)
}
@ -816,3 +841,37 @@ func (user *User) reportErrorNoContextCancel(title string, err error, reportCont
}
}
}
// safePublishMessageUpdate handles the rare case where the address' update channel may have been deleted in the same
// event. This rare case can take place if in the same event fetch request there is an update for delete address and
// create/update message.
// If the user is in combined mode, we simply push the update to the primary address. If the user is in split mode
// we do not publish the update as the address no longer exists.
func safePublishMessageUpdate(user *User, addressID string, update imap.Update) (bool, error) {
v, ok := user.updateCh[addressID]
if !ok {
if user.GetAddressMode() == vault.CombinedMode {
primAddr, err := getPrimaryAddr(user.apiAddrs)
if err != nil {
return false, fmt.Errorf("failed to get primary address: %w", err)
}
primaryCh, ok := user.updateCh[primAddr.ID]
if !ok {
return false, fmt.Errorf("primary address channel is not available")
}
primaryCh.Enqueue(update)
return true, nil
}
logrus.Warnf("Update channel not found for address %v, it may have been already deleted", addressID)
_ = user.reporter.ReportMessage("Message Update channel does not exist")
return false, nil
}
v.Enqueue(update)
return true, nil
}

View File

@ -83,6 +83,18 @@ func getAddrIdx(apiAddrs map[string]proton.Address, idx int) (proton.Address, er
return sorted[idx], nil
}
func getPrimaryAddr(apiAddrs map[string]proton.Address) (proton.Address, error) {
sorted := sortSlice(maps.Values(apiAddrs), func(a, b proton.Address) bool {
return a.Order < b.Order
})
if len(sorted) == 0 {
return proton.Address{}, fmt.Errorf("no addresses available")
}
return sorted[0], nil
}
// sortSlice returns the given slice sorted by the given comparator.
func sortSlice[Item any](items []Item, less func(Item, Item) bool) []Item {
sorted := make([]Item, len(items))