GTask cancellation in Vala does not reach nested async calls — propagation semantics unclear
0 reputation · 05 Feb 2022, 18:44 UTC
0 reputation · 05 Feb 2022, 18:44 UTC
I am designing an asynchronous Vala service that wraps several nested GIO async calls inside a single GTask, and I need cancellation and a hard timeout to terminate the whole chain cleanly. The documented pieces are in place — GCancellable is passed to each async entry point and GTask.cancel() / is_cancelled are available — but the propagation semantics are ambiguous.
The uncertainty is twofold. First, cancelling the GCancellable does not appear to be documented as automatically cancelling the enclosing GTask (or vice versa), so it is unclear which object nested async calls should observe. Second, when cancellation fires mid-operation, it is not obvious whether the innermost async callback receives a GTaskCanceledError that I must forward manually, or whether the original GTask result surfaces the cancellation on its own.
For the timeout side, GIO async APIs expose no timeout parameter, so the plan is a GLib.timeout_add_seconds callback that cancels the cancellable — which raises a race if the operation completes just before the timeout fires. Assume a recent GLib/GIO (2.7x) runtime; behavior may vary by version.
GCancellable propagate to a GTask created in the same async operation, or must each nested call be wired explicitly?GTaskCanceledError itself, or propagate the inner async result?GCancellable does not automatically cancel a GTask. You must explicitly associate the cancellable with the task using task.set_cancellable(cancellable) (or the C equivalent g_task_set_cancellable). Once set, a cancellation of the GCancellable causes the GTask to finish with a GTaskCanceledError.GTask, any nested GIO async operation that receives the same GCancellable will return G_IO_ERROR_CANCELLED. The GTask then propagates that as GTaskCanceledError in its finish callback, so the wrapper need not manually forward the error – simply return the task’s result.GLib.timeout_add_seconds that calls cancellable.cancel(). In the GTask’s thread, after the async operation finishes, invoke task.return_error_if_cancelled() before returning a successful result. This guarantees that if the operation completes just before the timeout fires the task returns success, and if cancellation occurs just after, the error is returned.Confirmed (from GLib 2.70‑2.78):
g_task_set_cancellable links a GCancellable to a GTask.GCancellable makes the task complete with GTaskCanceledError.g_task_return_error_if_cancelled safely checks cancellation after an async step.Likely explanation for the observed behaviour:
If you do not call task.set_cancellable(cancellable), the GTask remains unaware of the cancellable’s state. Nested async calls still observe the cancellable and may return G_IO_ERROR_CANCELLED, but the task will treat that as a normal successful completion unless you manually check the cancellable and return an error yourself. This explains why cancellation seemed not to propagate.
using GLib;
using Gio;
public class AsyncService {
private Cancellable _cancellable;
private Task _task;
public void start_operation () {
_cancellable = new Cancellable ();
_task = new Task (this, null, null, null);
_task.set_cancellable (_cancellable); // link cancellable to task
// Example nested async call: read a file
File file = File.new_for_path ("example.txt");
file.read_async (_cancellable, _task, (source_object, async_result) => {
try {
InputStream stream = file.read_finish (async_result);
// Process stream …
_task.return_pointer (stream);
} catch (Error e) {
// Propagate any error (including G_IO_ERROR_CANCELLED) as‑is
_task.return_error (e);
}
});
// Timeout that cancels after N seconds
Timeout.add_seconds (5, () => {
_cancellable.cancel ();
return Source.REMOVE; // do not repeat
});
}
// Called when the task finishes
private void task_ready (Task task, AsyncResult result) {
// This is the safe point: check cancellation before using a success result
if (task.return_error_if_cancelled ()) {
// Cancellation happened; error already set in task
stderr.printf "Operation cancelled\n";
return;
}
// No cancellation – get the successful result
Object res = task.propagate_pointer (result);
// use res …
}
}
The safety of task.return_error_if_cancelled() depends on the GLib version. If you are targeting a version older than 2.70, replace the call with a manual check:
if (task.get_cancellable () != null && task.get_cancellable ().is_cancelled) { task.return_error (new Error.literal (IOError.CANCELLED, "Operation cancelled")); return; }Please confirm the GLib version you are using so the recommendation can be finalized.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.