changeset 7195:bca55675d77c HEAD

When pipelining commands, delay synchronizations so that only one combined sync is performed for all the commands. This improves performance as well as fixes some problems with the earlier method.
author Timo Sirainen <tss@iki.fi>
date Sat, 26 Jan 2008 12:31:31 +0200
parents 226e80da0a23
children 4f1527e3d61b
files src/imap/client.c src/imap/client.h src/imap/cmd-append.c src/imap/imap-sync.c src/imap/imap-sync.h
diffstat 5 files changed, 205 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/client.c	Sat Jan 26 10:47:03 2008 +0200
+++ b/src/imap/client.c	Sat Jan 26 12:31:31 2008 +0200
@@ -49,7 +49,7 @@
 	client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
 				      client_idle_timeout, client);
 
-	client->command_pool = pool_alloconly_create("client command", 8192);
+	client->command_pool = pool_alloconly_create("client command", 1024*12);
 	client->namespaces = namespaces;
 
 	while (namespaces != NULL) {
@@ -512,8 +512,18 @@
 
 bool client_handle_unfinished_cmd(struct client_command_context *cmd)
 {
-	if (cmd->state != CLIENT_COMMAND_STATE_WAIT_OUTPUT)
+	if (cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT) {
+		/* need more input */
 		return FALSE;
+	}
+	if (cmd->state != CLIENT_COMMAND_STATE_WAIT_OUTPUT) {
+		/* waiting for something */
+		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC) {
+			/* this is mainly for APPEND. */
+			client_add_missing_io(cmd->client);
+		}
+		return TRUE;
+	}
 
 	/* output is blocking, we can execute more commands */
 	o_stream_set_flush_pending(cmd->client->output, TRUE);
@@ -586,7 +596,7 @@
 	}
 }
 
-static int client_handle_next_command(struct client *client, bool *remove_io_r)
+static bool client_handle_next_command(struct client *client, bool *remove_io_r)
 {
 	size_t size;
 
@@ -630,7 +640,6 @@
 {
 	bool ret, remove_io, handled_commands = FALSE;
 
-	o_stream_cork(client->output);
 	client->handling_input = TRUE;
 	do {
 		T_FRAME(
@@ -638,9 +647,8 @@
 		);
 		if (ret)
 			handled_commands = TRUE;
-	} while (ret && !client->disconnected);
+	} while (ret && !client->disconnected && client->io != NULL);
 	client->handling_input = FALSE;
-	o_stream_uncork(client->output);
 
 	if (client->output->closed) {
 		client_destroy(client, NULL);
@@ -650,13 +658,20 @@
 			io_remove(&client->io);
 		else
 			client_add_missing_io(client);
-		return handled_commands;
+		if (!handled_commands)
+			return FALSE;
+
+		ret = cmd_sync_delayed(client);
+		if (ret)
+			client_continue_pending_input(&client);
+		return TRUE;
 	}
 }
 
 void client_input(struct client *client)
 {
 	struct client_command_context *cmd;
+	struct ostream *output = client->output;
 	ssize_t bytes;
 
 	i_assert(client->io != NULL);
@@ -671,6 +686,8 @@
 		return;
 	}
 
+	o_stream_ref(output);
+	o_stream_cork(output);
 	if (!client_handle_input(client) && bytes == -2) {
 		/* parameter word is longer than max. input buffer size.
 		   this is most likely an error, so skip the new data
@@ -683,6 +700,8 @@
 		client_send_command_error(cmd, "Too long argument.");
 		client_command_free(cmd);
 	}
+	o_stream_uncork(output);
+	o_stream_unref(&output);
 }
 
 static void client_output_cmd(struct client_command_context *cmd)
@@ -750,6 +769,7 @@
 		client_destroy(client, NULL);
 		return 1;
 	} else {
+		(void)cmd_sync_delayed(client);
 		client_continue_pending_input(&client);
 	}
 	return ret;
--- a/src/imap/client.h	Sat Jan 26 10:47:03 2008 +0200
+++ b/src/imap/client.h	Sat Jan 26 12:31:31 2008 +0200
@@ -27,6 +27,8 @@
 	CLIENT_COMMAND_STATE_WAIT_OUTPUT,
 	/* Wait for other commands to finish execution */
 	CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY,
+	/* Waiting for other commands to finish so we can sync */
+	CLIENT_COMMAND_STATE_WAIT_SYNC,
 	/* Command is finished */
 	CLIENT_COMMAND_STATE_DONE
 };
@@ -46,6 +48,13 @@
 	struct imap_parser *parser;
 	enum client_command_state state;
 
+	/* if multiple commands are in progress, we may need to wait for them
+	   to finish before syncing mailbox. */
+	unsigned int sync_counter;
+	enum mailbox_sync_flags sync_flags;
+	enum imap_sync_flags sync_imap_flags;
+	const char *sync_tagline;
+
 	unsigned int uid:1; /* used UID command */
 	unsigned int cancel:1; /* command is wanted to be cancelled */
 	unsigned int param_error:1;
