How Tinymail implements Gdk-lock awareness
About
This is internal documentation of Tinymail's internals. It has very few to do with what application developers need to know to develop an application on top of this.
First, I'll point you to some documentation of the Gtk+ toolkit:
- http://live.gnome.org/GdkLock
- http://live.gnome.org/NonGdkLibsWithGdkLock
- http://developer.gnome.org/doc/API/2.0/gdk/gdk-Threads.html
The Gtk+ and Gdk subsystems require that you perform code that touches them in the Gdk lock. The base libraries of Tinymail don't link with Gdk so the functionalities of Gdk to cope with this had to be abstracted. This is why there's a TnyLockable type in libtinymail and a TnyGtkLockable implementation in libtinymailui-gtk.
How is it used?
Callbacks
In Asynchronous operations and Asynchronous connecting we already discussed how operations are invoked asynchronously on a queue. These operations perform a callback to the application developer's application. We allow these callbacks to contain code that touches the Gtk+ and Gdk subsystems. Therefore we must correctly use Gdk's locking infrastructure.
The TnySessionCamel gets the ui locker set using tny_session_camel_set_ui_lock. Usually the application developer sets this in his TnyAccountStore implementation. On that instance we use the tny_lockable_lock and tny_lockable_unlock API:
static gboolean
my_op_async_callback (gpointer user_data)
{
my_op_info *info = user_data;
if (info->callback) {
tny_lockable_lock (info->session->priv->ui_lock);
info->actual_callback (info->self, info->user_data);
tny_lockable_unlock (info->session->priv->ui_lock);
}
return;
}
Signals
Usually signals's moment of "having to happen" happens in a thread, in Tinymail. This means that we usually escape this thread using g_idle_add_full. In idle's callbacks we need to acquire the Gdk lock. Again we wrap the call with tny_lockable_lock and tny_lockable_unlock. Note that there are some places in Tinymail, mostly the platform specific ones, of which we know that they link with Gdk, where Tinymail calls gdk_threads_enter and gdk_threads_leave directly.
More about signals here.
static gboolean
changed_idle (gpointer data)
{
TnyAccount *self = (TnyAccount *) data;
TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (self);
tny_lockable_lock (apriv->session->priv->ui_lock);
g_signal_emit (G_OBJECT (self),
tny_account_signals [TNY_ACCOUNT_CHANGED], 0);
tny_lockable_unlock (apriv->session->priv->ui_lock);
return FALSE;
}
Observer's update invocations
An observable that notifies its observers is exactly like signal emissions (in fact, it's more or less the exact same thing). This means that the application developer can implement the update() method of the observer. This means that we must allow Gtk+ and Gdk subsystems to be touched in that implementation. This means that we should run it in the GMainLoop and that we should acquire Gdk's lock.
Both TnyCamelFolder and TnyCamelStoreAccount have helper functions like notify_folder_observers_about and notify_folder_observers_about_in_idle. The notify_folder_observers_about is only called from a callback handler (so it happens in the mainloop already) and the notify_folder_observers_about_in_idle is used in any other case. This one launches a g_idle_add_full and forces the event to happen in the GMainLoop.
The implementation is like this. You'll notice that we copy the list in stead of iterating over the observers immediately. This is done to shorten the obs_lock (so that the lock is not held while the updates of the observers happen).
static void
notify_folder_observers_about (TnyFolder *self, TnyFolderChange *change)
{
TnyCamelFolderPriv *priv = TNY_CAMEL_FOLDER_GET_PRIVATE (self);
TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (priv->account);
TnyIterator *iter;
TnyList *list;
g_static_rec_mutex_lock (priv->obs_lock);
if (!priv->observers) {
g_static_rec_mutex_unlock (priv->obs_lock);
return;
}
list = tny_list_copy (priv->observers);
g_static_rec_mutex_unlock (priv->obs_lock);
iter = tny_list_create_iterator (list);
while (!tny_iterator_is_done (iter))
{
TnyFolderObserver *observer = TNY_FOLDER_OBSERVER (tny_iterator_get_current (iter));
tny_lockable_lock (apriv->session->priv->ui_lock);
tny_folder_observer_update (observer, change);
tny_lockable_unlock (apriv->session->priv->ui_lock);
g_object_unref (observer);
tny_iterator_next (iter);
}
g_object_unref (iter);
g_object_unref (list);
return;
}
