Merge lp:~mardy/gnome-control-center-signon/lp1122520 into lp:gnome-control-center-signon

Proposed by Alberto Mardegan
Status: Merged
Approved by: David King
Approved revision: 145
Merged at revision: 142
Proposed branch: lp:~mardy/gnome-control-center-signon/lp1122520
Merge into: lp:gnome-control-center-signon
Diff against target: 149 lines (+42/-11)
2 files modified
libaccount-plugin/oauth-plugin.c (+42/-10)
libaccount-plugin/plugin.c (+0/-1)
To merge this branch: bzr merge lp:~mardy/gnome-control-center-signon/lp1122520
Reviewer Review Type Date Requested Status
David King (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+154912@code.launchpad.net

Commit message

Don't crash when cancelling the authentication or disposing the plugin

This fixes a couple of crashes which would occur:

1) when cancelling an ongoing authentication by pressing the "Cancel" button: this is fixed by tracking the state of the asynchronous operations and delaying the emission of the "finished" signal until they have all completed.

2) when going back to the System Settings: this operation would destroy the plugin, but the asynchronous operations would still continue and the callbacks would be invoked later on an invalid pointer to "self" (see also https://bugzilla.gnome.org/show_bug.cgi?id=696369). This is fixed by guarding the "self" pointer with a weak GObject pointer.

Description of the change

Don't crash when cancelling the authentication or disposing the plugin

This fixes a couple of crashes which would occur:

1) when cancelling an ongoing authentication by pressing the "Cancel" button: this is fixed by tracking the state of the asynchronous operations and delaying the emission of the "finished" signal until they have all completed.

2) when going back to the System Settings: this operation would destroy the plugin, but the asynchronous operations would still continue and the callbacks would be invoked later on an invalid pointer to "self" (see also https://bugzilla.gnome.org/show_bug.cgi?id=696369). This is fixed by guarding the "self" pointer with a weak GObject pointer.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
David King (amigadave) wrote :

I have a few reservations about this approach. This situation is broadly covered by an email that Alexander Larsson wrote about how to cancel asynchronous GIO operations:

https://mail.gnome.org/archives/gtk-devel-list/2010-April/msg00116.html

There is some good explanation there (and earlier in the thread) about this sort of occurrence which tends to support an approach of delaying finalization until all asynchronous operations owned by an object are finished.

Specifically, my concern with this approach is that the asynchronous completion callback will always be called, and therefore guarding access to the "self" pointer inside the callbacks is bogus. To put it another way, g_return_if_fail() is supposed to represent a programming error, and this change introduces an error in the case of going back to All Settings in System Settings. :-)

I think this is a neat approach for solving (1) in your description, but I do not think it is appropriate for solving (2). There, I think that my approach of delaying dispose/finalize is a better option.

Revision history for this message
Alberto Mardegan (mardy) wrote :

I'm afraid that this might turn into a philosophical discussion :-)

First, I think we need to recognize that this is a hack around what IMHO is a wrong practice yet relatively common in GLib: continuing keeping an object alive (and kicking at callbacks!) after all explicit references to it have been dropped.

I agree with most of what Alex was saying in that thread; I actually even considered the idea of running the main loop in the dispose method, but quickly discarded it. :-)

However, your criticism seems to be based on a misunderstanding: the g_return_if_fail() is used only as a non-essential extra check, just for safety. The line above it actually checks whether the pointer is NULL (and this is what happens if the object has been destroyed), so that g_return_if_fail() will never fail under normal usage (if you actually saw it emitting a critical message on the terminal, then it means that my solution is buggy).

So, what this fix does is to set that pointer to NULL if the object has been disposed, so that all callbacks happening after it don't perform any action.

However, I realized just now that this would introduce a memory leak. I'll get it fixed soon :-)

144. By Alberto Mardegan

Don't leak errors and GSimpleAsync results

Remove the macros, to make the code more explicit.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
David King (amigadave) wrote :

> I'm afraid that this might turn into a philosophical discussion :-)

Sounds interesting. :-)

> First, I think we need to recognize that this is a hack around what IMHO is a
> wrong practice yet relatively common in GLib: continuing keeping an object
> alive (and kicking at callbacks!) after all explicit references to it have
> been dropped.

This is true, but with one caveat: "Object methods should be able to run without program error in-between [dispose and finalize]." https://developer.gnome.org/gobject/stable/gobject-memory.html#gobject-memory-cycles

> I agree with most of what Alex was saying in that thread; I actually even
> considered the idea of running the main loop in the dispose method, but
> quickly discarded it. :-)
>
> However, your criticism seems to be based on a misunderstanding: the
> g_return_if_fail() is used only as a non-essential extra check, just for
> safety.

My mistake, thanks for pointing it out.

> So, what this fix does is to set that pointer to NULL if the object has been
> disposed, so that all callbacks happening after it don't perform any action.

Indeed, and this is the part of the approach that I disagree with. Admittedly, the object method will not access invalid memory, but checking whether the object is valid in a "private" method is a symptom to me that something is wrong. A neater solution might be for the asynchronous operations to own (or hold a reference to) everything required to complete the operation, or one of the other solutions mentioned in the mailing list thread.

145. By Alberto Mardegan

Don't use weak pointers, but check on the error type

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
David King (amigadave) wrote :