@@ -63,6 +72,7 @@
 	struct mailbox *mailbox;
         struct mailbox_keywords keywords;
 	unsigned int select_counter; /* increased when mailbox is changed */
+	unsigned int sync_counter;
 	uint32_t messages_count, recent_count, uidvalidity;
 
 	time_t last_input, last_output;
--- a/src/imap/cmd-append.c	Sat Jan 26 10:47:03 2008 +0200
+++ b/src/imap/cmd-append.c	Sat Jan 26 12:31:31 2008 +0200
@@ -78,10 +78,10 @@
 	o_stream_uncork(client->output);
 	if (!finished && cmd->state != CLIENT_COMMAND_STATE_DONE)
 		(void)client_handle_unfinished_cmd(cmd);
-	else {
+	else
 		client_command_free(cmd);
+	if (cmd_sync_delayed(client))
 		client_continue_pending_input(&client);
-	}
 }
 
 /* Returns -1 = error, 0 = need more data, 1 = successful. flags and
@@ -455,6 +455,13 @@
         struct cmd_append_context *ctx;
 	const char *mailbox;
 
+	if (client->syncing) {
+		/* if transaction is created while its view is synced,
+		   appends aren't allowed for it. */
+		cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+		return FALSE;
+	}
+
 	/* <mailbox> */
 	if (!client_read_string_args(cmd, 1, &mailbox))
 		return FALSE;
--- a/src/imap/imap-sync.c	Sat Jan 26 10:47:03 2008 +0200
+++ b/src/imap/imap-sync.c	Sat Jan 26 12:31:31 2008 +0200
@@ -2,16 +2,12 @@
 
 #include "common.h"
 #include "str.h"
+#include "ostream.h"
 #include "mail-storage.h"
 #include "imap-util.h"
 #include "imap-sync.h"
 #include "commands.h"
 
-struct cmd_sync_context {
-	const char *tagline;
-	struct imap_sync_context *sync_ctx;
-};
-
 struct imap_sync_context {
 	struct client *client;
 	struct mailbox *box;
@@ -189,46 +185,82 @@
 	return ret;
 }
 
-static bool cmd_sync_continue(struct client_command_context *cmd)
+static bool cmd_sync_continue(struct client_command_context *sync_cmd)
 {
-	struct cmd_sync_context *ctx = cmd->context;
+	struct client_command_context *cmd;
+	struct client *client = sync_cmd->client;
+	struct imap_sync_context *ctx = sync_cmd->context;
 	int ret;
 
-	if (cmd->cancel)
-		ret = 0;
-	else {
-		if ((ret = imap_sync_more(ctx->sync_ctx)) == 0)
-			return FALSE;
-	}
+	i_assert(ctx->client == client);
 
+	if ((ret = imap_sync_more(ctx)) == 0)
+		return FALSE;
 	if (ret < 0)
-		ctx->sync_ctx->failed = TRUE;
+		ctx->failed = TRUE;
+
+	client->syncing = FALSE;
+	if (imap_sync_deinit(ctx) < 0) {
+		client_send_untagged_storage_error(client,
+			mailbox_get_storage(client->mailbox));
+	}
+	sync_cmd->context = NULL;
 
-	cmd->client->syncing = FALSE;
-	if (imap_sync_deinit(ctx->sync_ctx) < 0) {
-		client_send_untagged_storage_error(cmd->client,
-			mailbox_get_storage(cmd->client->mailbox));
+	/* finish all commands that waited for this sync */
+	for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
+		    cmd != sync_cmd &&
+		    cmd->sync_counter+1 == client->sync_counter) {
+			client_send_tagline(cmd, cmd->sync_tagline);
+			client_command_free(cmd);
+		}
 	}
-
-	if (!cmd->cancel)
-		client_send_tagline(cmd, ctx->tagline);
+	client_send_tagline(sync_cmd, sync_cmd->sync_tagline);
 	return TRUE;
 }
 
-bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
-	      enum imap_sync_flags imap_flags, const char *tagline)
+static void get_common_sync_flags(struct client *client,
+				  enum mailbox_sync_flags *flags_r,
+				  enum imap_sync_flags *imap_flags_r)
 {
-	struct client *client = cmd->client;
-	struct cmd_sync_context *ctx;
+	struct client_command_context *cmd;
+	unsigned int count = 0, fast_count = 0, noexpunges_count = 0;
+
+	*flags_r = 0;
+	*imap_flags_r = 0;
+
+	for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+		if (cmd->sync_tagline != NULL &&
+		    cmd->sync_counter == client->sync_counter) {
+			if ((cmd->sync_flags & MAILBOX_SYNC_FLAG_FAST) != 0)
+				fast_count++;
+			if (cmd->sync_flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES)
+				noexpunges_count++;
+			*flags_r |= cmd->sync_flags;
+			*imap_flags_r |= cmd->sync_imap_flags;
+			count++;
+		}
+	}
+	if (fast_count != count)
+		*flags_r &= ~MAILBOX_SYNC_FLAG_FAST;
+	if (noexpunges_count != count)
+		*flags_r &= ~MAILBOX_SYNC_FLAG_NO_EXPUNGES;
+
+	i_assert((*flags_r & (MAILBOX_SYNC_AUTO_STOP |
+			      MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) == 0);
+}
+
+static bool cmd_sync_client(struct client_command_context *sync_cmd)
+{
+	struct client *client = sync_cmd->client;
+	struct imap_sync_context *ctx;
+	enum mailbox_sync_flags flags;
+	enum imap_sync_flags imap_flags;
 	bool no_newmail;
 
-	i_assert(client->output_lock == cmd || client->output_lock == NULL);
-
-	if (client->mailbox == NULL ||
-	    mailbox_transaction_get_count(client->mailbox) > 0) {
-		client_send_tagline(cmd, tagline);
-		return TRUE;
-	}
+	/* there may be multiple commands waiting. use their combined flags */
+	get_common_sync_flags(client, &flags, &imap_flags);
+	client->sync_counter++;
 
 	no_newmail = (client_workarounds & WORKAROUND_DELAY_NEWMAIL) != 0 &&
 		(imap_flags & IMAP_SYNC_FLAG_SAFE) == 0;
@@ -238,18 +270,101 @@
 		flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
 	}
 
