From nobody Sun Apr 12 07:20:25 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1771421636; cv=none; d=zohomail.com; s=zohoarc; b=PTA+n4KUFEcHnVT7v7ap+oJw5Fu6XL74XZn7vnEc37XuWClWP/wsAJMp/74lX9jXk7IFedny/zK6OpFQ++fjyMCkeX1240xIqOebou/vV6D/ztf51fhHv2HF9gBdUAt+7dzmwLFq4rAeR80MfVIdF9IG6yKslbh7NGnhk6fkrF4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1771421636; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=BO4JPf4lZk0oOeAlnCpzdI34YuiTPFRED7Rq/jcZllk=; b=Kq5PwiULd7QL5BSVjis0tRdqPV5j97nuNulk8C2j+MSB8CyE2ihlrgIO1Z1p5xii0QXVlQa+snwiO6MnEUgqLtOagK10TLRTsjjBnLYla8VTs1te5FEVzrZq7u4oT3jG2hgXOY8RCyWcUK8CMJBjtFe13tnYZov3/D3GC1Tgm0k= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1771421636524470.97446334394294; Wed, 18 Feb 2026 05:33:56 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vshfi-0004IV-HI; Wed, 18 Feb 2026 08:32:54 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vshfg-0004GX-39 for qemu-devel@nongnu.org; Wed, 18 Feb 2026 08:32:52 -0500 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vshfX-0005yD-7f for qemu-devel@nongnu.org; Wed, 18 Feb 2026 08:32:51 -0500 Received: from mail-wr1-f71.google.com (mail-wr1-f71.google.com [209.85.221.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-284-IklIBxe4NWuvxZDpbI83VQ-1; Wed, 18 Feb 2026 08:27:33 -0500 Received: by mail-wr1-f71.google.com with SMTP id ffacd0b85a97d-435db9425ebso4763455f8f.1 for ; Wed, 18 Feb 2026 05:27:33 -0800 (PST) Received: from localhost (p200300cfd737d029edef7b8da7441ac2.dip0.t-ipconnect.de. [2003:cf:d737:d029:edef:7b8d:a744:1ac2]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-43796abc21dsm43672633f8f.20.2026.02.18.05.27.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 18 Feb 2026 05:27:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1771421562; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BO4JPf4lZk0oOeAlnCpzdI34YuiTPFRED7Rq/jcZllk=; b=bqb5Hx3IhRcafUfyT61M8QBn3sirdarlWrahIB6toUOa0JEMBZqgESHeC/FeMUnTCJtQz2 szQnDAxh21CO1J1BnWcJpZnLIuaW/UWRkH2Tcyxe/W1X74v0ZC3oiKOIEwuT3UVab6CaTQ 8SSKnB7KBNTkh8Kln3xku7k8qlcStWY= X-MC-Unique: IklIBxe4NWuvxZDpbI83VQ-1 X-Mimecast-MFC-AGG-ID: IklIBxe4NWuvxZDpbI83VQ_1771421252 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=google; t=1771421252; x=1772026052; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=BO4JPf4lZk0oOeAlnCpzdI34YuiTPFRED7Rq/jcZllk=; b=Kz8lGsjc9PryG1QqP2ewJkZSfJS9nK+gdAyqvSO6LOY++aZz89l4woBTv1ajJq1b2k Bfx8GkEQWkiUnP1y1ChCzcMqcetCOLwVKvr0XFIR4M5v+2R6TkC6GqhnO7to5szhNuWr x6TwBRp61CLKquyncXqk+qal2RqPwmKvLJuTyZNEpTVfkzhPaO4Y/qXRnSHONl8y8Mt8 BsH8lXOKLVc/QdKJQxtc5aS+kq2hjK4QsL2AyHeH8Jtx3+szDJ8HUIwW3stW87gU6nFr eQEBpHdglMZwvGWWdf37pbCFpUS50pVpw3MOm1k2bYUtQZ3LX3Tigc8M2Sge+WkgvCqX 9zkQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771421252; x=1772026052; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=BO4JPf4lZk0oOeAlnCpzdI34YuiTPFRED7Rq/jcZllk=; b=vvhXuAuIqbgWnuwxKczhAkDt8U/BE8fC5mdOt81Xm67YLvtieyLOBgTW8WLZf9gWmA TpxyVTZydmr53oIs1kqTWs3CAJeDP/mCDJIkNsWKyQTdVgNQ2CtoJGIzaOIC6gbgkDyk u7OJPdQ4Z0NLvg5KtS7+FVaI7b+YvyeY2khG2drAO9WLI6hZZn4YGKmaz9WEcg1bipkI rrsMn6jcoeCRMNiuB+tVZTz+9uWxoum/k/WcVI0YIKouB8daqfYeLQiO4RMJWPvFBKaO 54VwXh+4v/VIu79l6I3wuWCTvOnOEcdEU/VUMWuRU1476dQvINGtUuTwm/5P8eAQ2NYU rPng== X-Gm-Message-State: AOJu0YwmMNn9NGJUyQ7CMyStJnVxHf3+rnmVTWkzA1cSWx1OsZ6aXj26 Mw7MitrB4IuBZTvD1d5BuqeJ2yJl5tM3aGy+IDcXyyp5y9k5gYgkzBPaHk18xfR+Gkm7eJOZksR bP22/FyBtLJJUoKHP7E/KmbN/8Ywrxl/o1Uw5JZuBbEpi89vtw7Wzy6bObin3mOIx X-Gm-Gg: AZuq6aIaiUz3kfolTENLIv0/cMbPOUXkRmoDZCVgtZMicINWnSa5oUTY4tBJ2PPbfKK c6/yh2xfEjDyIh/WydebevWukYZeXgN829A1i15DsATrLRS5Lo3u0f2Kv78oO2NGl2kkh9lZwld 5hMUwofJAbXdo3E46APJ+IrpLObBK3z5v83H1h1+2lysl6AIOVZRQYegOQtqyFOnAz7s44OI7nJ iV8JfQcZaounoUWZLDE6x+aACmd+weuJ9/oN8Nrqg/jJxf7WN62jb4FBkHUtJQ1dkxRw5PblC67 /HMDhPMxHZUmj6xHpINIa801A9lNlPl0CE5alPmEKJJr3y5St/aCtykKwv5lQKwCE09Xgu8FLeb WGvBFys1N9ZA6j3xiBvo2v0Hig2C4QKbb47PD+a/1Ri0erw60X+S4csiea9l7bQxlE+AEgUkSCP h1Yobq X-Received: by 2002:a05:6000:2911:b0:437:6b2e:a262 with SMTP id ffacd0b85a97d-43958df1428mr3408186f8f.3.1771421251627; Wed, 18 Feb 2026 05:27:31 -0800 (PST) X-Received: by 2002:a05:6000:2911:b0:437:6b2e:a262 with SMTP id ffacd0b85a97d-43958df1428mr3408110f8f.3.1771421250891; Wed, 18 Feb 2026 05:27:30 -0800 (PST) From: Hanna Czenczek To: qemu-block@nongnu.org Cc: qemu-devel@nongnu.org, Hanna Czenczek , Kevin Wolf , Brian Song Subject: [PATCH v4 19/24] block/export: Add multi-threading interface Date: Wed, 18 Feb 2026 14:26:28 +0100 Message-ID: <20260218132633.29748-20-hreitz@redhat.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260218132633.29748-1-hreitz@redhat.com> References: <20260218132633.29748-1-hreitz@redhat.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=hreitz@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.043, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1771421639184154100 Content-Type: text/plain; charset="utf-8" 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 Acked-by: Markus Armbruster Reviewed-by: Stefan Hajnoczi Signed-off-by: Hanna Czenczek --- qapi/block-export.json | 36 ++++++++++++++++++--- include/block/export.h | 12 +++++-- block/export/export.c | 48 +++++++++++++++++++++++++--- block/export/fuse.c | 7 ++++ block/export/vduse-blk.c | 7 ++++ block/export/vhost-user-blk-server.c | 8 +++++ nbd/server.c | 6 ++++ 7 files changed, 113 insertions(+), 11 deletions(-) diff --git a/qapi/block-export.json b/qapi/block-export.json index 076954ef1a..160cd2e3ca 100644 --- a/qapi/block-export.json +++ b/qapi/block-export.json @@ -363,14 +363,16 @@ # to the export before completion is signalled. (since: 5.2; # default: false) # -# @iothread: The name of the iothread object where the export will -# run. The default is to use the thread currently associated with -# the block node. (since: 5.2) +# @iothread: The name(s) of one or more iothread object(s) where the +# export will run. The default is to use the thread currently +# associated with the block node. (since: 5.2; multi-threading +# since 10.1) # # @fixed-iothread: True prevents the block node from being moved to # another thread while the export is active. If true and # @iothread is given, export creation fails if the block node -# cannot be moved to the iothread. The default is false. +# cannot be moved to the iothread. Must not be true when giving +# multiple iothreads for @iothread. The default is false. # (since: 5.2) # # @allow-inactive: If true, the export allows the exported node to be @@ -387,7 +389,7 @@ 'base': { 'type': 'BlockExportType', 'id': 'str', '*fixed-iothread': 'bool', - '*iothread': 'str', + '*iothread': 'BlockExportIothreads', 'node-name': 'str', '*writable': 'bool', '*writethrough': 'bool', @@ -403,6 +405,30 @@ 'if': 'CONFIG_VDUSE_BLK_EXPORT' } } } =20 +## +# @BlockExportIothreads: +# +# Specify a single or multiple I/O threads in which to run a block +# export's I/O. +# +# @single: Run the export's I/O in the given single I/O thread. +# +# @multi: Use multi-threading across the given set of I/O threads, +# which must not be empty. Note: Passing a single I/O thread via +# this variant is still treated as multi-threading, which is +# different from using the @single variant. In particular, even +# if there only is a single I/O thread in the set, export types +# that do not support multi-threading will generally reject this +# variant, and BlockExportOptions.fixed-iothread is always +# incompatible with it. +# +# Since: 10.1 +## +{ 'alternate': 'BlockExportIothreads', + 'data': { + 'single': 'str', + 'multi': ['str'] } } + ## # @block-export-add: # diff --git a/include/block/export.h b/include/block/export.h index 4bd9531d4d..ca45da928c 100644 --- a/include/block/export.h +++ b/include/block/export.h @@ -32,8 +32,16 @@ typedef struct BlockExportDriver { /* True if the export type supports running on an inactive node */ bool supports_inactive; =20 - /* Creates and starts a new block export */ - int (*create)(BlockExport *, BlockExportOptions *, Error **); + /* + * Creates and starts a new block export. + * + * If the user passed a set of I/O threads for multi-threading, @multi= thread + * is a list of the @multithread_count corresponding contexts (freed b= y the + * caller). Note that @exp->ctx has no relation to that list. + */ + int (*create)(BlockExport *exp, BlockExportOptions *opts, + AioContext *const *multithread, size_t multithread_count, + Error **errp); =20 /* * Frees a removed block export. This function is only called after all diff --git a/block/export/export.c b/block/export/export.c index f3bbf11070..b733f269f3 100644 --- a/block/export/export.c +++ b/block/export/export.c @@ -76,16 +76,26 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Er= ror **errp) { bool fixed_iothread =3D export->has_fixed_iothread && export->fixed_io= thread; bool allow_inactive =3D export->has_allow_inactive && export->allow_in= active; + bool multithread =3D export->iothread && + export->iothread->type =3D=3D QTYPE_QLIST; const BlockExportDriver *drv; BlockExport *exp =3D NULL; BlockDriverState *bs; BlockBackend *blk =3D NULL; AioContext *ctx; + AioContext **multithread_ctxs =3D NULL; + size_t multithread_count =3D 0; uint64_t perm; int ret; =20 GLOBAL_STATE_CODE(); =20 + if (fixed_iothread && multithread) { + error_setg(errp, + "Cannot use fixed-iothread for a multi-threaded export"= ); + return NULL; + } + if (!id_wellformed(export->id)) { error_setg(errp, "Invalid block export id"); return NULL; @@ -116,14 +126,16 @@ BlockExport *blk_exp_add(BlockExportOptions *export, = Error **errp) =20 ctx =3D bdrv_get_aio_context(bs); =20 - if (export->iothread) { + /* Move the BDS to the target I/O thread, if it is a single one */ + if (export->iothread && !multithread) { + const char *iothread_id =3D export->iothread->u.single; IOThread *iothread; AioContext *new_ctx; Error **set_context_errp; =20 - iothread =3D iothread_by_id(export->iothread); + iothread =3D iothread_by_id(iothread_id); if (!iothread) { - error_setg(errp, "iothread \"%s\" not found", export->iothread= ); + error_setg(errp, "iothread \"%s\" not found", iothread_id); goto fail; } =20 @@ -137,6 +149,32 @@ BlockExport *blk_exp_add(BlockExportOptions *export, E= rror **errp) } else if (fixed_iothread) { goto fail; } + } else if (multithread) { + strList *iothread_list =3D export->iothread->u.multi; + size_t i; + + multithread_count =3D 0; + for (strList *e =3D iothread_list; e; e =3D e->next) { + multithread_count++; + } + + if (multithread_count =3D=3D 0) { + error_setg(errp, "The set of I/O threads must not be empty"); + return NULL; + } + + multithread_ctxs =3D g_new(AioContext *, multithread_count); + i =3D 0; + for (strList *e =3D iothread_list; e; e =3D e->next) { + IOThread *iothread =3D iothread_by_id(e->value); + + if (!iothread) { + error_setg(errp, "iothread \"%s\" not found", e->value); + goto fail; + } + multithread_ctxs[i++] =3D iothread_get_aio_context(iothread); + } + assert(i =3D=3D multithread_count); } =20 bdrv_graph_rdlock_main_loop(); @@ -195,7 +233,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Er= ror **errp) .blk =3D blk, }; =20 - ret =3D drv->create(exp, export, errp); + ret =3D drv->create(exp, export, multithread_ctxs, multithread_count, = errp); if (ret < 0) { goto fail; } @@ -203,6 +241,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Er= ror **errp) assert(exp->blk !=3D NULL); =20 QLIST_INSERT_HEAD(&block_exports, exp, next); + g_free(multithread_ctxs); return exp; =20 fail: @@ -214,6 +253,7 @@ fail: g_free(exp->id); g_free(exp); } + g_free(multithread_ctxs); return NULL; } =20 diff --git a/block/export/fuse.c b/block/export/fuse.c index b60affe86b..aa4f1c0307 100644 --- a/block/export/fuse.c +++ b/block/export/fuse.c @@ -260,6 +260,8 @@ static const BlockDevOps fuse_export_blk_dev_ops =3D { =20 static int fuse_export_create(BlockExport *blk_exp, BlockExportOptions *blk_exp_args, + AioContext *const *multithread, + size_t mt_count, Error **errp) { ERRP_GUARD(); /* ensure clean-up even with error_fatal */ @@ -269,6 +271,11 @@ static int fuse_export_create(BlockExport *blk_exp, =20 assert(blk_exp_args->type =3D=3D BLOCK_EXPORT_TYPE_FUSE); =20 + if (multithread) { + error_setg(errp, "FUSE export does not support multi-threading"); + return -EINVAL; + } + /* For growable and writable exports, take the RESIZE permission */ if (args->growable || blk_exp_args->writable) { uint64_t blk_perm, blk_shared_perm; diff --git a/block/export/vduse-blk.c b/block/export/vduse-blk.c index 8af13b7f0b..10dc673c56 100644 --- a/block/export/vduse-blk.c +++ b/block/export/vduse-blk.c @@ -267,6 +267,7 @@ static const BlockDevOps vduse_block_ops =3D { }; =20 static int vduse_blk_exp_create(BlockExport *exp, BlockExportOptions *opts, + AioContext *const *multithread, size_t mt_= count, Error **errp) { VduseBlkExport *vblk_exp =3D container_of(exp, VduseBlkExport, export); @@ -302,6 +303,12 @@ static int vduse_blk_exp_create(BlockExport *exp, Bloc= kExportOptions *opts, return -EINVAL; } } + + if (multithread) { + error_setg(errp, "vduse-blk export does not support multi-threadin= g"); + return -EINVAL; + } + vblk_exp->num_queues =3D num_queues; vblk_exp->handler.blk =3D exp->blk; vblk_exp->handler.serial =3D g_strdup(vblk_opts->serial ?: ""); diff --git a/block/export/vhost-user-blk-server.c b/block/export/vhost-user= -blk-server.c index a4d54e824f..e89422bb85 100644 --- a/block/export/vhost-user-blk-server.c +++ b/block/export/vhost-user-blk-server.c @@ -316,6 +316,7 @@ static const BlockDevOps vu_blk_dev_ops =3D { }; =20 static int vu_blk_exp_create(BlockExport *exp, BlockExportOptions *opts, + AioContext *const *multithread, size_t mt_cou= nt, Error **errp) { VuBlkExport *vexp =3D container_of(exp, VuBlkExport, export); @@ -341,6 +342,13 @@ static int vu_blk_exp_create(BlockExport *exp, BlockEx= portOptions *opts, error_setg(errp, "num-queues must be greater than 0"); return -EINVAL; } + + if (multithread) { + error_setg(errp, + "vhost-user-blk export does not support multi-threading= "); + return -EINVAL; + } + vexp->handler.blk =3D exp->blk; vexp->handler.serial =3D g_strdup("vhost_user_blk"); vexp->handler.logical_block_size =3D logical_block_size; diff --git a/nbd/server.c b/nbd/server.c index acec0487a8..620097c58c 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -1795,6 +1795,7 @@ static const BlockDevOps nbd_block_ops =3D { }; =20 static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp= _args, + AioContext *const *multithread, size_t mt_cou= nt, Error **errp) { NBDExport *exp =3D container_of(blk_exp, NBDExport, common); @@ -1831,6 +1832,11 @@ static int nbd_export_create(BlockExport *blk_exp, B= lockExportOptions *exp_args, return -EEXIST; } =20 + if (multithread) { + error_setg(errp, "NBD export does not support multi-threading"); + return -EINVAL; + } + size =3D blk_getlength(blk); if (size < 0) { error_setg_errno(errp, -size, --=20 2.53.0