mail-notification

Fork of Jean-Yves Lefort's mail-notification, a tray icon to notify of new mail
git clone https://code.djc.id.au/git/mail-notification/

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 }