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);