@samitouri / QOSamiQemu / commits / 277fc1ce18

block/export: Add multi-threading interface

Make BlockExportType.iothread an alternate between a single-thread variant 'str' and a multi-threading variant '[str]'. In contrast to the single-thread setting, the multi-threading setting will not change the BDS's context (and so is incompatible with the fixed-iothread setting), but instead just pass a list to the export driver, with which it can do whatever it wants. Currently no export driver supports multi-threading, so they all return an error when receiving such a list. Suggested-by: Kevin Wolf <kwolf@redhat.com> Acked-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Hanna Czenczek <hreitz@redhat.com> Message-ID: <20260309150856.26800-21-hreitz@redhat.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Hanna Czenczek committed Mar 9, 2026 at 16:08 UTC 277fc1ce184a7d4df6790a3deeffd4025e3ad7d0
7 files changed +113 -11
block/export/export.c
+44 -4
@@ -76,16 +76,26 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
76 {
77 bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread;
78 bool allow_inactive = export->has_allow_inactive && export->allow_inactive;
79 + bool multithread = export->iothread &&
80 + export->iothread->type == QTYPE_QLIST;
81 const BlockExportDriver *drv;
82 BlockExport *exp = NULL;
83 BlockDriverState *bs;
84 BlockBackend *blk = NULL;
85 AioContext *ctx;
86 + AioContext **multithread_ctxs = NULL;
87 + size_t multithread_count = 0;
88 uint64_t perm;
89 int ret;
90
91 GLOBAL_STATE_CODE();
92
93 + if (fixed_iothread && multithread) {
94 + error_setg(errp,
95 + "Cannot use fixed-iothread for a multi-threaded export");
96 + return NULL;
97 + }
98 +
99 if (!id_wellformed(export->id)) {
100 error_setg(errp, "Invalid block export id");
101 return NULL;
@@ -116,14 +126,16 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
126
127 ctx = bdrv_get_aio_context(bs);
128
119 - if (export->iothread) {
129 + /* Move the BDS to the target I/O thread, if it is a single one */
130 + if (export->iothread && !multithread) {
131 + const char *iothread_id = export->iothread->u.single;
132 IOThread *iothread;
133 AioContext *new_ctx;
134 Error **set_context_errp;
135
124 - iothread = iothread_by_id(export->iothread);
136 + iothread = iothread_by_id(iothread_id);
137 if (!iothread) {
126 - error_setg(errp, "iothread \"%s\" not found", export->iothread);
138 + error_setg(errp, "iothread \"%s\" not found", iothread_id);
139 goto fail;
140 }
141
@@ -137,6 +149,32 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
149 } else if (fixed_iothread) {
150 goto fail;
151 }
152 + } else if (multithread) {
153 + strList *iothread_list = export->iothread->u.multi;
154 + size_t i;
155 +
156 + multithread_count = 0;
157 + for (strList *e = iothread_list; e; e = e->next) {
158 + multithread_count++;
159 + }
160 +
161 + if (multithread_count == 0) {
162 + error_setg(errp, "The set of I/O threads must not be empty");
163 + return NULL;
164 + }
165 +
166 + multithread_ctxs = g_new(AioContext *, multithread_count);
167 + i = 0;
168 + for (strList *e = iothread_list; e; e = e->next) {
169 + IOThread *iothread = iothread_by_id(e->value);
170 +
171 + if (!iothread) {
172 + error_setg(errp, "iothread \"%s\" not found", e->value);
173 + goto fail;
174 + }
175 + multithread_ctxs[i++] = iothread_get_aio_context(iothread);
176 + }
177 + assert(i == multithread_count);
178 }
179
180 bdrv_graph_rdlock_main_loop();
@@ -195,7 +233,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
233 .blk = blk,
234 };
235
198 - ret = drv->create(exp, export, errp);
236 + ret = drv->create(exp, export, multithread_ctxs, multithread_count, errp);
237 if (ret < 0) {
238 goto fail;
239 }
@@ -203,6 +241,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
241 assert(exp->blk != NULL);
242
243 QLIST_INSERT_HEAD(&block_exports, exp, next);
244 + g_free(multithread_ctxs);
245 return exp;
246
247 fail:
@@ -214,6 +253,7 @@ fail:
253 g_free(exp->id);
254 g_free(exp);
255 }
256 + g_free(multithread_ctxs);
257 return NULL;
258 }
259
block/export/fuse.c
+7
@@ -259,6 +259,8 @@ static const BlockDevOps fuse_export_blk_dev_ops = {
259
260 static int fuse_export_create(BlockExport *blk_exp,
261 BlockExportOptions *blk_exp_args,
262 + AioContext *const *multithread,
263 + size_t mt_count,
264 Error **errp)
265 {
266 ERRP_GUARD(); /* ensure clean-up even with error_fatal */
@@ -268,6 +270,11 @@ static int fuse_export_create(BlockExport *blk_exp,
270
271 assert(blk_exp_args->type == BLOCK_EXPORT_TYPE_FUSE);
272
273 + if (multithread) {
274 + error_setg(errp, "FUSE export does not support multi-threading");
275 + return -EINVAL;
276 + }
277 +
278 /* For growable and writable exports, take the RESIZE permission */
279 if (args->growable || blk_exp_args->writable) {
280 uint64_t blk_perm, blk_shared_perm;
block/export/vduse-blk.c
+7
@@ -267,6 +267,7 @@ static const BlockDevOps vduse_block_ops = {
267 };
268
269 static int vduse_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
270 + AioContext *const *multithread, size_t mt_count,
271 Error **errp)
272 {
273 VduseBlkExport *vblk_exp = container_of(exp, VduseBlkExport, export);
@@ -302,6 +303,12 @@ static int vduse_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
303 return -EINVAL;
304 }
305 }
306 +
307 + if (multithread) {
308 + error_setg(errp, "vduse-blk export does not support multi-threading");
309 + return -EINVAL;
310 + }
311 +
312 vblk_exp->num_queues = num_queues;
313 vblk_exp->handler.blk = exp->blk;
314 vblk_exp->handler.serial = g_strdup(vblk_opts->serial ?: "");
block/export/vhost-user-blk-server.c
+8
@@ -316,6 +316,7 @@ static const BlockDevOps vu_blk_dev_ops = {
316 };
317
318 static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
319 + AioContext *const *multithread, size_t mt_count,
320 Error **errp)
321 {
322 VuBlkExport *vexp = container_of(exp, VuBlkExport, export);
@@ -341,6 +342,13 @@ static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts,
342 error_setg(errp, "num-queues must be greater than 0");
343 return -EINVAL;
344 }
345 +
346 + if (multithread) {
347 + error_setg(errp,
348 + "vhost-user-blk export does not support multi-threading");
349 + return -EINVAL;
350 + }
351 +
352 vexp->handler.blk = exp->blk;
353 vexp->handler.serial = g_strdup("vhost_user_blk");
354 vexp->handler.logical_block_size = logical_block_size;
include/block/export.h
+10 -2
@@ -32,8 +32,16 @@ typedef struct BlockExportDriver {
32 /* True if the export type supports running on an inactive node */
33 bool supports_inactive;
34
35 - /* Creates and starts a new block export */
36 - int (*create)(BlockExport *, BlockExportOptions *, Error **);
35 + /*
36 + * Creates and starts a new block export.
37 + *
38 + * If the user passed a set of I/O threads for multi-threading, @multithread
39 + * is a list of the @multithread_count corresponding contexts (freed by the
40 + * caller). Note that @exp->ctx has no relation to that list.
41 + */
42 + int (*create)(BlockExport *exp, BlockExportOptions *opts,
43 + AioContext *const *multithread, size_t multithread_count,
44 + Error **errp);
45
46 /*
47 * Frees a removed block export. This function is only called after all
nbd/server.c
+6
@@ -1795,6 +1795,7 @@ static const BlockDevOps nbd_block_ops = {
1795 };
1796
1797 static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
1798 + AioContext *const *multithread, size_t mt_count,
1799 Error **errp)
1800 {
1801 NBDExport *exp = container_of(blk_exp, NBDExport, common);
@@ -1831,6 +1832,11 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
1832 return -EEXIST;
1833 }
1834
1835 + if (multithread) {
1836 + error_setg(errp, "NBD export does not support multi-threading");
1837 + return -EINVAL;
1838 + }
1839 +
1840 size = blk_getlength(blk);
1841 if (size < 0) {
1842 error_setg_errno(errp, -size,
qapi/block-export.json
+31 -5
@@ -363,14 +363,16 @@
363 # to the export before completion is signalled. (since: 5.2;
364 # default: false)
365 #
366 -# @iothread: The name of the iothread object where the export will
367 -# run. The default is to use the thread currently associated with
368 -# the block node. (since: 5.2)
366 +# @iothread: The name(s) of one or more iothread object(s) where the
367 +# export will run. The default is to use the thread currently
368 +# associated with the block node. (since: 5.2; multi-threading
369 +# since 10.1)
370 #
371 # @fixed-iothread: True prevents the block node from being moved to
372 # another thread while the export is active. If true and
373 # @iothread is given, export creation fails if the block node
373 -# cannot be moved to the iothread. The default is false.
374 +# cannot be moved to the iothread. Must not be true when giving
375 +# multiple iothreads for @iothread. The default is false.
376 # (since: 5.2)
377 #
378 # @allow-inactive: If true, the export allows the exported node to be
@@ -387,7 +389,7 @@
389 'base': { 'type': 'BlockExportType',
390 'id': 'str',
391 '*fixed-iothread': 'bool',
390 - '*iothread': 'str',
392 + '*iothread': 'BlockExportIothreads',
393 'node-name': 'str',
394 '*writable': 'bool',
395 '*writethrough': 'bool',
@@ -403,6 +405,30 @@
405 'if': 'CONFIG_VDUSE_BLK_EXPORT' }
406 } }
407
408 +##
409 +# @BlockExportIothreads:
410 +#
411 +# Specify a single or multiple I/O threads in which to run a block
412 +# export's I/O.
413 +#
414 +# @single: Run the export's I/O in the given single I/O thread.
415 +#
416 +# @multi: Use multi-threading across the given set of I/O threads,
417 +# which must not be empty. Note: Passing a single I/O thread via
418 +# this variant is still treated as multi-threading, which is
419 +# different from using the @single variant. In particular, even
420 +# if there only is a single I/O thread in the set, export types
421 +# that do not support multi-threading will generally reject this
422 +# variant, and BlockExportOptions.fixed-iothread is always
423 +# incompatible with it.
424 +#
425 +# Since: 10.1
426 +##
427 +{ 'alternate': 'BlockExportIothreads',
428 + 'data': {
429 + 'single': 'str',
430 + 'multi': ['str'] } }
431 +
432 ##
433 # @block-export-add:
434 #