src/mn-locked-callback.c (8688B) - raw
1 /*
2 * Mail Notification
3 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <glib.h>
21 #include <libgnomevfs/gnome-vfs.h>
22 #include "mn-locked-callback.h"
23 #include "mn-conf.h"
24 #include "mn-util.h"
25
26 /*
27 * About MNLockedCallback:
28 *
29 * Consider the following code:
30 *
31 * static gboolean idle_cb (gpointer data)
32 * {
33 * MyObject *object = data;
34 *
35 * GDK_THREADS_ENTER();
36 * ...do something with object
37 * GDK_THREADS_LEAVE();
38 *
39 * return TRUE;
40 * }
41 *
42 * static void my_object_init (MyObject *object)
43 * {
44 * object->idle_id = g_idle_add(idle_cb, object);
45 * }
46 *
47 * static void my_object_finalize (GObject *object)
48 * {
49 * g_source_remove(MY_OBJECT(object)->idle_id);
50 * G_OBJECT_CLASS(parent_class)->finalize(object);
51 * }
52 *
53 * Timeline of events:
54 *
55 * 1) in thread x: we grab the GDK lock
56 * 2) in the main_thread: idle_cb() is executed and waits to acquire
57 * the lock
58 * 3) in thread x: we do something which causes the object instance
59 * to be finalized, and we release the lock
60 * 4) in the main thread: idle_cb() acquires the lock and crashes
61 * because the object instance has been destroyed while it was
62 * waiting for the lock
63 *
64 * This code is therefore unsafe if MyObject can be destroyed from
65 * another thread than the thread running the GLib main loop.
66 *
67 * This observation can be generalized as follows: holding a lock in a
68 * callback and unregistering the callback from another thread with
69 * the same lock held is not thread-safe.
70 *
71 * As of version 2.12, GTK+ is no longer affected by this problem
72 * since the gdk_threads_add_*() family of functions has been added
73 * (see http://bugzilla.gnome.org/show_bug.cgi?id=321886).
74 *
75 * However, most GTK+-based libraries (libgnomeui, libeel, ...) are
76 * still affected. MNLockedCallback provides solutions for the
77 * components used by MN (GConf, GnomeVFS).
78 *
79 * Note that throughout Mail Notification, we always use our
80 * MNLockedCallback variants (except of course for callbacks which do
81 * not need to hold a lock), even in objects which cannot be destroyed
82 * from a thread and are therefore not affected by the problem. It is
83 * safer and more maintainable than having to follow the code path to
84 * decide whether the stock library functions can safely be used or
85 * not, and the MNLockedCallback overhead is neglectible.
86 */
87
88 typedef struct
89 {
90 gboolean removed;
91 gpointer function;
92 gpointer data;
93 gpointer handle;
94 } LockedCallback;
95
96 static GHashTable *vfs_monitors = NULL;
97 G_LOCK_DEFINE_STATIC(vfs_monitors);
98
99 static void
100 gconf_notification_cb (GConfClient *client,
101 unsigned int cnxn_id,
102 GConfEntry *entry,
103 gpointer user_data)
104 {
105 LockedCallback *callback = user_data;
106
107 GDK_THREADS_ENTER();
108
109 if (! callback->removed)
110 ((GConfClientNotifyFunc) callback->function)(client, cnxn_id, entry, callback->data);
111
112 GDK_THREADS_LEAVE();
113 }
114
115 static void
116 gconf_notification_weak_notify_cb (gpointer data, GObject *former_object)
117 {
118 LockedCallback *callback = data;
119
120 callback->removed = TRUE;
121 mn_conf_notification_remove(GPOINTER_TO_UINT(callback->handle));
122 }
123
124 static void
125 locked_callback_free (LockedCallback *callback)
126 {
127 g_return_if_fail(callback != NULL);
128
129 g_free(callback);
130 }
131
132 /**
133 * mn_g_object_gconf_notification_add_gdk_locked:
134 * @object: a GObject-derived instance
135 * @key: a GConf key or namespace section
136 * @function: function to call when changes occur
137 * @user_data: data to pass to @function
138 *
139 * Monitors the GConf key or namespace @key for changes. The @function
140 * invocation will be wrapped with GDK_THREADS_ENTER() and
141 * GDK_THREADS_LEAVE(). The notification will be removed when @object
142 * is finalized. If after acquiring the GDK lock, the object has been
143 * removed, @function will not be executed.
144 **/
145 void
146 mn_g_object_gconf_notification_add_gdk_locked (gpointer object,
147 const char *key,
148 GConfClientNotifyFunc function,
149 gpointer user_data)
150 {
151 LockedCallback *callback;
152
153 g_return_if_fail(G_IS_OBJECT(object));
154 g_return_if_fail(key != NULL);
155 g_return_if_fail(function != NULL);
156
157 callback = g_new0(LockedCallback, 1);
158 callback->function = function;
159 callback->data = user_data;
160 callback->handle = GUINT_TO_POINTER(mn_conf_notification_add(key, gconf_notification_cb, callback, (GDestroyNotify) locked_callback_free));
161
162 g_object_weak_ref(G_OBJECT(object), gconf_notification_weak_notify_cb, callback);
163 }
164
165 /**
166 * mn_g_object_gconf_notifications_add_gdk_locked:
167 * @object: a GObject-derived instance
168 * @...: a %NULL-terminated list of key/function/user_data tuples
169 *
170 * Adds multiple GConf notifications with
171 * mn_g_object_gconf_notification_add_gdk_locked().
172 **/
173 void
174 mn_g_object_gconf_notifications_add_gdk_locked (gpointer object, ...)
175 {
176 va_list args;
177 const char *key;
178
179 g_return_if_fail(G_IS_OBJECT(object));
180
181 va_start(args, object);
182
183 while ((key = va_arg(args, const char *)))
184 {
185 GConfClientNotifyFunc function;
186 gpointer user_data;
187
188 function = va_arg(args, GConfClientNotifyFunc);
189 g_return_if_fail(function != NULL);
190
191 user_data = va_arg(args, gpointer);
192
193 mn_g_object_gconf_notification_add_gdk_locked(object, key, function, user_data);
194 }
195
196 va_end(args);
197 }
198
199 static void
200 vfs_monitor_cb (GnomeVFSMonitorHandle *handle,
201 const char *monitor_uri,
202 const char *info_uri,
203 GnomeVFSMonitorEventType event_type,
204 gpointer user_data)
205 {
206 GnomeVFSMonitorCallback callback;
207
208 G_LOCK(vfs_monitors);
209 callback = g_hash_table_lookup(vfs_monitors, handle);
210 if (callback)
211 callback(handle, monitor_uri, info_uri, event_type, user_data);
212 G_UNLOCK(vfs_monitors);
213 }
214
215 /**
216 * mn_gnome_vfs_monitor_add_locked:
217 * @handle: a location to return the monitor handle on success
218 * @text_uri: URI to monitor
219 * @monitor_type: monitor type
220 * @callback: function to call
221 * @user_data: data to pass to @function
222 *
223 * Monitors @text_uri and invokes @callback when a change occurs.
224 * Callback invocations will be protected by an internal global lock,
225 * and will not occur if the monitor has been removed.
226 *
227 * Return value: the status of the operation
228 **/
229 GnomeVFSResult
230 mn_gnome_vfs_monitor_add_locked (GnomeVFSMonitorHandle **handle,
231 const char *text_uri,
232 GnomeVFSMonitorType monitor_type,
233 GnomeVFSMonitorCallback callback,
234 gpointer user_data)
235 {
236 GnomeVFSMonitorHandle *_handle;
237 GnomeVFSResult result;
238
239 g_return_val_if_fail(handle != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
240 g_return_val_if_fail(text_uri != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
241 g_return_val_if_fail(callback != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
242
243 /*
244 * We need a global monitor hash table because
245 * gnome_vfs_monitor_add() has no destroy_data parameter.
246 */
247
248 G_LOCK(vfs_monitors);
249 result = gnome_vfs_monitor_add(&_handle, text_uri, monitor_type, vfs_monitor_cb, user_data);
250 if (result == GNOME_VFS_OK)
251 {
252 *handle = _handle;
253
254 if (! vfs_monitors)
255 vfs_monitors = g_hash_table_new(g_direct_hash, g_direct_equal);
256
257 g_hash_table_insert(vfs_monitors, _handle, callback);
258 }
259 G_UNLOCK(vfs_monitors);
260
261 return result;
262 }
263
264 /**
265 * mn_gnome_vfs_monitor_cancel_locked:
266 * @handle: handle of the monitor to cancel
267 *
268 * Cancels the monitor pointed to by @handle (which must have been
269 * returned by a call to mn_gnome_vfs_monitor_add_locked()), using a
270 * global lock to make sure that its callback function will no longer
271 * be executed.
272 *
273 * Return value: the status of the operation
274 **/
275 GnomeVFSResult
276 mn_gnome_vfs_monitor_cancel_locked (GnomeVFSMonitorHandle *handle)
277 {
278 GnomeVFSResult result;
279
280 g_return_val_if_fail(handle != NULL, GNOME_VFS_ERROR_BAD_PARAMETERS);
281
282 G_LOCK(vfs_monitors);
283 g_assert(g_hash_table_lookup(vfs_monitors, handle) != NULL);
284 g_hash_table_remove(vfs_monitors, handle);
285 result = gnome_vfs_monitor_cancel(handle);
286 G_UNLOCK(vfs_monitors);
287
288 return result;
289 }