]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-index: Fix deadlock when expunging mails and adding lots of data to cache
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Wed, 26 Aug 2020 15:39:22 +0000 (18:39 +0300)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 3 Sep 2020 15:30:40 +0000 (18:30 +0300)
This practically happened only when dovecot.index.cache contents were lost
and they were being re-filled while mails were also being expunged.

Broken by 9efb99924d0b7de27ca83e373f2290f3dd5b22cf

src/lib-index/mail-cache-transaction.c

index 603d3fa901a9a0776f94db295d0ee738469636a6..ad9191cbed9d5e0d28b986d39ae9e47487d6cc6f 100644 (file)
@@ -334,6 +334,7 @@ mail_cache_transaction_update_index(struct mail_cache_transaction_ctx *ctx,
                ctx->records_written++;
        }
        if (trans != ctx->trans) {
+               i_assert(cache->index->log_sync_locked);
                if (mail_index_transaction_commit(&trans) < 0) {
                        /* failed, but can't really do anything */
                } else {
@@ -515,13 +516,36 @@ mail_cache_transaction_flush(struct mail_cache_transaction_ctx *ctx,
                return 0;
        }
 
-       if (mail_cache_transaction_lock(ctx) <= 0)
+       /* If we're going to be committing a transaction, the log must be
+          locked before we lock cache or we can deadlock. */
+       bool lock_log = !ctx->cache->index->log_sync_locked &&
+               !committing && !ctx->have_noncommited_mails;
+       if (lock_log) {
+               uint32_t file_seq;
+               uoff_t file_offset;
+
+               if (mail_transaction_log_sync_lock(ctx->cache->index->log,
+                               "mail cache transaction flush",
+                               &file_seq, &file_offset) < 0)
+                       return -1;
+       }
+
+       if (mail_cache_transaction_lock(ctx) <= 0) {
+               if (lock_log) {
+                       mail_transaction_log_sync_unlock(ctx->cache->index->log,
+                               "mail cache transaction flush: cache lock failed");
+               }
                return -1;
+       }
 
        i_assert(ctx->cache_data != NULL);
        i_assert(ctx->last_rec_pos <= ctx->cache_data->used);
 
        if (mail_cache_transaction_update_fields(ctx) < 0) {
+               if (lock_log) {
+                       mail_transaction_log_sync_unlock(ctx->cache->index->log,
+                               "mail cache transaction flush: field update failed");
+               }
                mail_cache_unlock(ctx->cache);
                return -1;
        }
@@ -552,6 +576,11 @@ mail_cache_transaction_flush(struct mail_cache_transaction_ctx *ctx,
        }
        if (mail_cache_flush_and_unlock(ctx->cache) < 0)
                ret = -1;
+
+       if (lock_log) {
+               mail_transaction_log_sync_unlock(ctx->cache->index->log,
+                       "mail cache transaction flush");
+       }
        return ret;
 }