fix(GODT-2812): Fix rare sync deadlock

Copy data rather than hold onto the locks while sync is ongoing. The
data in question does not change while the sync is ongoing and holding
on to the locks during a very long sync can create a deadlock with
due to some IMAP operation that needs to acquire one of those locks with
write access.
This commit is contained in:
Leander Beernaert
2023-07-26 18:12:06 +02:00
committed by Jakub
parent f1cf4ee194
commit f82965b825

View File

@ -113,12 +113,27 @@ func (user *User) doSync(ctx context.Context) error {
}
func (user *User) sync(ctx context.Context) error {
return safe.RLockRet(func() error {
return withAddrKRs(user.apiUser, user.apiAddrs, user.vault.KeyPass(), func(_ *crypto.KeyRing, addrKRs map[string]*crypto.KeyRing) error {
var apiUser proton.User
var apiAddrs map[string]proton.Address
var apiLabels map[string]proton.Label
safe.RLock(func() {
// Make a copy of the user, labels and addresses. They don't change during sync and we don't need to
// keep holding on to these locks which are required for imap commands to succeed while this is going on.
apiUser = user.apiUser
apiAddrs = maps.Clone(user.apiAddrs)
apiLabels = maps.Clone(user.apiLabels)
}, user.apiUserLock, user.apiAddrsLock, user.apiLabelsLock)
// We can keep holding on to the updateCh as this is not used during regular imap processing, only with events.
user.updateChLock.RLock()
defer user.updateChLock.RUnlock()
return withAddrKRs(apiUser, apiAddrs, user.vault.KeyPass(), func(_ *crypto.KeyRing, addrKRs map[string]*crypto.KeyRing) error {
if !user.vault.SyncStatus().HasLabels {
user.log.Info("Syncing labels")
if err := syncLabels(ctx, user.apiLabels, xslices.Unique(maps.Values(user.updateCh))...); err != nil {
if err := syncLabels(ctx, apiLabels, xslices.Unique(maps.Values(user.updateCh))...); err != nil {
return fmt.Errorf("failed to sync labels: %w", err)
}
@ -140,8 +155,6 @@ func (user *User) sync(ctx context.Context) error {
return fmt.Errorf("failed to get message IDs to sync: %w", err)
}
logrus.Debugf("User has the following failed synced message ids: %v", user.vault.SyncStatus().FailedMessageIDs)
// Remove any messages that have already failed to sync.
messageIDs = xslices.Filter(messageIDs, func(messageID string) bool {
return !slices.Contains(user.vault.SyncStatus().FailedMessageIDs, messageID)
@ -163,7 +176,7 @@ func (user *User) sync(ctx context.Context) error {
user.client,
user.reporter,
user.vault,
user.apiLabels,
apiLabels,
addrKRs,
user.updateCh,
user.eventCh,
@ -183,7 +196,6 @@ func (user *User) sync(ctx context.Context) error {
return nil
})
}, user.apiUserLock, user.apiAddrsLock, user.apiLabelsLock, user.updateChLock)
}
// nolint:exhaustive