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 }