diff src/plugins/fts-squat/squat-trie.c @ 6931:97702c9c4111 HEAD

Locking and error handling fixes.
author Timo Sirainen <tss@iki.fi>
date Tue, 04 Dec 2007 19:59:57 +0200
parents 9c3f0e180751
children 414c9d631a81
line wrap: on
line diff
--- a/src/plugins/fts-squat/squat-trie.c	Tue Dec 04 15:20:01 2007 +0200
+++ b/src/plugins/fts-squat/squat-trie.c	Tue Dec 04 19:59:57 2007 +0200
@@ -29,6 +29,9 @@
 struct squat_trie_build_context {
 	struct squat_trie *trie;
 	struct ostream *output;
+	struct squat_uidlist_build_context *uidlist_build_ctx;
+
+	struct file_lock *file_lock;
 
 	uint32_t first_uid;
 	unsigned int compress_nodes:1;
@@ -46,7 +49,7 @@
 	bool failed;
 };
 
-static int squat_trie_map(struct squat_trie *trie);
+static int squat_trie_map(struct squat_trie *trie, bool building);
 
 void squat_trie_delete(struct squat_trie *trie)
 {
@@ -115,14 +118,6 @@
 	}
 }
 
-static void squat_trie_clear(struct squat_trie *trie)
-{
-	trie->corrupted = FALSE;
-	node_free(trie, &trie->root);
-	memset(&trie->root, 0, sizeof(trie->root));
-	memset(&trie->hdr, 0, sizeof(trie->hdr));
-}
-
 struct squat_trie *
 squat_trie_init(const char *path, uint32_t uidvalidity,
 		enum file_lock_method lock_method, bool mmap_disable)
@@ -139,19 +134,13 @@
 	return trie;
 }
 
-void squat_trie_deinit(struct squat_trie **_trie)
-{
-	struct squat_trie *trie = *_trie;
-
-	*_trie = NULL;
-	squat_trie_clear(trie);
-	squat_uidlist_deinit(trie->uidlist);
-	i_free(trie->path);
-	i_free(trie);
-}
-
 static void squat_trie_close(struct squat_trie *trie)
 {
+	trie->corrupted = FALSE;
+	node_free(trie, &trie->root);
+	memset(&trie->root, 0, sizeof(trie->root));
+	memset(&trie->hdr, 0, sizeof(trie->hdr));
+
 	if (trie->mmap_size != 0) {
 		if (munmap(trie->mmap_base, trie->mmap_size) < 0)
 			i_error("munmap(%s) failed: %m", trie->path);
@@ -162,7 +151,17 @@
 		trie->fd = -1;
 	}
 	trie->locked_file_size = 0;
-	squat_uidlist_close(trie->uidlist);
+}
+
+void squat_trie_deinit(struct squat_trie **_trie)
+{
+	struct squat_trie *trie = *_trie;
+
+	*_trie = NULL;
+	squat_trie_close(trie);
+	squat_uidlist_deinit(trie->uidlist);
+	i_free(trie->path);
+	i_free(trie);
 }
 
 static void squat_trie_header_init(struct squat_trie *trie)
@@ -197,14 +196,10 @@
 static int squat_trie_open(struct squat_trie *trie)
 {
 	squat_trie_close(trie);
-	squat_trie_clear(trie);
 
 	if (squat_trie_open_fd(trie) < 0)
 		return -1;
-	if (squat_trie_map(trie) < 0)
-		return -1;
-
-	return squat_uidlist_open(trie->uidlist);
+	return squat_trie_map(trie, FALSE);
 }
 
 static int squat_trie_is_file_stale(struct squat_trie *trie)
@@ -522,7 +517,8 @@
 }
 
 static inline void
