How Tinymail uses signals and observer update notifies

About

Signals are glib's "observer" pattern. In Tinymail both the original observer pattern and these signals are used. The original one where there's a certain importance for the application developer to really understand what will happen. Signals for the typical cases.

The very last moment

In the documentation about asynchronous operations we explained that operations wait in their queue for the callback of the app developer. It's usually right before we call that callback, in the idle callback that will launch it, that we do signal emissions and notify observers. This means that asynchronous operations need to store all their change notifes in order until the callback will happen (after the ending of the actual operation).

The order is important for operations like folder ones: deleting, renaming and creating. Some observers require the order to be correct. For example a rename is a delete of all the kids up to the actual parent folder being renamed, and a create of a parent folder with the creation of all kids that the original parent had. If that would happen out of order, the tree of an observer that is a tree model couldn't be correctly rebuild.

What will be notified?

  • Status notifies (TnyStatus)
  • Callbacks (all async methods)
  • Folder changes (TnyFolderStoreChange)
    • Renames are deletes of all kids and parent and creates of all kids and parent
    • Copies are creates of all kids and parent
    • Creates
    • Deletes
  • Folder content changes (TnyFolderChange)
    • Header expunged
    • Header added
  • Others usually as signals

The GDK lock

Read all about how the GDK lock must be used here. Signal emissions, if you want the application developer to run Gtk+ touching code in the handlers, must protect GDK using the lock. Therefore are all observer update notifies and signal emissions in Tinymail are locking the GDK lock (some aren't yet, at the time of writing this, the vast majority are).

If there's no TnyLockable

if there's no TnyLockable yet the subsystem of Tinymail where the signal is emitted links with Gtk+, gdk_threads_enter and gdk_threads_leave is used. Else, the constructor of the type gets a TnyLockable (or should get, as there's some types at the time of writing this documentation that don't yet do this, like TnyMergeFolder).

static gboolean 
my_op_async_idle (gpointer user_data)
{
	MyType *self = user_data;
	gdk_threads_enter ();
	g_signal_emit (self, SIGNAL_NAME, 0, ...);
	gdk_threads_leave ();
	return FALSE;
}
static void destroy_it (gpointer user_data) 
{
	g_object_unref (user_data); 
}
static gpointer
my_op_thread (gpointer user_data)
{
	MyType *self = user_data;
	g_idle_add_full (G_PRIORITY_DEFAULT, my_op_async_idle, 
		g_object_ref (self), destroy_it)
	g_thread_exit (NULL);
	return NULL;
}

If there's a mainloop running

If there's a mainloop running the emission of the signal should happen in it. You can use g_idle_add(_full) for this. You should still wrap the emission itself with the GDK lock which is explained here.

static gboolean 
my_op_async_idle (gpointer user_data)
{
	MyType *self = user_data;
	tny_lockable_lock (self->session->priv->ui_lock);
	g_signal_emit (self, SIGNAL_NAME, 0, ...);
	tny_lockable_unlock (self->session->priv->ui_lock);
	return FALSE;
}
static void destroy_it (gpointer user_data) 
{
	g_object_unref (user_data); 
}
static gpointer
my_op_thread (gpointer user_data)
{
	MyType *self = user_data;
	g_idle_add_full (G_PRIORITY_DEFAULT, my_op_async_idle, 
		g_object_ref (self), destroy_it)
	g_thread_exit (NULL);
	return NULL;
}

If there's no mainloop running (yet)

If there's no mainloop running, the emission should wrap the GDK lock and emit in the context (the thread) where the state change happened. For example in your TnyDevice implementation it's possible that a state change happens before the application's gtk_main is running. You must correctly handle this (the conic device in the libtinymail-maemo package illustrates this).

/* Try to avoid this! */
static gpointer
my_op_thread (gpointer user_data)
{
	MyType *self = user_data;
	gdk_threads_enter ();
	g_signal_emit (self, SIGNAL_NAME, 0, ...);
	gdk_threads_leave ();	
	g_thread_exit (NULL);
	return NULL;
}

Related other pages