GTask allows (buggy) application code to cause callback to be called twice, without warning
I discovered while testing some application code that it is possible to cause a
GTask's callback to be called twice, without any warnings from GLib, by returning both an error and a non-error value. Here is a sample program that demonstrates this: gtask-multiple-returns.c. It has four modes:
- If run with no arguments, it calls
g_task_return_new_error()from outside any
GMainContext, which forces the callback to be deferred to an idle. In this case, the call to
g_return_if_fail()guard, and GTask behaves as I would expect: it issues a critical for the invalid second return, and only calls the callback once.
- If run with
--error-first, it calls
g_task_return_boolean(). In this case, the callback is called twice from two successive idles, and GTask is silent.
- If run with
--return-in-idle, it calls those two functions (in one or other order, depending on
--error-first) from an idle. In this case, the callback fires synchronously, and because the callback immediately calls
g_task_propagate_boolean(), the error/success state of the GTask is cleared and GTask happily calls the callback a second time.
The second case bothers me most because GTask has all the information it needs to make the call to
g_task_return_boolean() fail. The third/fourth cases are maybe less bothersome because
g_task_propagate_boolean() has this line in its documentation, which you could argue gives GTask license to discard all of its internal state about the success/failure of the operation:
Since this method transfers ownership of the return value (or error) to the caller, you may only call it once.
But I think GTask should try pretty hard to ensure the callback is only called once, even in the face of application bugs, given its introspection annotations:
* @callback: (scope async): a #GAsyncReadyCallback. * @callback_data: (closure): user data passed to @callback.
These are not true, so a Python program equivalent to a cut-down version of case 2 above segfaults. gtask-multiple-returns.py