summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Woodhouse <David.Woodhouse@intel.com>2010-06-23 19:27:34 +0100
committerDavid Woodhouse <David.Woodhouse@intel.com>2010-06-28 13:34:13 +0100
commit800589c0d525d64b3a25e4dd3725402b74609756 (patch)
tree1f284b36161ed495e813d349cb435e9fd60f51a1
parentb256ad948135ee15c968cf7eb00c8a7e939952f9 (diff)
downloadevolution-data-server-800589c0d525d64b3a25e4dd3725402b74609756.tar.gz
Fix overzealous IDLE handling
We were sometimes entering the IDLE state even during a multi-part message fetch, between one part and the other. This happened because we call imapx_command_start_next() which sees an empty queue and triggers IDLE, before we called the completion handler for the previous command which puts a new FETCH request into the queue. Similar behaviour was seen in various other situations, including between subsequent sync_message() calls from the front end. Fix this by having a dwell time of 2 seconds between the queue becoming empty and actually sending the IDLE command. Only if the queue _remains_ empty for 2 seconds do we really enter the IDLE state. Clean up the IDLE handling to use a state machine instead of a set of boolean flags, while we're at it. (cherry picked from commit 6f615e11d4dff18dfc11fe317d75f3c8dc1aafd9)
-rw-r--r--camel/providers/imapx/camel-imapx-server.c136
1 files changed, 105 insertions, 31 deletions
diff --git a/camel/providers/imapx/camel-imapx-server.c b/camel/providers/imapx/camel-imapx-server.c
index ef57f4d32..ed546841f 100644
--- a/camel/providers/imapx/camel-imapx-server.c
+++ b/camel/providers/imapx/camel-imapx-server.c
@@ -3,6 +3,7 @@
#include <config.h>
#endif
+#include <time.h>
#include <errno.h>
#include <string.h>
#include <glib.h>
@@ -62,6 +63,9 @@
#define QUEUE_LOCK(x) (g_static_rec_mutex_lock(&(x)->queue_lock))
#define QUEUE_UNLOCK(x) (g_static_rec_mutex_unlock(&(x)->queue_lock))
+#define IDLE_LOCK(x) (g_mutex_lock((x)->idle_lock))
+#define IDLE_UNLOCK(x) (g_mutex_unlock((x)->idle_lock))
+
/* All comms with server go here */
/* Try pipelining fetch requests, 'in bits' */
@@ -89,6 +93,7 @@ struct _CamelIMAPXCommand;
void imapx_uidset_init(struct _uidset_state *ss, gint total, gint limit);
gint imapx_uidset_done(struct _uidset_state *ss, struct _CamelIMAPXCommand *ic);
gint imapx_uidset_add(struct _uidset_state *ss, struct _CamelIMAPXCommand *ic, const gchar *uid);
+static gboolean imapx_command_idle_stop (CamelIMAPXServer *is, CamelException *ex);
static gint imapx_continuation(CamelIMAPXServer *imap, CamelException *ex, gboolean litplus);
static gboolean imapx_disconnect (CamelIMAPXServer *is);
static gint imapx_uid_cmp(gconstpointer ap, gconstpointer bp, gpointer data);
@@ -309,15 +314,28 @@ static gint imapx_refresh_info_uid_cmp(gconstpointer ap, gconstpointer bp);
static gint imapx_uids_array_cmp (gconstpointer ap, gconstpointer bp);
static void imapx_server_sync_changes(CamelIMAPXServer *is, CamelFolder *folder, gint pri, CamelException *ex);
+enum _idle_state {
+ IMAPX_IDLE_OFF,
+ IMAPX_IDLE_PENDING, /* Queue is idle; waiting to send IDLE command
+ soon if nothing more interesting happens */
+ IMAPX_IDLE_ISSUED, /* Sent IDLE command; waiting for response */
+ IMAPX_IDLE_STARTED, /* IDLE continuation received; IDLE active */
+ IMAPX_IDLE_CANCEL, /* Cancelled from ISSUED state; need to send
+ DONE as soon as we receive continuation */
+};
+#define IMAPX_IDLE_DWELL_TIME 2 /* Number of seconds to remain in PENDING
+ state waiting for other commands to be
+ queued, before actually sending IDLE */
+
typedef struct _CamelIMAPXIdle CamelIMAPXIdle;
+
struct _CamelIMAPXIdle {
GMutex *idle_lock;
EFlag *idle_start_watch;
GThread *idle_thread;
- gboolean idle_issue_done;
- gboolean in_idle;
- gboolean started;
+ time_t started;
+ enum _idle_state state;
gboolean idle_exit;
};
@@ -326,7 +344,7 @@ static gboolean imapx_idle_supported (CamelIMAPXServer *is);
static void imapx_start_idle (CamelIMAPXServer *is);
static void imapx_exit_idle (CamelIMAPXServer *is);
static void imapx_init_idle (CamelIMAPXServer *is);
-static void imapx_stop_idle (CamelIMAPXServer *is, CamelException *ex);
+static gboolean imapx_stop_idle (CamelIMAPXServer *is, CamelException *ex);
static void camel_imapx_server_idle (CamelIMAPXServer *is, CamelFolder *folder, CamelException *ex);
enum {
@@ -863,9 +881,13 @@ imapx_command_start_next(CamelIMAPXServer *is, CamelException *ex)
gboolean empty = imapx_is_command_queue_empty (is);
if (imapx_in_idle (is) && !camel_dlist_empty (&is->queue)) {
- imapx_stop_idle (is, ex);
- c(printf ("waiting for idle to stop \n"));
- return;
+ /* if imapx_stop_idle() returns FALSE, it was only
+ pending and we can go ahead and send a new command
+ immediately. If it returns TRUE, we must wait. */
+ if (imapx_stop_idle (is, ex)) {
+ c(printf ("waiting for idle to stop \n"));
+ return;
+ }
} else if (empty && !imapx_in_idle (is)) {
imapx_start_idle (is);
c(printf ("starting idle \n"));
@@ -1538,7 +1560,21 @@ imapx_continuation(CamelIMAPXServer *imap, CamelException *ex, gboolean litplus)
camel_imapx_stream_skip (imap->stream, ex);
c(printf("Got continuation response for IDLE \n"));
- imap->idle->started = TRUE;
+ IDLE_LOCK(imap->idle);
+ /* We might have actually sent the DONE already! */
+ if (imap->idle->state == IMAPX_IDLE_ISSUED)
+ imap->idle->state = IMAPX_IDLE_STARTED;
+ else if (imap->idle->state == IMAPX_IDLE_CANCEL) {
+ /* IDLE got cancelled after we sent the command, while
+ we were waiting for this continuation. Send DONE
+ immediately. */
+ imapx_command_idle_stop(imap, ex);
+ imap->idle->state = IMAPX_IDLE_OFF;
+ } else {
+ c(printf("idle starts in wrong state %d\n",
+ imap->idle->state));
+ }
+ IDLE_UNLOCK(imap->idle);
QUEUE_LOCK(imap);
imap->literal = NULL;
@@ -1858,8 +1894,6 @@ imapx_run_job (CamelIMAPXServer *is, CamelIMAPXJob *job)
/* ********************************************************************** */
// IDLE support
-#define IDLE_LOCK(x) (g_mutex_lock((x)->idle_lock))
-#define IDLE_UNLOCK(x) (g_mutex_unlock((x)->idle_lock))
/*TODO handle negative cases sanely */
static gboolean
@@ -1886,9 +1920,7 @@ imapx_command_idle_done (CamelIMAPXServer *is, CamelIMAPXCommand *ic)
}
IDLE_LOCK (idle);
- idle->in_idle = FALSE;
- idle->idle_issue_done = FALSE;
- idle->started = FALSE;
+ idle->state = IMAPX_IDLE_OFF;
IDLE_UNLOCK (idle);
imapx_job_done (is, ic->job);
@@ -1911,7 +1943,16 @@ imapx_job_idle_start(CamelIMAPXServer *is, CamelIMAPXJob *job)
cp->type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
QUEUE_LOCK (is);
- imapx_command_start (is, ic);
+ IDLE_LOCK(is->idle);
+ /* Don't issue it if the idle was cancelled already */
+ if (is->idle->state == IMAPX_IDLE_PENDING) {
+ is->idle->state = IMAPX_IDLE_ISSUED;
+ imapx_command_start (is, ic);
+ } else {
+ imapx_job_done (is, ic->job);
+ camel_imapx_command_free (ic);
+ }
+ IDLE_UNLOCK(is->idle);
QUEUE_UNLOCK (is);
}
@@ -1959,20 +2000,33 @@ imapx_idle_thread (gpointer data)
CamelIMAPXServer *is = (CamelIMAPXServer *) data;
while (TRUE) {
- CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) is->select_folder;
-
+ CamelIMAPXFolder *ifolder;
e_flag_clear (is->idle->idle_start_watch);
- camel_imapx_server_idle (is, is->select_folder, ex);
- if (!camel_exception_is_set (ex) && ifolder->exists_on_server >
- camel_folder_summary_count (((CamelFolder *) ifolder)->summary) && imapx_is_command_queue_empty (is))
- imapx_server_fetch_new_messages (is, is->select_folder, TRUE, ex);
+ IDLE_LOCK(is->idle);
+ while ((ifolder = (CamelIMAPXFolder *) is->select_folder) &&
+ is->idle->state == IMAPX_IDLE_PENDING) {
+ time_t dwelled = time(NULL) - is->idle->started;
- if (camel_exception_is_set (ex)) {
- e(printf ("Caught exception in idle thread: %s \n", ex->desc));
- /* No way to asyncronously notify UI ? */
- camel_exception_clear (ex);
+ if (dwelled < IMAPX_IDLE_DWELL_TIME) {
+ IDLE_UNLOCK(is->idle);
+ sleep(IMAPX_IDLE_DWELL_TIME - dwelled);
+ continue;
+ }
+ IDLE_UNLOCK(is->idle);
+ camel_imapx_server_idle (is, (void *)ifolder, ex);
+
+ if (!camel_exception_is_set (ex) && ifolder->exists_on_server >
+ camel_folder_summary_count (((CamelFolder *) ifolder)->summary) && imapx_is_command_queue_empty (is))
+ imapx_server_fetch_new_messages (is, is->select_folder, TRUE, ex);
+
+ if (camel_exception_is_set (ex)) {
+ e(printf ("Caught exception in idle thread: %s \n", ex->desc));
+ /* No way to asyncronously notify UI ? */
+ camel_exception_clear (ex);
+ }
}
+ IDLE_UNLOCK(is->idle);
e_flag_wait (is->idle->idle_start_watch);
@@ -1985,19 +2039,37 @@ imapx_idle_thread (gpointer data)
return NULL;
}
-static void
+static gboolean
imapx_stop_idle (CamelIMAPXServer *is, CamelException *ex)
{
CamelIMAPXIdle *idle = is->idle;
+ int stopped = FALSE;
+ time_t now;
+ time(&now);
IDLE_LOCK (idle);
- if (!idle->idle_issue_done && idle->started) {
+ switch(idle->state) {
+ case IMAPX_IDLE_ISSUED:
+ idle->state = IMAPX_IDLE_CANCEL;
+ case IMAPX_IDLE_CANCEL:
+ stopped = TRUE;
+ break;
+
+ case IMAPX_IDLE_STARTED:
imapx_command_idle_stop (is, ex);
- idle->idle_issue_done = TRUE;
+ idle->state = IMAPX_IDLE_OFF;
+ stopped = TRUE;
+ c(printf("Stopping idle after %ld seconds\n",
+ (long)(now - idle->started)));
+ case IMAPX_IDLE_PENDING:
+ idle->state = IMAPX_IDLE_OFF;
+ case IMAPX_IDLE_OFF:
+ break;
}
-
IDLE_UNLOCK (idle);
+
+ return stopped;
}
static void
@@ -2046,14 +2118,16 @@ imapx_start_idle (CamelIMAPXServer *is)
IDLE_LOCK (idle);
+ g_assert (idle->state == IMAPX_IDLE_OFF);
+ time(&idle->started);
+ idle->state = IMAPX_IDLE_PENDING;
+
if (!idle->idle_thread) {
idle->idle_start_watch = e_flag_new ();
idle->idle_thread = g_thread_create ((GThreadFunc) imapx_idle_thread, is, TRUE, NULL);
} else
e_flag_set (idle->idle_start_watch);
- idle->in_idle = TRUE;
-
IDLE_UNLOCK (idle);
}
@@ -2064,7 +2138,7 @@ imapx_in_idle (CamelIMAPXServer *is)
CamelIMAPXIdle *idle = is->idle;
IDLE_LOCK (idle);
- ret = idle->in_idle;
+ ret = (idle->state > IMAPX_IDLE_OFF);
IDLE_UNLOCK (idle);
return ret;