-	ctx = p_new(cmd->pool, struct cmd_sync_context, 1);
-	ctx->tagline = p_strdup(cmd->pool, tagline);
-	ctx->sync_ctx = imap_sync_init(client, client->mailbox,
-				       imap_flags, flags);
-	ctx->sync_ctx->no_newmail = no_newmail;
+	client->syncing = TRUE;
+
+	ctx = imap_sync_init(client, client->mailbox, imap_flags, flags);
+	ctx->no_newmail = no_newmail;
+
+	/* handle the syncing using sync_cmd. it doesn't actually matter which
+	   one of the pending commands it is. */
+	sync_cmd->func = cmd_sync_continue;
+	sync_cmd->context = ctx;
+	sync_cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+	if (!cmd_sync_continue(sync_cmd)) {
+		o_stream_set_flush_pending(client->output, TRUE);
+		return FALSE;
+	}
+
+	client_command_free(sync_cmd);
+	(void)cmd_sync_delayed(client);
+	return TRUE;
+}
 
-	cmd->func = cmd_sync_continue;
-	cmd->context = ctx;
-	cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
+	      enum imap_sync_flags imap_flags, const char *tagline)
+{
+	struct client *client = cmd->client;
+
+	i_assert(client->output_lock == cmd || client->output_lock == NULL);
+
+	if (cmd->cancel)
+		return TRUE;
+
+	if (client->mailbox == NULL) {
+		/* no mailbox selected, no point in delaying the sync */
+		client_send_tagline(cmd, tagline);
+		return TRUE;
+	}
+
+	cmd->sync_counter = client->sync_counter;
+	cmd->sync_flags = flags;
+	cmd->sync_imap_flags = imap_flags;
+	cmd->sync_tagline = p_strdup(cmd->pool, tagline);
+	cmd->state = CLIENT_COMMAND_STATE_WAIT_SYNC;
+
+	cmd->func = NULL;
+	cmd->context = NULL;
+
+	client->output_lock = NULL;
 	if (client->input_lock == cmd)
 		client->input_lock = NULL;
-	client->output_lock = NULL;
-	client->syncing = TRUE;
-	return cmd_sync_continue(cmd);
+	return FALSE;
+}
+
+static bool cmd_sync_drop_fast(struct client *client)
+{
+	struct client_command_context *cmd, *next;
+	bool ret = FALSE;
+
+	for (cmd = client->command_queue; cmd != NULL; cmd = next) {
+		next = cmd->next;
+
+		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
+		    (cmd->sync_flags & MAILBOX_SYNC_FLAG_FAST) != 0) {
+			client_send_tagline(cmd, cmd->sync_tagline);
+			client_command_free(cmd);
+			ret = TRUE;
+		}
+	}
+	return ret;
 }
+
+bool cmd_sync_delayed(struct client *client)
+{
+	struct client_command_context *cmd;
+
+	if (client->output_lock != NULL) {
+		/* wait until we can send output to client */
+		return FALSE;
+	}
+
+	if (client->syncing ||
+	    (client->mailbox != NULL &&
+	     mailbox_transaction_get_count(client->mailbox) > 0)) {
+		/* wait until mailbox can be synced */
+		return cmd_sync_drop_fast(client);
+	}
+
+	/* find a command that we can sync */
+	for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC) {
+			if (cmd->sync_counter == client->sync_counter)
+				break;
+		}
+	}
+
+	if (cmd == NULL)
+		return cmd_sync_drop_fast(client);
+	i_assert(client->mailbox != NULL);
+	return cmd_sync_client(cmd);
+}
--- a/src/imap/imap-sync.h	Sat Jan 26 10:47:03 2008 +0200
+++ b/src/imap/imap-sync.h	Sat Jan 26 12:31:31 2008 +0200
@@ -16,5 +16,6 @@
 
 bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
 	      enum imap_sync_flags imap_flags, const char *tagline);
+bool cmd_sync_delayed(struct client *client);
 
 #endif