Much nicer!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libaccount-plugin/oauth-plugin.c'
2--- libaccount-plugin/oauth-plugin.c 2013-02-25 12:03:41 +0000
3+++ libaccount-plugin/oauth-plugin.c 2013-03-22 13:53:19 +0000
4@@ -62,6 +62,8 @@
5 GCancellable *cancellable;
6 gboolean identity_stored;
7 gboolean deleting_identity;
8+ gboolean authenticating;
9+ gboolean storing_account;
10 };
11
12 static const gchar signon_id[] = AP_PLUGIN_CREDENTIALS_ID_FIELD;
13@@ -136,25 +138,34 @@
14 {
15 ApOAuthPluginPrivate *priv = self->priv;
16
17- if (!priv->deleting_identity)
18- {
19- g_idle_add ((GSourceFunc)emit_finished, self);
20- }
21+ /* Check if some asynchronous operations are still running */
22+ if (priv->authenticating) return;
23+ if (priv->storing_account) return;
24+ if (priv->deleting_identity) return;
25+
26+ g_idle_add ((GSourceFunc)emit_finished, self);
27 }
28
29 static void
30 identity_removed_cb (SignonIdentity *identity, const GError *error,
31 gpointer user_data)
32 {
33- ApOAuthPlugin *self = AP_OAUTH_PLUGIN (user_data);
34+ ApOAuthPlugin *self;
35
36 if (G_UNLIKELY (error != NULL))
37 {
38+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
39+ {
40+ return;
41+ }
42 g_critical ("Error removing identity: %s", error->message);
43 /* No special handling, we are quitting anyways */
44 }
45
46+ self = AP_OAUTH_PLUGIN (user_data);
47+
48 self->priv->deleting_identity = FALSE;
49+ self->priv->identity_stored = FALSE;
50 finish_if_ready (self);
51 }
52
53@@ -169,7 +180,8 @@
54 }
55
56 if (priv->identity != NULL &&
57- priv->identity_stored)
58+ priv->identity_stored &&
59+ !priv->deleting_identity)
60 {
61 priv->deleting_identity = TRUE;
62 signon_identity_remove (priv->identity, identity_removed_cb, self);
63@@ -196,10 +208,19 @@
64 account_store_cb (GObject *source_object, GAsyncResult *res,
65 gpointer user_data)
66 {
67- ApOAuthPlugin *self = AP_OAUTH_PLUGIN (user_data);
68+ ApOAuthPlugin *self;
69 GError *error = NULL;
70
71 ag_account_store_finish (AG_ACCOUNT (source_object), res, &error);
72+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
73+ {
74+ g_error_free (error);
75+ return;
76+ }
77+
78+ self = AP_OAUTH_PLUGIN (user_data);
79+ self->priv->storing_account = FALSE;
80+
81 if (G_UNLIKELY (error != NULL))
82 {
83 g_critical ("Account write error: %s", error->message);
84@@ -242,7 +263,6 @@
85 ApOAuthPluginPrivate *priv = self->priv;
86 GHashTableIter iter;
87 gpointer key, value;
88- GHashTable *parameters;
89 gchar *full_key;
90 const gchar *mechanism;
91 const gchar *username;
92@@ -286,6 +306,7 @@
93 account = ap_plugin_get_account ((ApPlugin *)self);
94 store_authentication_parameters (self, account);
95
96+ self->priv->storing_account = TRUE;
97 ag_account_store_async (account, self->priv->cancellable,
98 account_store_cb, self);
99 }
100@@ -329,7 +350,7 @@
101 GAsyncResult *res,
102 gpointer user_data)
103 {
104- ApOAuthPlugin *self = AP_OAUTH_PLUGIN (user_data);
105+ ApOAuthPlugin *self;
106 SignonAuthSession *auth_session = SIGNON_AUTH_SESSION (source_object);
107 AgAccount *account;
108 GVariant *session_data;
109@@ -344,6 +365,15 @@
110 g_variant_unref (session_data);
111 }
112
113+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
114+ {
115+ g_error_free (error);
116+ return;
117+ }
118+
119+ self = AP_OAUTH_PLUGIN (user_data);
120+ self->priv->authenticating = FALSE;
121+
122 if (G_UNLIKELY (error != NULL))
123 {
124 if (error->domain == SIGNON_ERROR &&
125@@ -473,10 +503,12 @@
126 g_clear_error (&error);
127 return;
128 }
129+ priv->authenticating = TRUE;
130 signon_auth_session_process_async (priv->auth_session, session_data,
131 get_mechanism (priv),
132 priv->cancellable,
133- auth_session_process_cb, self);
134+ auth_session_process_cb,
135+ self);
136 }
137
138 static void
139
140=== modified file 'libaccount-plugin/plugin.c'
141--- libaccount-plugin/plugin.c 2013-01-24 12:09:04 +0000
142+++ libaccount-plugin/plugin.c 2013-03-22 13:53:19 +0000
143@@ -267,7 +267,6 @@
144 {
145 ApPluginPrivate *priv = self->priv;
146 GVariant *v_id;
147- AgSettingSource source;
148 GSimpleAsyncResult *result;
149 gboolean deleting_identity = FALSE;
150

Subscribers

People subscribed via source and target branches