[evolution-data-server] Bug #664793 - Deadlock on EClient operation cancel



commit 2d6f9dcca031268b7563287f0294d768480813a9
Author: Milan Crha <mcrha redhat com>
Date:   Wed Apr 4 19:10:31 2012 +0200

    Bug #664793 - Deadlock on EClient operation cancel

 libedataserver/e-client.c          |   10 ++++++
 libedataserver/e-gdbus-templates.c |   58 ++++++++++++++++++++++++++----------
 libedataserverui/e-client-utils.c  |    3 ++
 3 files changed, 55 insertions(+), 16 deletions(-)
---
diff --git a/libedataserver/e-client.c b/libedataserver/e-client.c
index 0f58720..74d7c02 100644
--- a/libedataserver/e-client.c
+++ b/libedataserver/e-client.c
@@ -880,10 +880,18 @@ struct EClientAuthData {
 static gboolean
 client_process_authentication_idle_cb (gpointer user_data)
 {
+	static gboolean processing_one = FALSE;
 	struct EClientAuthData *auth_data = user_data;
 
 	g_return_val_if_fail (auth_data != NULL, FALSE);
 
+	/* there is one currently processing, postpone this request for later */
+	if (processing_one)
+		return TRUE;
+
+	/* no need for locking, this is always main-thread's idle */
+	processing_one = TRUE;
+
 	if (e_client_emit_authenticate (auth_data->client, auth_data->credentials)) {
 		client_handle_authentication (auth_data->client, auth_data->credentials);
 	} else {
@@ -907,6 +915,8 @@ client_process_authentication_idle_cb (gpointer user_data)
 	g_object_unref (auth_data->client);
 	g_free (auth_data);
 
+	processing_one = FALSE;
+
 	return FALSE;
 }
 
diff --git a/libedataserver/e-gdbus-templates.c b/libedataserver/e-gdbus-templates.c
index fe31eb8..61b594d 100644
--- a/libedataserver/e-gdbus-templates.c
+++ b/libedataserver/e-gdbus-templates.c
@@ -836,6 +836,7 @@ typedef struct _AsyncOpData
 
 	GCancellable *cancellable;
 	gulong cancel_id;
+	guint cancel_idle_id;
 
 	gpointer async_source_tag;
 	GAsyncReadyCallback async_callback;
@@ -857,6 +858,18 @@ async_op_data_free (AsyncOpData *op_data)
 
 	g_return_if_fail (op_data != NULL);
 
+	if (op_data->cancel_idle_id) {
+		GError *error = NULL;
+
+		g_source_remove (op_data->cancel_idle_id);
+		op_data->cancel_idle_id = 0;
+
+		if (!e_gdbus_async_op_keeper_cancel_op_sync (op_data->proxy, op_data->opid, NULL, &error)) {
+			g_debug ("%s: Failed to cancel operation: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
+			g_clear_error (&error);
+		}
+	}
+
 	if (op_data->cancellable) {
 		if (op_data->cancel_id)
 			g_cancellable_disconnect (op_data->cancellable, op_data->cancel_id);
@@ -906,28 +919,41 @@ async_op_complete (AsyncOpData *op_data,
 	g_object_unref (simple);
 }
 
-static void
-e_gdbus_op_cancelled_cb (GCancellable *cancellable,
-                         AsyncOpData *op_data)
+static gboolean
+e_gdbus_op_cancelled_idle_cb (gpointer user_data)
 {
-	GError *call_error = NULL;
-
-	g_return_if_fail (op_data != NULL);
+	AsyncOpData *op_data = user_data;
+	GCancellable *cancellable;
+	GError *error = NULL;
 
-	if (!e_gdbus_async_op_keeper_cancel_op_sync (op_data->proxy, op_data->opid, NULL, &call_error)) {
-		/* only if failed, because otherwise will receive cancelled signal from the server */
-		GError *error = NULL;
+	g_return_val_if_fail (op_data != NULL, FALSE);
 
-		g_return_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
+	cancellable = op_data->cancellable;
+	op_data->cancel_idle_id = 0;
 
-		async_op_complete (op_data, error, TRUE);
-		g_error_free (error);
+	if (!e_gdbus_async_op_keeper_cancel_op_sync (op_data->proxy, op_data->opid, NULL, &error)) {
+		g_debug ("%s: Failed to cancel operation: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
+		g_clear_error (&error);
 	}
 
-	if (call_error) {
-		g_debug ("%s: Failed to cancel operation: %s\n", G_STRFUNC, call_error->message);
-		g_error_free (call_error);
-	}
+	g_return_val_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error), FALSE);
+
+	async_op_complete (op_data, error, TRUE);
+	g_clear_error (&error);
+
+	return FALSE;
+}
+
+static void
+e_gdbus_op_cancelled_cb (GCancellable *cancellable,
+                         AsyncOpData *op_data)
+{
+	g_return_if_fail (op_data != NULL);
+	g_return_if_fail (op_data->cancellable == cancellable);
+
+	/* do this on idle, because this callback should be left
+	   as soon as possible, with no sync calls being done */
+	op_data->cancel_idle_id = g_idle_add (e_gdbus_op_cancelled_idle_cb, op_data);
 }
 
 static void
diff --git a/libedataserverui/e-client-utils.c b/libedataserverui/e-client-utils.c
index 948bc17..3784535 100644
--- a/libedataserverui/e-client-utils.c
+++ b/libedataserverui/e-client-utils.c
@@ -598,6 +598,9 @@ client_utils_open_new_auth_cb (EClient *client,
 	g_return_val_if_fail (async_data != NULL, FALSE);
 	g_return_val_if_fail (async_data->auth_handler != NULL, FALSE);
 
+	if (async_data->cancellable && g_cancellable_is_cancelled (async_data->cancellable))
+		return FALSE;
+
 	if (async_data->used_credentials) {
 		const gchar *reason = e_credentials_peek (async_data->used_credentials, E_CREDENTIALS_KEY_PROMPT_REASON);
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]