[gcr] gcr: Cancel the prompt when prompter goes away



commit 064d7588d1272481b22e70ed2c3901e7673b23f7
Author: Stef Walter <stefw gnome org>
Date:   Mon Aug 20 16:03:48 2012 +0200

    gcr: Cancel the prompt when prompter goes away
    
     * If the prompter quits, cancel any prompting that's going on.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=684478

 gcr/gcr-base.symbols           |    1 +
 gcr/gcr-mock-prompter.c        |   36 +++++++++++++---
 gcr/gcr-mock-prompter.h        |    2 +
 gcr/gcr-system-prompt.c        |   95 +++++++++++++++++++++++++++++++++++++---
 gcr/tests/test-system-prompt.c |   84 +++++++++++++++++++++++++++++++++++
 5 files changed, 206 insertions(+), 12 deletions(-)
---
diff --git a/gcr/gcr-base.symbols b/gcr/gcr-base.symbols
index c794829..35a74a1 100644
--- a/gcr/gcr-base.symbols
+++ b/gcr/gcr-base.symbols
@@ -111,6 +111,7 @@ gcr_mock_prompter_is_prompting
 gcr_mock_prompter_set_delay_msec
 gcr_mock_prompter_start
 gcr_mock_prompter_stop
+gcr_mock_prompter_disconnect
 gcr_parsed_get_attributes
 gcr_parsed_get_data
 gcr_parsed_get_description
diff --git a/gcr/gcr-mock-prompter.c b/gcr/gcr-mock-prompter.c
index 564ed2d..31cbba2 100644
--- a/gcr/gcr-mock-prompter.c
+++ b/gcr/gcr-mock-prompter.c
@@ -105,7 +105,7 @@ typedef struct {
 
 	/* Owned by the prompter thread*/
 	GcrSystemPrompter *prompter;
-	const gchar *bus_name;
+	GDBusConnection *connection;
 	GMainLoop *loop;
 } ThreadData;
 
@@ -943,9 +943,9 @@ mock_prompter_thread (gpointer data)
 		                                                     G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
 		                                                     NULL, NULL, &error);
 		if (error == NULL) {
+			thread_data->connection = connection;
 			gcr_system_prompter_register (GCR_SYSTEM_PROMPTER (thread_data->prompter),
 			                              connection);
-			thread_data->bus_name = g_dbus_connection_get_unique_name (connection);
 		} else {
 			g_critical ("couldn't create connection: %s", error->message);
 			g_error_free (error);
@@ -978,10 +978,19 @@ mock_prompter_thread (gpointer data)
 	thread_data->prompter = NULL;
 
 	if (connection) {
-		if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
-			g_critical ("connection flush failed: %s", error->message);
-			g_error_free (error);
+		thread_data->connection = NULL;
+
+		if (!g_dbus_connection_is_closed (connection)) {
+			if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
+				g_critical ("connection flush failed: %s", error->message);
+				g_error_free (error);
+			}
+			if (!g_dbus_connection_close_sync (connection, NULL, &error)) {
+				g_critical ("connection close failed: %s", error->message);
+				g_error_free (error);
+			}
 		}
+
 		g_object_unref (connection);
 	}
 
@@ -1030,7 +1039,22 @@ gcr_mock_prompter_start (void)
 	g_assert (running->prompter);
 	g_mutex_unlock (running->mutex);
 
