Porting async calls to GTask: newcomers welcome!
Aleksander Morgado
aleksander at aleksander.es
Sat Jan 30 19:27:12 PST 2016
Hey hey,
ModemManager git master is already using GTask already for some
things, instead of the good old GSimpleAsyncResult. GTask was released
in GLib/GIO 2.36, and it has a really good API, including built-in
cancellation support.
https://developer.gnome.org/gio/stable/GTask.html
Porting to GTask all the ModemManager code is a huge task, and
definitely not a high priority thing, but it really is something good
to have. So, if there is anyone out there willing to help with this,
it's more than appreciated.
Given the scope of the work, it can really be done per-object, as the
object API is anyway kept the same. As an example of the work needed,
I ported the methods in MMSimQmi to using GTask:
http://cgit.freedesktop.org/ModemManager/ModemManager/commit/?id=aeb63621723fd3d7e39870466d2cffcf56fc6570
Both GTask and GSimpleAsyncResult implement the GAsyncResult
interface, which means that the API of the methods don't need to
change, just the internals.
The main differences between GTask and GSimpleAsyncResult, regarding
how the methods are implemented in MM, are:
1) Context owned by the GTask, not the other way around.
For the async methods which need some context, we usually created a
Context struct which contains the 'self' pointer as well as the
GSimpleAsyncResult result (e.g. the LoadCapabilitiesContext in
mm-broadband-modem.c). In this case, the context is owning the
GSimpleAsyncResult, and during the async method steps, we pass the
full context around. Once we have set a final result in the
GSimpleAsyncResult, we would call a context_complete_and_free(), which
would complete the GSimpleAsyncResult and free the context.
With GTask, this approach changes slightly. It is now the GTask the
one which owns the Context, via g_task_set_task_data(). This method
also receives a GDestroyNotify callback where we can pass the method
to free the context. The 'self' pointer can be retrieved directly
(transfer none, no new reference returned) with
g_task_get_source_object(). Also, it is now the GTask object the one
passed around in the internals of the async method, instead of the
context struct.
2) Completing the async result in idle or not in idle
With GSimpleAsyncResult we had to take care of completing it in an
idle if the async() call was finishing right away, and we could
complete not-in-idle otherwise. With GTask, this is taken care of
automatically, all the g_task_return_() calls will do the right thing.
Oh, and these calls do both setting the result and completing the
call, unlike with GSimpleAsyncResult, which needed first a
set_op_res_() call and then a complete() or complete_in_idle().
3) Ownership of the task result data
In GSimpleAsyncResult, when a pointer data was set with
g_simple_async_result_set_op_res_gpointer(), we could pass ownership
of the data by explicitly providing a GDestroyNotify callback, meaning
that when the GSimpleAsyncResult was disposed, the data would also be
disposed with it. In the finish() function we would then need to dup
or ref explicitly the data we got from
g_simple_async_result_get_op_res_gpointer(), to get our own copy that
we can return to the caller.
With GTask, we also set ownership of the data to the GTask in
g_task_return_pointer(), but once we are in the finish() step, we can
just g_task_propagate_pointer() to return the data itself to the
caller, no need to get a dup or an extra reference.
4) No need for 2 steps to return error and return data in finish()
With GSimpleAsyncResult, we usually had to first check if an error had
to be returned with g_simple_async_result_propagate_error() and if
not, then use e.g. the get_op_res_gpointer() call to prepare the
output to return.
With GTask, a single g_task_propagate_() call does both things already.
5) Built-in cancellations
With GSimpleAsyncResult we had to handle cancellations of async calls
manually (and therefore we didn't do it much).
With GTask, the cancellation support is built in; the API of the GTask
expects an optional GCancellable, and if this is ever cancelled, the
async method will *always* return a G_IO_ERROR_CANCELLED error
(regardless of whether a result was already set or not). Of course,
cancellation points still need to be handled manually in the async
operation. The not-yet-merge work on port probing cancellations uses
the built-in GTask cancellation support:
http://cgit.freedesktop.org/ModemManager/ModemManager/commit/?h=aleksander/plugin-manager&id=334f908e735e248b79e644c3ed4cf0301f7f864f
I think those points are the ones most applicable to ModemManager. And
given the type of work, I really think that newcomers wanting to learn
about GLib/GIO async calls could benefit a lot.
Anyone up to this (G)Task? ;)
--
Aleksander
https://aleksander.es
More information about the ModemManager-devel
mailing list