dovecot-2.2: lib-index: Delay unlocking cache compression until ...

dovecot at dovecot.org dovecot at dovecot.org
Tue Oct 7 16:07:50 UTC 2014


details:   http://hg.dovecot.org/dovecot-2.2/rev/51f5680d4108
changeset: 17905:51f5680d4108
user:      Timo Sirainen <tss at iki.fi>
date:      Tue Oct 07 19:07:16 2014 +0300
description:
lib-index: Delay unlocking cache compression until changes to transaction log are committed.
This should fix race condition with two processes compressing the file at
the same time with same file_seq and becoming confused.

diffstat:

 src/lib-index/mail-cache-compress.c    |  228 ++++++++++++++++++++------------
 src/lib-index/mail-cache-transaction.c |    5 +-
 src/lib-index/mail-cache.h             |   11 +-
 src/lib-index/mail-index-sync.c        |   13 +-
 src/lib-storage/index/index-rebuild.c  |   10 +-
 5 files changed, 170 insertions(+), 97 deletions(-)

diffs (truncated from 436 to 300 lines):

diff -r 0a34975b2dcc -r 51f5680d4108 src/lib-index/mail-cache-compress.c
--- a/src/lib-index/mail-cache-compress.c	Tue Oct 07 19:04:36 2014 +0300
+++ b/src/lib-index/mail-cache-compress.c	Tue Oct 07 19:07:16 2014 +0300
@@ -10,6 +10,7 @@
 #include "file-set-size.h"
 #include "mail-cache-private.h"
 
+#include <stdio.h>
 #include <sys/stat.h>
 
 struct mail_cache_copy_context {
@@ -23,6 +24,10 @@
 	bool new_msg;
 };
 
+struct mail_cache_compress_lock {
+	struct dotlock *dotlock;
+};
+
 static void
 mail_cache_merge_bitmask(struct mail_cache_copy_context *ctx,
 			 const struct mail_cache_iterate_field *field)
@@ -315,104 +320,27 @@
 	return 0;
 }
 