-node_add_uid(struct squat_trie *trie, uint32_t uid, struct squat_node *node)
+node_add_uid(struct squat_trie_build_context *ctx, uint32_t uid,
+	     struct squat_node *node)
 {
 	if (uid < node->next_uid) {
 		/* duplicate */
@@ -532,11 +528,12 @@
 	node->next_uid = uid + 1;
 
 	node->uid_list_idx =
-		squat_uidlist_build_add_uid(trie->uidlist,
+		squat_uidlist_build_add_uid(ctx->uidlist_build_ctx,
 					    node->uid_list_idx, uid);
 }
 
-static void node_split_string(struct squat_trie *trie, struct squat_node *node)
+static void
+node_split_string(struct squat_trie_build_context *ctx, struct squat_node *node)
 {
 	struct squat_node *child;
 	unsigned char *str;
@@ -557,14 +554,14 @@
 	node->leaf_string_length = 0;
 
 	/* create a new child node for the rest of the string */
-	idx = node_add_child(trie, node, str[0], MAX_FAST_LEVEL);
+	idx = node_add_child(ctx->trie, node, str[0], MAX_FAST_LEVEL);
 	child = NODE_CHILDREN_NODES(node) + idx;
 
 	/* update uidlist to contain all of parent's UIDs */
 	child->next_uid =  node->next_uid - node->unused_uids;
 	for (uid = 0; uid < child->next_uid; uid++) {
 		child->uid_list_idx =
-			squat_uidlist_build_add_uid(trie->uidlist,
+			squat_uidlist_build_add_uid(ctx->uidlist_build_ctx,
 						    child->uid_list_idx, uid);
 	}
 
@@ -585,7 +582,8 @@
 }
 
 static bool
-node_leaf_string_add_or_split(struct squat_trie *trie, struct squat_node *node,
+node_leaf_string_add_or_split(struct squat_trie_build_context *ctx,
+			      struct squat_node *node,
 			      const unsigned char *data, unsigned int data_len)
 {
 	const unsigned char *str = NODE_LEAF_STRING(node);
@@ -594,23 +592,24 @@
 
 	if (data_len != str_len) {
 		/* different lengths, can't match */
-		node_split_string(trie, node);
+		node_split_string(ctx, node);
 		return FALSE;
 	}
 
 	for (i = 0; i < data_len; i++) {
 		if (data[i] != str[i]) {
 			/* non-match */
-			node_split_string(trie, node);
+			node_split_string(ctx, node);
 			return FALSE;
 		}
 	}
 	return TRUE;
 }
 
-static int squat_build_add(struct squat_trie *trie, uint32_t uid,
+static int squat_build_add(struct squat_trie_build_context *ctx, uint32_t uid,
 			   const unsigned char *data, unsigned int size)
 {
+	struct squat_trie *trie = ctx->trie;
 	struct squat_node *node = &trie->root;
 	const unsigned char *end = data + size;
 	unsigned char *chars;
@@ -626,14 +625,14 @@
 		if (node->leaf_string_length != 0) {
 			/* the whole string must match or we need to split
 			   the node */
-			if (node_leaf_string_add_or_split(trie, node, data,
+			if (node_leaf_string_add_or_split(ctx, node, data,
 							  end - data)) {
-				node_add_uid(trie, uid, node);
+				node_add_uid(ctx, uid, node);
 				return 0;
 			}
 		}
 
-		node_add_uid(trie, uid, node);
+		node_add_uid(ctx, uid, node);
 
 		if (unlikely(uid < node->unused_uids)) {
 			squat_trie_set_corrupted(trie);
@@ -677,7 +676,7 @@
 				     size - (end - data) + 1);
 		node = NODE_CHILDREN_NODES(node) + idx;
 
-		node_add_uid(trie, uid, node);
+		node_add_uid(ctx, uid, node);
 		uid = 0;
 
 		if (++data == end)
@@ -703,23 +702,24 @@
 }
 
 static int
-squat_build_word_bytes(struct squat_trie *trie, uint32_t uid,
+squat_build_word_bytes(struct squat_trie_build_context *ctx, uint32_t uid,
 		       const unsigned char *data, unsigned int size)
 {
+	struct squat_trie *trie = ctx->trie;
 	unsigned int i;
 
 	if (trie->hdr.full_len <= trie->hdr.partial_len)
 		i = 0;
 	else {
 		/* the first word is longer than others */
-		if (squat_build_add(trie, uid, data,
+		if (squat_build_add(ctx, uid, data,
 				    I_MIN(size, trie->hdr.full_len)) < 0)
 			return -1;
 		i = 1;
 	}
 
 	for (; i < size; i++) {
-		if (squat_build_add(trie, uid, data + i,
+		if (squat_build_add(ctx, uid, data + i,
 				    I_MIN(trie->hdr.partial_len, size-i)) < 0)
 			return -1;
 	}
@@ -727,15 +727,16 @@
 }
 
 static int
-squat_build_word(struct squat_trie *trie, uint32_t uid,
+squat_build_word(struct squat_trie_build_context *ctx, uint32_t uid,
 		 const unsigned char *data, const uint8_t *char_lengths,
 		 unsigned int size)
 {
+	struct squat_trie *trie = ctx->trie;
 	unsigned int i, j, bytelen;
 
 	if (char_lengths == NULL) {
 		/* optimization path: all characters are bytes */
-		return squat_build_word_bytes(trie, uid, data, size);
+		return squat_build_word_bytes(ctx, uid, data, size);
 	}
 
 	if (trie->hdr.full_len <= trie->hdr.partial_len)
@@ -747,7 +748,7 @@
 			bytelen += char_lengths[bytelen];
 		i_assert(bytelen <= size);
 
-		if (squat_build_add(trie, uid, data, bytelen) < 0)
+		if (squat_build_add(ctx, uid, data, bytelen) < 0)
 			return -1;
 		i = char_lengths[0];
 	}
@@ -758,7 +759,7 @@
 			bytelen += char_lengths[i + bytelen];
 		i_assert(i + bytelen <= size);
 
-		if (squat_build_add(trie, uid, data + i, bytelen) < 0)
+		if (squat_build_add(ctx, uid, data + i, bytelen) < 0)
 			return -1;
 	}
 	return 0;
@@ -803,7 +804,7 @@
 		while (start < i && data[start] == '\0')
 			start++;
 		if (i != start) {
-			if (squat_build_word(trie, uid, data + start,
+			if (squat_build_word(ctx, uid, data + start,
 					     !multibyte_chars ? NULL :
 					     char_lengths + start,
 					     i - start) < 0) {
@@ -817,7 +818,7 @@
 	while (start < i && data[start] == '\0')
 		start++;
 	if (i != start) {
-		if (squat_build_word(trie, uid, data + start,
+		if (squat_build_word(ctx, uid, data + start,
 				     !multibyte_chars ? NULL :
 				     char_lengths + start, i - start) < 0)
 			ret = -1;
@@ -1014,31 +1015,37 @@
 	}
 }
 
-static int squat_trie_renumber_uidlists(struct squat_trie *trie, bool finish)
+static int
+squat_trie_renumber_uidlists(struct squat_trie_build_context *ctx,
+			     bool compress)
 {
 	struct squat_trie_iterate_context *iter;
 	struct squat_node *node;
-	struct squat_uidlist *uidlist = trie->uidlist;
-	struct squat_uidlist_rebuild_context *ctx;
+	struct squat_uidlist_rebuild_context *rebuild_ctx;
 	ARRAY_TYPE(uint32_t) uids;
 	uint32_t new_uid_list_idx, max_count=0;
 	int ret = 0;
 
-	if ((ret = squat_uidlist_rebuild_init(uidlist, finish, &ctx)) <= 0)
+	/* FIXME: update indexid */
+	if ((ret = squat_uidlist_rebuild_init(ctx->uidlist_build_ctx,
+					      compress, &rebuild_ctx)) <= 0)
 		return ret;
 
+	ctx->trie->hdr.indexid = I_MAX(ioloop_time, ctx->trie->hdr.indexid + 1);
+
 	i_array_init(&uids, 1024);
-	iter = squat_trie_iterate_uidlist_init(trie);
+	iter = squat_trie_iterate_uidlist_init(ctx->trie);
 	node = squat_trie_iterate_uidlist_first(iter);
 	new_uid_list_idx = 0x100;
 	while (node != NULL) {
 		array_clear(&uids);
-		if (squat_uidlist_get(uidlist, node->uid_list_idx, &uids) < 0) {
+		if (squat_uidlist_get(ctx->trie->uidlist, node->uid_list_idx,
+				      &uids) < 0) {
 			ret = -1;
 			break;
 		}
 		max_count = I_MAX(max_count, array_count(&uids));
-		squat_uidlist_rebuild_next(ctx, &uids);
+		squat_uidlist_rebuild_next(rebuild_ctx, &uids);
 		node->uid_list_idx = new_uid_list_idx << 1;
 		new_uid_list_idx++;
 
@@ -1047,7 +1054,10 @@
 	squat_trie_iterate_uidlist_deinit(iter);
 	array_free(&uids);
 
-	return squat_uidlist_rebuild_finish(ctx, ret < 0);
+	/* lock the trie before we rename uidlist */
+	if (squat_trie_lock(ctx->trie, F_WRLCK, &ctx->file_lock) <= 0)
+		ret = -1;
+	return squat_uidlist_rebuild_finish(rebuild_ctx, ret < 0);
 }
 
 static bool squat_trie_check_header(struct squat_trie *trie)
@@ -1103,7 +1113,7 @@
 	return 0;
 }
 
-static int squat_trie_map(struct squat_trie *trie)
+static int squat_trie_map(struct squat_trie *trie, bool building)
 {
 	struct file_lock *file_lock = NULL;
 	bool changed;
@@ -1134,6 +1144,11 @@
 		}
 	}
 
+	if (ret == 0 && !building) {
+		/* do this while we're still locked */
+		ret = squat_uidlist_refresh(trie->uidlist);
+	}
+
 	if (file_lock != NULL)
 		file_unlock(&file_lock);
 	if (ret < 0)
@@ -1147,6 +1162,7 @@
 			  struct squat_trie_build_context **ctx_r)
 {
 	struct squat_trie_build_context *ctx;
+	struct squat_uidlist_build_context *uidlist_build_ctx;
 
 	if (trie->fd == -1) {
 		trie->fd = open(trie->path, O_RDWR | O_CREAT, 0600);
@@ -1157,14 +1173,17 @@
 	}
 
 	/* uidlist locks building */
-	if (squat_uidlist_build_init(trie->uidlist) < 0)
+	if (squat_uidlist_build_init(trie->uidlist, &uidlist_build_ctx) < 0)
 		return -1;
 
-	if (squat_trie_map(trie) < 0)
+	if (squat_trie_map(trie, TRUE) < 0) {
+		squat_uidlist_build_deinit(&uidlist_build_ctx);
 		return -1;
+	}
 
 	ctx = i_new(struct squat_trie_build_context, 1);
 	ctx->trie = trie;
+	ctx->uidlist_build_ctx = uidlist_build_ctx;
 	ctx->first_uid = trie->root.next_uid;
 
 	*last_uid_r = I_MAX((trie->root.next_uid+1)/2, 1) - 1;
@@ -1172,10 +1191,19 @@
 	return 0;
 }
 
+static int squat_trie_write_lock(struct squat_trie_build_context *ctx)
+{
+	if (ctx->file_lock != NULL)
+		return 0;
+
+	if (squat_trie_lock(ctx->trie, F_WRLCK, &ctx->file_lock) <= 0)
+		return -1;
+	return 0;
+}
+
 static int squat_trie_write(struct squat_trie_build_context *ctx)
 {
 	struct squat_trie *trie = ctx->trie;
-	struct file_lock *file_lock;
 	struct ostream *output;
 	const char *path;
 	int fd = -1, ret = 0;
@@ -1191,25 +1219,35 @@
 			i_error("creat(%s) failed: %m", path);
 			return -1;
 		}
+		ret = file_wait_lock(fd, path, F_WRLCK, trie->lock_method,
+				     SQUAT_TRIE_LOCK_TIMEOUT, &ctx->file_lock);
+		if (ret <= 0) {
+			if (ret == 0)
+				i_error("file_wait_lock(%s) failed: %m", path);
+			(void)close(fd);
+			return -1;
+		}
+
 		output = o_stream_create_fd(fd, 0, FALSE);
 		o_stream_cork(output);
 		o_stream_send(output, &trie->hdr, sizeof(trie->hdr));
-		file_lock = NULL;
 	} else {
-		/* we need to lock only the header update */
-		if (squat_trie_lock(trie, F_WRLCK, &file_lock) <= 0)
-			return -1;
-
+		/* we need to lock only while header is being written */
 		path = trie->path;
 		ctx->compress_nodes =
 			trie->hdr.used_file_size == sizeof(trie->hdr);
 		output = o_stream_create_fd(trie->fd, 0, FALSE);
 		o_stream_cork(output);
 
-		if (trie->hdr.used_file_size == 0)
+		if (trie->hdr.used_file_size != 0)
+			o_stream_seek(output, trie->hdr.used_file_size);
+		else {
+			if (squat_trie_write_lock(ctx) < 0) {
+				o_stream_unref(&output);
+				return -1;
+			}
 			o_stream_send(output, &trie->hdr, sizeof(trie->hdr));
-		else
-			o_stream_seek(output, trie->hdr.used_file_size);
+		}
 	}
 
 	ctx->output = output;
@@ -1218,38 +1256,44 @@
 
 	if (trie->corrupted)
 		ret = -1;
+	if (ret == 0)
+		ret = squat_trie_write_lock(ctx);
 	if (ret == 0) {
 		trie->hdr.used_file_size = output->offset;
 		o_stream_seek(output, 0);
 		o_stream_send(output, &trie->hdr, sizeof(trie->hdr));
-		o_stream_flush(output);
 	}
-	if (file_lock != NULL)
-		file_unlock(&file_lock);
-
 	if (output->last_failed_errno != 0) {
 		errno = output->last_failed_errno;
 		i_error("write() to %s failed: %m", path);
 		ret = -1;
 	}
-	o_stream_unref(&output);
-	if (fd != -1) {
-		if (ret < 0) {
-			if (close(fd) < 0)
-				i_error("close(%s) failed: %m", path);
-		} else if (rename(path, trie->path) < 0) {
-			i_error("rename(%s, %s) failed: %m", path, trie->path);
-			ret = -1;
-		}
+	o_stream_destroy(&output);
+
+	if (fd == -1) {
+		/* appended to the existing file */
+		return ret;
+	}
 
-		if (ret < 0) {
-			if (unlink(path) < 0 && errno != ENOENT)
-				i_error("unlink(%s) failed: %m", path);
-		} else {
+	/* recreating the trie file */
+	if (ret < 0) {
+		if (close(fd) < 0)
+			i_error("close(%s) failed: %m", path);
+		fd = -1;
+	} else if (rename(path, trie->path) < 0) {
+		i_error("rename(%s, %s) failed: %m", path, trie->path);
+		ret = -1;
+	}
+
+	if (ret < 0) {
+		if (unlink(path) < 0 && errno != ENOENT)
+			i_error("unlink(%s) failed: %m", path);
+	} else {
+		if (trie->fd != -1) {
 			if (close(trie->fd) < 0)
 				i_error("close(%s) failed: %m", trie->path);
-			trie->fd = fd;
 		}
+		trie->fd = fd;
 	}
 	return ret;
 }
@@ -1264,12 +1308,20 @@
 
 	compress = (ctx->trie->root.next_uid - ctx->first_uid) > 10;
 
-	ret = squat_uidlist_build_deinit(ctx->trie->uidlist);
-	if (squat_trie_renumber_uidlists(ctx->trie, compress) < 0)
-		ret = -1;
-	else
+	/* keep trie locked while header is being written and when files are
+	   being renamed, so that while trie is read locked, uidlist can't
+	   change under. */
+	squat_uidlist_build_flush(ctx->uidlist_build_ctx);
+	ret = squat_trie_renumber_uidlists(ctx, compress);
+	if (ret == 0)
 		ret = squat_trie_write(ctx);
 
+	if (ret == 0)
+		ret = squat_uidlist_build_finish(ctx->uidlist_build_ctx);
+	if (ctx->file_lock != NULL)
+		file_unlock(&ctx->file_lock);
+	squat_uidlist_build_deinit(&ctx->uidlist_build_ctx);
+
 	i_free(ctx);
 	return ret;
 }