-	return running->bus_name;
+	return g_dbus_connection_get_unique_name (running->connection);
+}
+
+void
+gcr_mock_prompter_disconnect (void)
+{
+	GError *error = NULL;
+
+	g_assert (running != NULL);
+	g_assert (running->connection);
+
+	g_dbus_connection_close_sync (running->connection, NULL, &error);
+	if (error != NULL) {
+		g_critical ("disconnect connection close failed: %s", error->message);
+		g_error_free (error);
+	}
 }
 
 /**
diff --git a/gcr/gcr-mock-prompter.h b/gcr/gcr-mock-prompter.h
index 3e2cf26..f89f762 100644
--- a/gcr/gcr-mock-prompter.h
+++ b/gcr/gcr-mock-prompter.h
@@ -36,6 +36,8 @@ G_BEGIN_DECLS
 
 const gchar *        gcr_mock_prompter_start                     (void);
 
+void                 gcr_mock_prompter_disconnect                (void);
+
 void                 gcr_mock_prompter_stop                      (void);
 
 gboolean             gcr_mock_prompter_is_prompting              (void);
diff --git a/gcr/gcr-system-prompt.c b/gcr/gcr-system-prompt.c
index 640aa77..b370b1c 100644
--- a/gcr/gcr-system-prompt.c
+++ b/gcr/gcr-system-prompt.c
@@ -143,8 +143,10 @@ static gint unique_prompt_id = 0;
 
 typedef struct {
 	GSource *timeout;
+	GSource *waiting;
 	GMainContext *context;
 	GCancellable *cancellable;
+	guint watch_id;
 } CallClosure;
 
 static void
@@ -153,11 +155,45 @@ call_closure_free (gpointer data)
 	CallClosure *closure = data;
 	if (closure->timeout)
 		g_source_destroy (closure->timeout);
-	g_clear_object (&closure->cancellable);
+	if (closure->waiting)
+		g_source_destroy (closure->waiting);
+	if (closure->watch_id)
+		g_bus_unwatch_name (closure->watch_id);
+	g_object_unref (closure->cancellable);
 	g_free (data);
 }
 
 static void
+on_propagate_cancelled (GCancellable *cancellable,
+                        gpointer user_data)
+{
+	/* Propagate the cancelled signal */
+	GCancellable *cancel = G_CANCELLABLE (user_data);
+	g_cancellable_cancel (cancel);
+}
+
+static CallClosure *
+call_closure_new (GCancellable *cancellable)
+{
+	CallClosure *call;
+
+	/*
+	 * We use our own cancellable object, since we cancel it it in
+	 * situations other than when the caller cancels.
+	 */
+
+	call = g_new0 (CallClosure, 1);
+	call->cancellable = g_cancellable_new ();
+
+	if (cancellable) {
+		g_cancellable_connect (cancellable, G_CALLBACK (on_propagate_cancelled),
+		                       g_object_ref (call->cancellable), g_object_unref);
+	}
+
+	return call;
+}
+
+static void
 gcr_system_prompt_init (GcrSystemPrompt *self)
 {
 	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_SYSTEM_PROMPT,
@@ -742,6 +778,16 @@ register_prompt_object (GcrSystemPrompt *self,
 }
 
 static void
+on_prompter_vanished (GDBusConnection *connection,
+                      const gchar *name,
+                      gpointer user_data)
+{
+	GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+	CallClosure *call = g_simple_async_result_get_op_res_gpointer (async);
+	g_cancellable_cancel (call->cancellable);
+}
+
+static void
 on_bus_connected (GObject *source,
                   GAsyncResult *result,
                   gpointer user_data)
@@ -763,6 +809,12 @@ on_bus_connected (GObject *source,
 
 		g_main_context_push_thread_default (closure->context);
 
+		closure->watch_id = g_bus_watch_name_on_connection (self->pv->connection,
+		                                                    self->pv->prompter_bus_name,
+		                                                    G_BUS_NAME_WATCHER_FLAGS_NONE,
+		                                                    NULL, on_prompter_vanished,
+		                                                    res, NULL);
+
 		register_prompt_object (self, &error);
 
 		g_main_context_pop_thread_default (closure->context);
@@ -835,6 +887,27 @@ on_call_timeout (gpointer user_data)
 	return FALSE; /* Don't call this function again */
 }
 
+static gboolean
+on_call_cancelled (GCancellable *cancellable,
+                   gpointer user_data)
+{
+	GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
+	CallClosure *call = g_simple_async_result_get_op_res_gpointer (async);
+	GcrSystemPrompt *self = GCR_SYSTEM_PROMPT (g_async_result_get_source_object (user_data));
+
+	g_source_destroy (call->waiting);
+	call->waiting = NULL;
+
+	g_simple_async_result_set_error (async, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+	                                 _("The operation was cancelled"));
+
+	/* Tell the prompter we're no longer interested */
+	gcr_system_prompt_close_async (self, NULL, NULL, NULL);
+
+	g_object_unref (self);
+	return FALSE; /* Don't call this function again */
+}
+
 void
 perform_init_async (GcrSystemPrompt *self,
                     GSimpleAsyncResult *res)
@@ -896,7 +969,7 @@ gcr_system_prompt_real_init_async (GAsyncInitable *initable,
 
 	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
 	                                 gcr_system_prompt_real_init_async);
-	closure = g_new0 (CallClosure, 1);
+	closure = call_closure_new (cancellable);
 	closure->context = g_main_context_get_thread_default ();
 	if (closure->context)
 		g_main_context_ref (closure->context);
@@ -1029,6 +1102,7 @@ on_perform_prompt_complete (GObject *source,
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
 	GcrSystemPrompt *self = GCR_SYSTEM_PROMPT (g_async_result_get_source_object (user_data));
+	CallClosure *call = g_simple_async_result_get_op_res_gpointer (res);
 	GError *error = NULL;
 	GVariant *retval;
 
@@ -1037,6 +1111,11 @@ on_perform_prompt_complete (GObject *source,
 		self->pv->pending = NULL;
 		g_simple_async_result_take_error (res, error);
 		g_simple_async_result_complete (res);
+	} else {
+		g_assert (call->waiting == NULL);
+		call->waiting = g_cancellable_source_new (call->cancellable);
+		g_source_set_callback (call->waiting, (GSourceFunc)on_call_cancelled, res, NULL);
+		g_source_attach (call->waiting, call->context);
 	}
 
 	if (retval)
@@ -1069,8 +1148,7 @@ perform_prompt_async (GcrSystemPrompt *self,
 	}
 
 	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, source_tag);
-	closure = g_new0 (CallClosure, 1);
-	closure->cancellable = cancellable ? g_object_ref (cancellable) : cancellable;
+	closure = call_closure_new (cancellable);
 	g_simple_async_result_set_op_res_gpointer (res, closure, call_closure_free);
 
 	if (self->pv->closed) {
@@ -1088,6 +1166,12 @@ perform_prompt_async (GcrSystemPrompt *self,
 	else
 		sent = gcr_secret_exchange_begin (exchange);
 
+	closure->watch_id = g_bus_watch_name_on_connection (self->pv->connection,
+	                                                    self->pv->prompter_bus_name,
+	                                                    G_BUS_NAME_WATCHER_FLAGS_NONE,
+	                                                    NULL, on_prompter_vanished,
+	                                                    res, NULL);
+
 	builder = build_dirty_properties (self);
 
 	/* Reregister the prompt object in the current GMainContext */
@@ -1470,8 +1554,7 @@ gcr_system_prompt_close_async (GcrSystemPrompt *self,
 
 	res = g_simple_async_result_new (NULL, callback, user_data,
 	                                 gcr_system_prompt_close_async);
-	closure = g_new0 (CallClosure, 1);
-	closure->cancellable = cancellable ? g_object_ref (cancellable) : g_cancellable_new ();
+	closure = call_closure_new (cancellable);
 	closure->context = g_main_context_get_thread_default ();
 	if (closure->context != NULL)
 		g_main_context_ref (closure->context);
diff --git a/gcr/tests/test-system-prompt.c b/gcr/tests/test-system-prompt.c
index 0d48124..b5cae2c 100644
--- a/gcr/tests/test-system-prompt.c
+++ b/gcr/tests/test-system-prompt.c
@@ -641,6 +641,89 @@ test_after_close_dismisses (Test *test,
 	egg_assert_not_object (prompt);
 }
 
+typedef struct {
+	GAsyncResult *result1;
+	GAsyncResult *result2;
+} ResultPair;
+
+static void
+on_result_pair_one (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
+{
+	ResultPair *pair = user_data;
+	g_assert (pair->result1 == NULL);
+	pair->result1 = g_object_ref (result);
+	if (pair->result1 && pair->result2)
+		egg_test_wait_stop ();
+}
+
+static void
+on_result_pair_two (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
+{
+	ResultPair *pair = user_data;
+	g_assert (pair->result2 == NULL);
+	pair->result2 = g_object_ref (result);
+	if (pair->result1 && pair->result2)
+		egg_test_wait_stop ();
+}
+
+static void
+test_watch_cancels (Test *test,
+                    gconstpointer unused)
+{
+	GDBusConnection *connection;
+	GcrPrompt *prompt;
+	GcrPrompt *prompt2;
+	GError *error = NULL;
+	const gchar *password;
+	ResultPair pair = { NULL, NULL };
+
+	gcr_mock_prompter_set_delay_msec (3000);
+	gcr_mock_prompter_expect_password_ok ("booo", NULL);
+
+	connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+	g_assert_no_error (error);
+
+	/* This should happen immediately */
+	prompt = gcr_system_prompt_open_for_prompter (test->prompter_name, 0, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (GCR_IS_SYSTEM_PROMPT (prompt));
+
+	/* Show a password prompt */
+	gcr_prompt_password_async (prompt, NULL, on_result_pair_one, &pair);
+
+	/* This prompt should wait, block */
+	gcr_system_prompt_open_for_prompter_async (test->prompter_name, 0, NULL,
+	                                           on_result_pair_two, &pair);
+
+	/* Wait a bit before stopping, so outgoing request is done */
+	g_usleep (G_TIME_SPAN_SECOND / 4);
+
+	/* Kill the mock prompter */
+	gcr_mock_prompter_disconnect ();
+
+	/* Both the above operations should cancel */
+	egg_test_wait ();
+
+	prompt2 = gcr_system_prompt_open_finish (pair.result2, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_clear_error (&error);
+	g_assert (prompt2 == NULL);
+
+	password = gcr_prompt_password_finish (prompt, pair.result1, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_clear_error (&error);
+	g_assert (password == NULL);
+
+	g_object_unref (prompt);
+	g_object_unref (pair.result1);
+	g_object_unref (pair.result2);
+	g_object_unref (connection);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -667,6 +750,7 @@ main (int argc, char **argv)
 	g_test_add ("/gcr/system-prompt/close-cancels", Test, NULL, setup, test_close_cancels, teardown);
 	g_test_add ("/gcr/system-prompt/after-close-dismisses", Test, NULL, setup, test_after_close_dismisses, teardown);
 	g_test_add ("/gcr/system-prompt/close-from-prompter", Test, NULL, setup, test_close_from_prompter, teardown);
+	g_test_add ("/gcr/system-prompt/watch-cancels", Test, NULL, setup, test_watch_cancels, teardown);
 
 	return egg_tests_run_with_loop ();
 }



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