mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 20:56:51 +00:00
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:
@ -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
|
||||
|
||||
Reference in New Issue
Block a user