]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-index: mail_cache_*lock() - On reset_id mismatch wait for cache compression to...
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 30 Mar 2020 13:48:21 +0000 (16:48 +0300)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Wed, 15 Apr 2020 09:41:42 +0000 (12:41 +0300)
It was possible that the reset_id mismatch happened because cache
compression had already recreated the .cache file, but hadn't yet written
the new reset_id to the .log file. By waiting for the .log lock, we can be
sure that this won't happen.

src/lib-index/mail-cache.c

index 76a456a8547d003834c90bba5727f107c372fbbc..a12be27c7666f61404e8182dd2dd64e0628f6192 100644 (file)
@@ -659,7 +659,8 @@ static int mail_cache_lock_file(struct mail_cache *cache, bool nonblock)
 
 static void mail_cache_unlock_file(struct mail_cache *cache)
 {
-       file_unlock(&cache->file_lock);
+       if (cache->file_lock != NULL)
+               file_unlock(&cache->file_lock);
 }
 
 static bool mail_cache_verify_reset_id(struct mail_cache *cache)
@@ -676,6 +677,54 @@ static bool mail_cache_verify_reset_id(struct mail_cache *cache)
        return cache->hdr->file_seq == reset_id;
 }
 
+static int mail_cache_sync_wait_index(struct mail_cache *cache)
+{
+       const char *lock_reason = "cache reset_id sync";
+       uint32_t file_seq;
+       uoff_t file_offset;
+       int ret;
+
+       i_assert(cache->file_lock != NULL);
+
+       if (cache->index->log_sync_locked)
+               return 0;
+
+       /* Wait for .log file lock, so we can be sure that there are no cache
+          compressions going on. (Because it first recreates the cache file,
+          unlocks it and only then writes the changes to the index and
+          releases the .log lock.) To prevent deadlocks, cache file must be
+          locked after the .log, not before. */
+       mail_cache_unlock_file(cache);
+       if (mail_transaction_log_sync_lock(cache->index->log, lock_reason,
+                                          &file_seq, &file_offset) < 0)
+               return -1;
+       /* Lock the cache file as well so we'll get a guaranteed result on
+          whether the reset_id can be synced or if it's already desynced and
+          the cache just needs to be recreated. */
+       ret = -1;
+       while (mail_cache_lock_file(cache, FALSE) > 0) {
+               /* Locked the current fd, but it may have already been
+                  recreated. Reopen and retry if needed. */
+               if (!mail_cache_need_reopen(cache)) {
+                       ret = 1;
+                       break;
+               }
+               if ((ret = mail_cache_reopen(cache)) <= 0)
+                       break;
+       }
+
+       if (ret <= 0)
+               ;
+       else if (mail_index_refresh(cache->index) < 0)
+               ret = -1;
+       else
+               ret = mail_cache_verify_reset_id(cache) ? 1 : 0;
+       mail_transaction_log_sync_unlock(cache->index->log, lock_reason);
+       if (ret <= 0)
+               mail_cache_unlock_file(cache);
+       return ret;
+}
+
 static int mail_cache_sync_reset_id(struct mail_cache *cache)
 {
        /* verify that the index reset_id matches the cache's file_seq */
@@ -695,7 +744,10 @@ static int mail_cache_sync_reset_id(struct mail_cache *cache)
                return -1;
        if (mail_cache_verify_reset_id(cache))
                return 1;
-       return 0;
+
+       /* Use locking to wait for a potential cache compressing to finish.
+          If that didn't work either, the cache is corrupted or lost. */
+       return mail_cache_sync_wait_index(cache);
 }
 
 static int
@@ -708,6 +760,8 @@ mail_cache_lock_full(struct mail_cache *cache, bool nonblock)
           if we're coming from mail_cache_expunge_count() while syncing the
           index. */
        i_assert(!cache->index->mapping || cache->index->log_sync_locked);
+       /* No need to fully support nonblock=TRUE for now at least */
+       i_assert(!nonblock || cache->index->log_sync_locked);
 
        if (MAIL_INDEX_IS_IN_MEMORY(cache->index) ||
            cache->index->readonly)
@@ -727,7 +781,6 @@ mail_cache_lock_full(struct mail_cache *cache, bool nonblock)
        for (;;) {
                if (mail_cache_lock_file(cache, nonblock) <= 0)
                        return -1;
-               i_assert(!MAIL_CACHE_IS_UNUSABLE(cache));
                if (!mail_cache_need_reopen(cache)) {
                        /* locked the latest file */
                        break;
@@ -744,6 +797,7 @@ mail_cache_lock_full(struct mail_cache *cache, bool nonblock)
                mail_cache_unlock_file(cache);
                return ret;
        }
+       i_assert(cache->file_lock != NULL);
 
        /* successfully locked - make sure our header is up to date */
        cache->locked = TRUE;