-static int mail_cache_compress_has_file_changed(struct mail_cache *cache)
+static int
+mail_cache_compress_write(struct mail_cache *cache,
+			  struct mail_index_transaction *trans,
+			  int fd, const char *temp_path, bool *unlock)
 {
-	struct mail_cache_header hdr;
-	unsigned int i;
-	int fd, ret;
-
-	for (i = 0;; i++) {
-		fd = nfs_safe_open(cache->filepath, O_RDONLY);
-		if (fd == -1) {
-			if (errno == ENOENT)
-				return 0;
-
-			mail_cache_set_syscall_error(cache, "open()");
-			return -1;
-		}
-
-		ret = read_full(fd, &hdr, sizeof(hdr));
-		i_close_fd(&fd);
-
-		if (ret >= 0) {
-			if (ret == 0)
-				return 0;
-			if (cache->need_compress_file_seq == 0) {
-				/* previously it didn't exist */
-				return 1;
-			}
-			return hdr.file_seq != cache->need_compress_file_seq;
-		} else if (errno != ESTALE || i >= NFS_ESTALE_RETRY_COUNT) {
-			mail_cache_set_syscall_error(cache, "read()");
-			return -1;
-		}
-	}
-}
-
-static int mail_cache_compress_locked(struct mail_cache *cache,
-				      struct mail_index_transaction *trans,
-				      bool *unlock)
-{
-	struct dotlock *dotlock;
 	struct stat st;
-	mode_t old_mask;
 	uint32_t file_seq, old_offset;
 	ARRAY_TYPE(uint32_t) ext_offsets;
 	const uint32_t *offsets;
-	const void *data;
 	unsigned int i, count;
-	int fd, ret;
 
-	old_mask = umask(cache->index->mode ^ 0666);
-	fd = file_dotlock_open(&cache->dotlock_settings, cache->filepath,
-			       DOTLOCK_CREATE_FLAG_NONBLOCK, &dotlock);
-	umask(old_mask);
-
-	if (fd == -1) {
-		if (errno != EAGAIN)
-			mail_cache_set_syscall_error(cache, "file_dotlock_open()");
+	if (mail_cache_copy(cache, trans, fd, &file_seq, &ext_offsets) < 0)
 		return -1;
-	}
-
-	if ((ret = mail_cache_compress_has_file_changed(cache)) != 0) {
-		if (ret < 0)
-			return -1;
-
-		/* was just compressed, forget this */
-		cache->need_compress_file_seq = 0;
-		file_dotlock_delete(&dotlock);
-
-		if (*unlock) {
-			(void)mail_cache_unlock(cache);
-			*unlock = FALSE;
-		}
-
-		return mail_cache_reopen(cache);
-	}
-
-	mail_index_fchown(cache->index, fd,
-			  file_dotlock_get_lock_path(dotlock));
-
-	if (mail_cache_copy(cache, trans, fd, &file_seq, &ext_offsets) < 0) {
-		/* the fields may have been updated in memory already.
-		   reverse those changes by re-reading them from file. */
-		if (mail_cache_header_fields_read(cache) < 0)
-			return -1;
-		file_dotlock_delete(&dotlock);
-		return -1;
-	}
 
 	if (fstat(fd, &st) < 0) {
 		mail_cache_set_syscall_error(cache, "fstat()");
-		file_dotlock_delete(&dotlock);
+		array_free(&ext_offsets);
 		return -1;
 	}
-
-	if (file_dotlock_replace(&dotlock,
-				 DOTLOCK_REPLACE_FLAG_DONT_CLOSE_FD) < 0) {
-		mail_cache_set_syscall_error(cache,
-					     "file_dotlock_replace()");
-		i_close_fd(&fd);
+	if (rename(temp_path, cache->filepath) < 0) {
+		mail_cache_set_syscall_error(cache, "rename()");
 		array_free(&ext_offsets);
 		return -1;
 	}
@@ -439,7 +367,101 @@
 	cache->st_ino = st.st_ino;
 	cache->st_dev = st.st_dev;
 	cache->field_header_write_pending = FALSE;
+	return 0;
+}
 
+static int mail_cache_compress_has_file_changed(struct mail_cache *cache)
+{
+	struct mail_cache_header hdr;
+	unsigned int i;
+	int fd, ret;
+
+	for (i = 0;; i++) {
+		fd = nfs_safe_open(cache->filepath, O_RDONLY);
+		if (fd == -1) {
+			if (errno == ENOENT)
+				return 0;
+
+			mail_cache_set_syscall_error(cache, "open()");
+			return -1;
+		}
+
+		ret = read_full(fd, &hdr, sizeof(hdr));
+		i_close_fd(&fd);
+
+		if (ret >= 0) {
+			if (ret == 0)
+				return 0;
+			if (cache->need_compress_file_seq == 0) {
+				/* previously it didn't exist or it
+				   was unusable and was just unlinked */
+				return 1;
+			}
+			return hdr.file_seq != cache->need_compress_file_seq;
+		} else if (errno != ESTALE || i >= NFS_ESTALE_RETRY_COUNT) {
+			mail_cache_set_syscall_error(cache, "read()");
+			return -1;
+		}
+	}
+}
+
+static int mail_cache_compress_dotlock(struct mail_cache *cache,
+				       struct dotlock **dotlock_r)
+{
+	if (file_dotlock_create(&cache->dotlock_settings, cache->filepath,
+				DOTLOCK_CREATE_FLAG_NONBLOCK, dotlock_r) <= 0) {
+		if (errno != EAGAIN)
+			mail_cache_set_syscall_error(cache, "file_dotlock_open()");
+		return -1;
+	}
+	return 0;
+}
+
+static int mail_cache_compress_locked(struct mail_cache *cache,
+				      struct mail_index_transaction *trans,
+				      bool *unlock, struct dotlock **dotlock_r)
+{
+	const char *temp_path;
+	const void *data;
+	int fd, ret;
+
+	/* There are two possible locking situations here:
+	   a) Cache is locked against any modifications.
+	   b) Cache doesn't exist or is unusable. There's no lock.
+	   Because the cache lock itself is unreliable, we'll be using a
+	   separate dotlock to guard against two processes compressing the
+	   cache at the same time. */
+
+	if (mail_cache_compress_dotlock(cache, dotlock_r) < 0)
+		return -1;
+	/* we've locked the cache compression now. if somebody else had just
+	   recreated the cache, reopen the cache and return success. */
+	if ((ret = mail_cache_compress_has_file_changed(cache)) != 0) {
+		if (ret < 0)
+			return -1;
+
+		/* was just compressed, forget this */
+		cache->need_compress_file_seq = 0;
+		file_dotlock_delete(dotlock_r);
+
+		if (*unlock) {
+			(void)mail_cache_unlock(cache);
+			*unlock = FALSE;
+		}
+
+		return mail_cache_reopen(cache) < 0 ? -1 : 0;
+	}
+
+	/* we want to recreate the cache. write it first to a temporary file */
+	fd = mail_index_create_tmp_file(cache->index, cache->filepath, &temp_path);
+	if (fd == -1)
+		return -1;
+	if (mail_cache_compress_write(cache, trans, fd, temp_path, unlock) < 0) {
+		i_close_fd(&fd);
+		if (unlink(temp_path) < 0)
+			i_error("unlink(%s) failed: %m", temp_path);
+		return -1;
+	}
 	if (cache->file_cache != NULL)
 		file_cache_set_fd(cache->file_cache, cache->fd);
 
@@ -453,15 +475,21 @@
 }
 
 int mail_cache_compress(struct mail_cache *cache,
-			struct mail_index_transaction *trans)
+			struct mail_index_transaction *trans,
+			struct mail_cache_compress_lock **lock_r)
 {
+	struct dotlock *dotlock = NULL;
 	bool unlock = FALSE;
 	int ret;
 
 	i_assert(!cache->compressing);
 
-	if (MAIL_INDEX_IS_IN_MEMORY(cache->index) || cache->index->readonly)
+	*lock_r = NULL;
+
+	if (MAIL_INDEX_IS_IN_MEMORY(cache->index) || cache->index->readonly) {
+		*lock_r = i_new(struct mail_cache_compress_lock, 1);
 		return 0;
+	}
 
 	/* compression isn't very efficient with small read()s */
 	if (cache->map_with_read) {
@@ -483,9 +511,10 @@
 	} else {
 		switch (mail_cache_try_lock(cache)) {
 		case -1:
+			/* already locked or some other error */
 			return -1;
 		case 0:
-			/* couldn't lock, either it's broken or doesn't exist.
+			/* cache is broken or doesn't exist.
 			   just start creating it. */
 			break;
 		default:
@@ -494,15 +523,36 @@
 		}
 	}
 	cache->compressing = TRUE;
-	ret = mail_cache_compress_locked(cache, trans, &unlock);
+	ret = mail_cache_compress_locked(cache, trans, &unlock, &dotlock);
 	cache->compressing = FALSE;
 	if (unlock) {
 		if (mail_cache_unlock(cache) < 0)
 			ret = -1;
 	}
+	if (ret < 0) {
+		if (dotlock != NULL)
+			file_dotlock_delete(&dotlock);
+		/* the fields may have been updated in memory already.
+		   reverse those changes by re-reading them from file. */
+		(void)mail_cache_header_fields_read(cache);
+	} else {
+		*lock_r = i_new(struct mail_cache_compress_lock, 1);
+		(*lock_r)->dotlock = dotlock;
+	}
 	return ret;
 }
 
+void mail_cache_compress_unlock(struct mail_cache_compress_lock **_lock)
+{
+	struct mail_cache_compress_lock *lock = *_lock;


More information about the dovecot-cvs mailing list