@samitouri / QOSamiQemu / commits / 163cd0ae11

io: separate freeing of tasks from marking them as complete

The original design of QIOTask was intended to simplify lifecycle management by automatically freeing it when the task was marked as complete. This overlooked the fact that when a QIOTask is used in combination with a GSource, there may be times when the source callback is never invoked. This is typically when a GSource is released before any I/O event arrives. In such cases it is not desirable to mark a QIOTask as complete, but it still needs to be freed. To satisfy this, the task must be released manually. Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

Daniel P. Berrangé committed Jan 6, 2026 at 16:08 UTC 163cd0ae1182e67509b271f244a73dfd938337b9
5 files changed +60 -10
include/io/task.h
+21 -8
@@ -96,7 +96,7 @@ typedef void (*QIOTaskWorker)(QIOTask *task,
96 * 1000,
97 * myobject_operation_timer,
98 * task,
99 - * NULL);
99 + * qio_task_free);
100 * }
101 * </programlisting>
102 * </example>
@@ -138,9 +138,8 @@ typedef void (*QIOTaskWorker)(QIOTask *task,
138 * the callback func 'myobject_operation_notify' shown
139 * earlier to deal with the results.
140 *
141 - * Once this function returns false, object_unref will be called
142 - * automatically on the task causing it to be released and the
143 - * ref on QMyObject dropped too.
141 + * Once this function returns FALSE, the task will be freed,
142 + * causing it release the ref on QMyObject too.
143 *
144 * The QIOTask module can also be used to perform operations
145 * in a background thread context, while still reporting the
@@ -208,8 +207,8 @@ typedef void (*QIOTaskWorker)(QIOTask *task,
207 * 'err' attribute in the task object to determine if
208 * the operation was successful or not.
209 *
211 - * The returned task will be released when qio_task_complete()
212 - * is invoked.
210 + * The returned task must be released by calling
211 + * qio_task_free() when no longer required.
212 *
213 * Returns: the task struct
214 */
@@ -218,6 +217,19 @@ QIOTask *qio_task_new(Object *source,
217 gpointer opaque,
218 GDestroyNotify destroy);
219
220 +/**
221 + * qio_task_free:
222 + * task: the task object to free
223 + *
224 + * Free the resources associated with the task. Typically
225 + * the qio_task_complete() method will be called immediately
226 + * before this to trigger the task callback, however, it is
227 + * permissible to free the task in the case of cancellation.
228 + * The destroy callback will be used to release the opaque
229 + * data provided to qio_task_new().
230 + */
231 +void qio_task_free(QIOTask *task);
232 +
233 /**
234 * qio_task_run_in_thread:
235 * @task: the task struct
@@ -268,8 +280,9 @@ void qio_task_wait_thread(QIOTask *task);
280 * qio_task_complete:
281 * @task: the task struct
282 *
271 - * Invoke the completion callback for @task and
272 - * then free its memory.
283 + * Invoke the completion callback for @task. This should typically
284 + * only be invoked once on a task, and then qio_task_free() used
285 + * to free it.
286 */
287 void qio_task_complete(QIOTask *task);
288
io/channel-tls.c
+4
@@ -170,6 +170,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc,
170 trace_qio_channel_tls_handshake_fail(ioc);
171 qio_task_set_error(task, err);
172 qio_task_complete(task);
173 + qio_task_free(task);
174 return;
175 }
176
@@ -183,6 +184,7 @@ static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc,
184 trace_qio_channel_tls_credentials_allow(ioc);
185 }
186 qio_task_complete(task);
187 + qio_task_free(task);
188 } else {
189 GIOCondition condition;
190 QIOChannelTLSData *data = g_new0(typeof(*data), 1);
@@ -270,11 +272,13 @@ static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task,
272 trace_qio_channel_tls_bye_fail(ioc);
273 qio_task_set_error(task, err);
274 qio_task_complete(task);
275 + qio_task_free(task);
276 return;
277 }
278
279 if (status == QCRYPTO_TLS_BYE_COMPLETE) {
280 qio_task_complete(task);
281 + qio_task_free(task);
282 return;
283 }
284
io/channel-websock.c
+3
@@ -545,6 +545,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
545 trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
546 qio_task_set_error(task, err);
547 qio_task_complete(task);
548 + qio_task_free(task);
549 wioc->hs_io_tag = 0;
550 return FALSE;
551 }
@@ -561,6 +562,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
562 trace_qio_channel_websock_handshake_complete(ioc);
563 qio_task_complete(task);
564 }
565 + qio_task_free(task);
566 wioc->hs_io_tag = 0;
567 return FALSE;
568 }
@@ -588,6 +590,7 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
590 trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
591 qio_task_set_error(task, err);
592 qio_task_complete(task);
593 + qio_task_free(task);
594 wioc->hs_io_tag = 0;
595 return FALSE;
596 }
io/task.c
+6 -2
@@ -70,8 +70,12 @@ QIOTask *qio_task_new(Object *source,
70 return task;
71 }
72
73 -static void qio_task_free(QIOTask *task)
73 +void qio_task_free(QIOTask *task)
74 {
75 + if (!task) {
76 + return;
77 + }
78 +
79 qemu_mutex_lock(&task->thread_lock);
80 if (task->thread) {
81 if (task->thread->destroy) {
@@ -108,6 +112,7 @@ static gboolean qio_task_thread_result(gpointer opaque)
112
113 trace_qio_task_thread_result(task);
114 qio_task_complete(task);
115 + qio_task_free(task);
116
117 return FALSE;
118 }
@@ -194,7 +199,6 @@ void qio_task_complete(QIOTask *task)
199 {
200 task->func(task, task->opaque);
201 trace_qio_task_complete(task);
197 - qio_task_free(task);
202 }
203
204
tests/unit/test-io-task.c
+26
@@ -73,6 +73,7 @@ static void test_task_complete(void)
73 src = qio_task_get_source(task);
74
75 qio_task_complete(task);
76 + qio_task_free(task);
77
78 g_assert(obj == src);
79
@@ -84,6 +85,28 @@ static void test_task_complete(void)
85 }
86
87
88 +static void test_task_cancel(void)
89 +{
90 + QIOTask *task;
91 + Object *obj = object_new(TYPE_DUMMY);
92 + Object *src;
93 + struct TestTaskData data = { NULL, NULL, false };
94 +
95 + task = qio_task_new(obj, task_callback, &data, NULL);
96 + src = qio_task_get_source(task);
97 +
98 + qio_task_free(task);
99 +
100 + g_assert(obj == src);
101 +
102 + object_unref(obj);
103 +
104 + g_assert(data.source == NULL);
105 + g_assert(data.err == NULL);
106 + g_assert(data.freed == false);
107 +}
108 +
109 +
110 static void task_data_free(gpointer opaque)
111 {
112 struct TestTaskData *data = opaque;
@@ -101,6 +124,7 @@ static void test_task_data_free(void)
124 task = qio_task_new(obj, task_callback, &data, task_data_free);
125
126 qio_task_complete(task);
127 + qio_task_free(task);
128
129 object_unref(obj);
130
@@ -123,6 +147,7 @@ static void test_task_failure(void)
147
148 qio_task_set_error(task, err);
149 qio_task_complete(task);
150 + qio_task_free(task);
151
152 object_unref(obj);
153
@@ -260,6 +285,7 @@ int main(int argc, char **argv)
285 module_call_init(MODULE_INIT_QOM);
286 type_register_static(&dummy_info);
287 g_test_add_func("/crypto/task/complete", test_task_complete);
288 + g_test_add_func("/crypto/task/cancel", test_task_cancel);
289 g_test_add_func("/crypto/task/datafree", test_task_data_free);
290 g_test_add_func("/crypto/task/failure", test_task_failure);
291 g_test_add_func("/crypto/task/thread_complete", test_task_thread_complete);