* [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API
@ 2025-10-30 15:21 Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 01/13] aio-posix: fix race between io_uring CQE and AioHandler deletion Stefan Hajnoczi
` (13 more replies)
0 siblings, 14 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
v5:
- Explain how fdmon-io_uring.c differs from other fdmon implementations
in commit message [Kevin]
- Move test-nested-aio-poll aio_get_g_source() removal into commit that touches test case [Kevin]
- Avoid g_source_add_poll() use-after-free in fdmon_poll_update() [Kevin]
- Avoid duplication in fdmon_epoll_gsource_dispatch(), use fdmon_epoll_wait() [Kevin]
- Drop unnecessary revents checks in fdmon_poll_gsource_dispatch() [Kevin]
- Mention in commit message that fdmon-io_uring.c is the new default [Kevin]
- Add comments explaining how to clean up resources in error paths [Kevin]
- Indicate error in return value from function with Error *errp arg [Kevin]
- Add patch to unindent fdmon_io_uring_destroy() [Kevin]
- Add patch to introduce FDMonOps->dispatch() callback [Kevin]
- Drop patch with hacky BH optimization for fdmon-io_uring.c [Kevin]
- Replace cqe_handler_bh with FDMonOps->dispatch() [Kevin]
- Rename AioHandler->cqe_handler field to ->internal_cqe_handler [Kevin]
- Consolidate fdmon-io_uring.c trace-events changes into this commit
- Reduce #ifdef HAVE_IO_URING_PREP_WRITEV2 code duplication [Kevin]
v4:
- Rebased and tested after the QEMU 10.1.0 release
v3:
- Add assertions documenting that ADD and REMOVE flags cannot be present
together with DELETE_AIO_HANDLER [Kevin]
v2:
- Performance improvements
- Fix pre_sqe -> prep_sqe typo [Eric]
- Add #endif terminator comment [Eric]
- Fix spacing in aio_ctx_finalize() argument list [Eric]
- Add new "block/io_uring: use non-vectored read/write when possible" patch [Eric]
- Drop Patch 1 because multi-shot POLL_ADD has edge-triggered semantics instead
of level-triggered semantics required by QEMU's AioContext APIs. The
qemu-iotests 308 test case was hanging because block/export/fuse.c relies on
level-triggered semantics. Luckily the performance reason for switching from
one-shot to multi-shot has been solved by Patch 2 ("aio-posix: keep polling
enabled with fdmon-io_uring.c"), so it's okay to use single-shot.
- Add a new Patch 1. It's a bug fix for a user-after-free in fdmon-io_uring.c
triggered by qemu-iotests iothreads-nbd-export.
This patch series contains io_uring improvements:
1. Support the glib event loop in fdmon-io_uring.
- aio-posix: fix race between io_uring CQE and AioHandler deletion
- aio-posix: keep polling enabled with fdmon-io_uring.c
- tests/unit: skip test-nested-aio-poll with io_uring
- aio-posix: integrate fdmon into glib event loop
2. Enable fdmon-io_uring on hosts where io_uring is available at runtime.
Otherwise continue using ppoll(2) or epoll(7).
- aio: remove aio_context_use_g_source()
3. Add the new aio_add_sqe() API for submitting io_uring requests in the QEMU
event loop.
- aio: free AioContext when aio_context_new() fails
- aio: add errp argument to aio_context_setup()
- aio-posix: gracefully handle io_uring_queue_init() failure
- aio-posix: add aio_add_sqe() API for user-defined io_uring requests
- aio-posix: avoid EventNotifier for cqe_handler_bh
4. Use aio_add_sqe() in block/io_uring.c instead of creating a dedicated
io_uring context for --blockdev aio=io_uring. This simplifies the code,
reduces the number of file descriptors, and demonstrates the aio_add_sqe()
API.
- block/io_uring: use aio_add_sqe()
- block/io_uring: use non-vectored read/write when possible
The highlight is aio_add_sqe(), which is needed for the FUSE-over-io_uring
Google Summer of Code project and other future QEMU features that natively use
Linux io_uring functionality.
rw bs iodepth aio iothread before after diff
randread 4k 1 native 0 78353 84860 +8.3%
randread 4k 64 native 0 262370 269823 +2.8%
randwrite 4k 1 native 0 142703 144348 +1.2%
randwrite 4k 64 native 0 259947 263895 +1.5%
randread 4k 1 io_uring 0 76883 78270 +1.8%
randread 4k 64 io_uring 0 269712 250513 -7.1%
randwrite 4k 1 io_uring 0 143657 131481 -8.5%
randwrite 4k 64 io_uring 0 274461 264785 -3.5%
randread 4k 1 native 1 84080 84097 0.0%
randread 4k 64 native 1 314650 311193 -1.1%
randwrite 4k 1 native 1 172463 159993 -7.2%
randwrite 4k 64 native 1 303091 299726 -1.1%
randread 4k 1 io_uring 1 83415 84081 +0.8%
randread 4k 64 io_uring 1 324797 318429 -2.0%
randwrite 4k 1 io_uring 1 174421 172809 -0.9%
randwrite 4k 64 io_uring 1 323394 312286 -3.4%
Performance is in the same ballpark as without fdmon-io_uring. Results vary
from run to run due to the timing/batching of requests (even with iodepth=1 due
to 8 vCPUs using a single IOThread).
Here is the performance from v1 for reference:
rw bs iodepth aio iothread before after diff
randread 4k 1 native 0 76281 79707 +4.5%
randread 4k 64 native 0 255078 247293 -3.1%
randwrite 4k 1 native 0 132706 123337 -7.1%
randwrite 4k 64 native 0 275589 245192 -11%
randread 4k 1 io_uring 0 75284 78023 +3.5%
randread 4k 64 io_uring 0 254637 248222 -2.5%
randwrite 4k 1 io_uring 0 126519 128641 +1.7%
randwrite 4k 64 io_uring 0 258967 249266 -3.7%
randread 4k 1 native 1 90557 88436 -2.3%
randread 4k 64 native 1 290673 280456 -3.5%
randwrite 4k 1 native 1 183015 169106 -7.6%
randwrite 4k 64 native 1 281316 280078 -0.4%
randread 4k 1 io_uring 1 92479 86983 -5.9%
randread 4k 64 io_uring 1 304229 257730 -15.3%
randwrite 4k 1 io_uring 1 183983 157425 -14.4%
randwrite 4k 64 io_uring 1 299979 264156 -11.9%
This series replaces the following older series that were held off from merging
until the QEMU 10.1 development window opened and the performance results were
collected:
- "[PATCH 0/3] [RESEND] block: unify block and fdmon io_uring"
- "[PATCH 0/4] aio-posix: integrate fdmon into glib event loop"
Stefan Hajnoczi (13):
aio-posix: fix race between io_uring CQE and AioHandler deletion
aio-posix: keep polling enabled with fdmon-io_uring.c
tests/unit: skip test-nested-aio-poll with io_uring
aio-posix: integrate fdmon into glib event loop
aio: remove aio_context_use_g_source()
aio: free AioContext when aio_context_new() fails
aio: add errp argument to aio_context_setup()
aio-posix: gracefully handle io_uring_queue_init() failure
aio-posix: unindent fdmon_io_uring_destroy()
aio-posix: add fdmon_ops->dispatch()
aio-posix: add aio_add_sqe() API for user-defined io_uring requests
block/io_uring: use aio_add_sqe()
block/io_uring: use non-vectored read/write when possible
include/block/aio.h | 156 ++++++++-
include/block/raw-aio.h | 5 -
util/aio-posix.h | 18 +-
block/file-posix.c | 40 +--
block/io_uring.c | 507 ++++++++----------------------
stubs/io_uring.c | 32 --
tests/unit/test-aio.c | 7 +-
tests/unit/test-nested-aio-poll.c | 13 +-
util/aio-posix.c | 141 ++++-----
util/aio-win32.c | 7 +-
util/async.c | 71 ++---
util/fdmon-epoll.c | 34 +-
util/fdmon-io_uring.c | 211 +++++++++++--
util/fdmon-poll.c | 85 ++++-
block/trace-events | 12 +-
stubs/meson.build | 3 -
util/trace-events | 4 +
17 files changed, 710 insertions(+), 636 deletions(-)
delete mode 100644 stubs/io_uring.c
--
2.51.0
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 01/13] aio-posix: fix race between io_uring CQE and AioHandler deletion
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 02/13] aio-posix: keep polling enabled with fdmon-io_uring.c Stefan Hajnoczi
` (12 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
When an AioHandler is enqueued on ctx->submit_list for removal, the
fill_sq_ring() function will submit an io_uring POLL_REMOVE operation to
cancel the in-flight POLL_ADD operation.
There is a race when another thread enqueues an AioHandler for deletion
on ctx->submit_list when the POLL_ADD CQE has already appeared. In that
case POLL_REMOVE is unnecessary. The code already handled this, but
forgot that the AioHandler itself is still on ctx->submit_list when the
POLL_ADD CQE is being processed. It's unsafe to delete the AioHandler at
that point in time (use-after-free).
Solve this problem by keeping the AioHandler alive but setting a flag so
that it will be deleted by fill_sq_ring() when it runs.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
---
util/fdmon-io_uring.c | 33 ++++++++++++++++++++++++++-------
1 file changed, 26 insertions(+), 7 deletions(-)
diff --git a/util/fdmon-io_uring.c b/util/fdmon-io_uring.c
index b0d68bdc44..ad89160f31 100644
--- a/util/fdmon-io_uring.c
+++ b/util/fdmon-io_uring.c
@@ -52,9 +52,10 @@ enum {
FDMON_IO_URING_ENTRIES = 128, /* sq/cq ring size */
/* AioHandler::flags */
- FDMON_IO_URING_PENDING = (1 << 0),
- FDMON_IO_URING_ADD = (1 << 1),
- FDMON_IO_URING_REMOVE = (1 << 2),
+ FDMON_IO_URING_PENDING = (1 << 0),
+ FDMON_IO_URING_ADD = (1 << 1),
+ FDMON_IO_URING_REMOVE = (1 << 2),
+ FDMON_IO_URING_DELETE_AIO_HANDLER = (1 << 3),
};
static inline int poll_events_from_pfd(int pfd_events)
@@ -218,6 +219,16 @@ static void fill_sq_ring(AioContext *ctx)
if (flags & FDMON_IO_URING_REMOVE) {
add_poll_remove_sqe(ctx, node);
}
+ if (flags & FDMON_IO_URING_DELETE_AIO_HANDLER) {
+ /*
+ * process_cqe() sets this flag after ADD and REMOVE have been
+ * cleared. They cannot be set again, so they must be clear.
+ */
+ assert(!(flags & FDMON_IO_URING_ADD));
+ assert(!(flags & FDMON_IO_URING_REMOVE));
+
+ QLIST_INSERT_HEAD_RCU(&ctx->deleted_aio_handlers, node, node_deleted);
+ }
}
}
@@ -241,7 +252,12 @@ static bool process_cqe(AioContext *ctx,
*/
flags = qatomic_fetch_and(&node->flags, ~FDMON_IO_URING_REMOVE);
if (flags & FDMON_IO_URING_REMOVE) {
- QLIST_INSERT_HEAD_RCU(&ctx->deleted_aio_handlers, node, node_deleted);
+ if (flags & FDMON_IO_URING_PENDING) {
+ /* Still on ctx->submit_list, defer deletion until fill_sq_ring() */
+ qatomic_or(&node->flags, FDMON_IO_URING_DELETE_AIO_HANDLER);
+ } else {
+ QLIST_INSERT_HEAD_RCU(&ctx->deleted_aio_handlers, node, node_deleted);
+ }
return false;
}
@@ -347,10 +363,13 @@ void fdmon_io_uring_destroy(AioContext *ctx)
unsigned flags = qatomic_fetch_and(&node->flags,
~(FDMON_IO_URING_PENDING |
FDMON_IO_URING_ADD |
- FDMON_IO_URING_REMOVE));
+ FDMON_IO_URING_REMOVE |
+ FDMON_IO_URING_DELETE_AIO_HANDLER));
- if (flags & FDMON_IO_URING_REMOVE) {
- QLIST_INSERT_HEAD_RCU(&ctx->deleted_aio_handlers, node, node_deleted);
+ if ((flags & FDMON_IO_URING_REMOVE) ||
+ (flags & FDMON_IO_URING_DELETE_AIO_HANDLER)) {
+ QLIST_INSERT_HEAD_RCU(&ctx->deleted_aio_handlers,
+ node, node_deleted);
}
QSLIST_REMOVE_HEAD_RCU(&ctx->submit_list, node_submitted);
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 02/13] aio-posix: keep polling enabled with fdmon-io_uring.c
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 01/13] aio-posix: fix race between io_uring CQE and AioHandler deletion Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 03/13] tests/unit: skip test-nested-aio-poll with io_uring Stefan Hajnoczi
` (11 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block, Chao Gao
Commit 816a430c517e ("util/aio: Defer disabling poll mode as long as
possible") kept polling enabled when the event loop timeout is 0. Since
there is no timeout the event loop will continue immediately and the
overhead of disabling and re-enabling polling can be avoided.
fdmon-io_uring.c is unable to take advantage of this optimization
because its ->need_wait() function returns true whenever there are new
io_uring SQEs to submit:
if (timeout || ctx->fdmon_ops->need_wait(ctx)) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Polling will be disabled even when timeout == 0.
Extend the optimization to handle the case when need_wait() returns true
and timeout == 0.
Cc: Chao Gao <chao.gao@intel.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
---
util/aio-posix.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/util/aio-posix.c b/util/aio-posix.c
index 2e0a5dadc4..824fdc34cc 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -559,7 +559,14 @@ static bool run_poll_handlers(AioContext *ctx, AioHandlerList *ready_list,
elapsed_time = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start_time;
max_ns = qemu_soonest_timeout(*timeout, max_ns);
assert(!(max_ns && progress));
- } while (elapsed_time < max_ns && !ctx->fdmon_ops->need_wait(ctx));
+
+ if (ctx->fdmon_ops->need_wait(ctx)) {
+ if (fdmon_supports_polling(ctx)) {
+ *timeout = 0; /* stay in polling mode */
+ }
+ break;
+ }
+ } while (elapsed_time < max_ns);
if (remove_idle_poll_handlers(ctx, ready_list,
start_time + elapsed_time)) {
@@ -722,7 +729,7 @@ bool aio_poll(AioContext *ctx, bool blocking)
* up IO threads when some work becomes pending. It is essential to
* avoid hangs or unnecessary latency.
*/
- if (poll_set_started(ctx, &ready_list, false)) {
+ if (timeout && poll_set_started(ctx, &ready_list, false)) {
timeout = 0;
progress = true;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 03/13] tests/unit: skip test-nested-aio-poll with io_uring
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 01/13] aio-posix: fix race between io_uring CQE and AioHandler deletion Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 02/13] aio-posix: keep polling enabled with fdmon-io_uring.c Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 04/13] aio-posix: integrate fdmon into glib event loop Stefan Hajnoczi
` (10 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
test-nested-aio-poll relies on internal details of how fdmon-poll.c
handles AioContext polling. Skip it when other fdmon implementations are
in use.
The reason why fdmon-io_uring.c behaves differently from fdmon-poll.c is
that its fdmon_ops->need_wait() function returns true when
io_uring_enter(2) must be called (e.g. to submit pending SQEs).
AioContext polling is skipped when ->need_wait() returns true, so the
test case will never enter AioContext polling mode with
fdmon-io_uring.c.
Restrict this test to fdmon-poll.c and drop the
aio_context_use_g_source() call since it's no longer necessary.
Note that this test is only built on POSIX systems so it is safe to
include "util/aio-posix.h".
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
v5:
- Explain how fdmon-io_uring.c differs from other fdmon implementations
in commit message [Kevin]
- Move test-nested-aio-poll aio_get_g_source() removal into commit that touches test case [Kevin]
---
tests/unit/test-nested-aio-poll.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/tests/unit/test-nested-aio-poll.c b/tests/unit/test-nested-aio-poll.c
index d8fd92c43b..d13ecccd8c 100644
--- a/tests/unit/test-nested-aio-poll.c
+++ b/tests/unit/test-nested-aio-poll.c
@@ -15,6 +15,7 @@
#include "qemu/osdep.h"
#include "block/aio.h"
#include "qapi/error.h"
+#include "util/aio-posix.h"
typedef struct {
AioContext *ctx;
@@ -71,17 +72,17 @@ static void test(void)
.ctx = aio_context_new(&error_abort),
};
+ if (td.ctx->fdmon_ops != &fdmon_poll_ops) {
+ /* This test is tied to fdmon-poll.c */
+ g_test_skip("fdmon_poll_ops not in use");
+ return;
+ }
+
qemu_set_current_aio_context(td.ctx);
/* Enable polling */
aio_context_set_poll_params(td.ctx, 1000000, 2, 2, &error_abort);
- /*
- * The GSource is unused but this has the side-effect of changing the fdmon
- * that AioContext uses.
- */
- aio_get_g_source(td.ctx);
-
/* Make the event notifier active (set) right away */
event_notifier_init(&td.poll_notifier, 1);
aio_set_event_notifier(td.ctx, &td.poll_notifier,
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 04/13] aio-posix: integrate fdmon into glib event loop
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (2 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 03/13] tests/unit: skip test-nested-aio-poll with io_uring Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 05/13] aio: remove aio_context_use_g_source() Stefan Hajnoczi
` (9 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
AioContext's glib integration only supports ppoll(2) file descriptor
monitoring. epoll(7) and io_uring(7) disable themselves and switch back
to ppoll(2) when the glib event loop is used. The main loop thread
cannot use epoll(7) or io_uring(7) because it always uses the glib event
loop.
Future QEMU features may require io_uring(7). One example is uring_cmd
support in FUSE exports. Each feature could create its own io_uring(7)
context and integrate it into the event loop, but this is inefficient
due to extra syscalls. It would be more efficient to reuse the
AioContext's existing fdmon-io_uring.c io_uring(7) context because
fdmon-io_uring.c will already be active on systems where Linux io_uring
is available.
In order to keep fdmon-io_uring.c's AioContext operational even when the
glib event loop is used, extend FDMonOps with an API similar to
GSourceFuncs so that file descriptor monitoring can integrate into the
glib event loop.
A quick summary of the GSourceFuncs API:
- prepare() is called each event loop iteration before waiting for file
descriptors and timers.
- check() is called to determine whether events are ready to be
dispatched after waiting.
- dispatch() is called to process events.
More details here: https://docs.gtk.org/glib/struct.SourceFuncs.html
Move the ppoll(2)-specific code from aio-posix.c into fdmon-poll.c and
also implement epoll(7)- and io_uring(7)-specific file descriptor
monitoring code for glib event loops.
Note that it's still faster to use aio_poll() rather than the glib event
loop since glib waits for file descriptor activity with ppoll(2) and
does not support adaptive polling. But at least epoll(7) and io_uring(7)
now work in glib event loops.
Splitting this into multiple commits without temporarily breaking
AioContext proved difficult so this commit makes all the changes. The
next commit will remove the aio_context_use_g_source() API because it is
no longer needed.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
v5:
- Avoid g_source_add_poll() use-after-free in fdmon_poll_update() [Kevin]
- Avoid duplication in fdmon_epoll_gsource_dispatch(), use fdmon_epoll_wait() [Kevin]
- Drop unnecessary revents checks in fdmon_poll_gsource_dispatch() [Kevin]
---
include/block/aio.h | 36 ++++++++++++++++++
util/aio-posix.h | 5 +++
tests/unit/test-aio.c | 7 +++-
util/aio-posix.c | 69 ++++++++---------------------------
util/fdmon-epoll.c | 34 ++++++++++++++---
util/fdmon-io_uring.c | 44 +++++++++++++++++++++-
util/fdmon-poll.c | 85 ++++++++++++++++++++++++++++++++++++++++++-
7 files changed, 218 insertions(+), 62 deletions(-)
diff --git a/include/block/aio.h b/include/block/aio.h
index 99ff48420b..39ed86d14d 100644
--- a/include/block/aio.h
+++ b/include/block/aio.h
@@ -106,6 +106,38 @@ typedef struct {
* Returns: true if ->wait() should be called, false otherwise.
*/
bool (*need_wait)(AioContext *ctx);
+
+ /*
+ * gsource_prepare:
+ * @ctx: the AioContext
+ *
+ * Prepare for the glib event loop to wait for events instead of the usual
+ * ->wait() call. See glib's GSourceFuncs->prepare().
+ */
+ void (*gsource_prepare)(AioContext *ctx);
+
+ /*
+ * gsource_check:
+ * @ctx: the AioContext
+ *
+ * Called by the glib event loop from glib's GSourceFuncs->check() after
+ * waiting for events.
+ *
+ * Returns: true when ready to be dispatched.
+ */
+ bool (*gsource_check)(AioContext *ctx);
+
+ /*
+ * gsource_dispatch:
+ * @ctx: the AioContext
+ * @ready_list: list for handlers that become ready
+ *
+ * Place ready AioHandlers on ready_list. Called as part of the glib event
+ * loop from glib's GSourceFuncs->dispatch().
+ *
+ * Called with list_lock incremented.
+ */
+ void (*gsource_dispatch)(AioContext *ctx, AioHandlerList *ready_list);
} FDMonOps;
/*
@@ -222,6 +254,7 @@ struct AioContext {
/* State for file descriptor monitoring using Linux io_uring */
struct io_uring fdmon_io_uring;
AioHandlerSList submit_list;
+ gpointer io_uring_fd_tag;
#endif
/* TimerLists for calling timers - one per clock type. Has its own
@@ -254,6 +287,9 @@ struct AioContext {
/* epoll(7) state used when built with CONFIG_EPOLL */
int epollfd;
+ /* The GSource unix fd tag for epollfd */
+ gpointer epollfd_tag;
+
const FDMonOps *fdmon_ops;
};
diff --git a/util/aio-posix.h b/util/aio-posix.h
index 82a0201ea4..f9994ed79e 100644
--- a/util/aio-posix.h
+++ b/util/aio-posix.h
@@ -47,9 +47,14 @@ void aio_add_ready_handler(AioHandlerList *ready_list, AioHandler *node,
extern const FDMonOps fdmon_poll_ops;
+/* Switch back to poll(2). list_lock must be held. */
+void fdmon_poll_downgrade(AioContext *ctx);
+
#ifdef CONFIG_EPOLL_CREATE1
bool fdmon_epoll_try_upgrade(AioContext *ctx, unsigned npfd);
void fdmon_epoll_setup(AioContext *ctx);
+
+/* list_lock must be held */
void fdmon_epoll_disable(AioContext *ctx);
#else
static inline bool fdmon_epoll_try_upgrade(AioContext *ctx, unsigned npfd)
diff --git a/tests/unit/test-aio.c b/tests/unit/test-aio.c
index e77d86be87..010d65b79a 100644
--- a/tests/unit/test-aio.c
+++ b/tests/unit/test-aio.c
@@ -527,7 +527,12 @@ static void test_source_bh_delete_from_cb(void)
g_assert_cmpint(data1.n, ==, data1.max);
g_assert(data1.bh == NULL);
- assert(g_main_context_iteration(NULL, false));
+ /*
+ * There may be up to one more iteration due to the aio_notify
+ * EventNotifier.
+ */
+ g_main_context_iteration(NULL, false);
+
assert(!g_main_context_iteration(NULL, false));
}
diff --git a/util/aio-posix.c b/util/aio-posix.c
index 824fdc34cc..9de05ee7e8 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -70,15 +70,6 @@ static AioHandler *find_aio_handler(AioContext *ctx, int fd)
static bool aio_remove_fd_handler(AioContext *ctx, AioHandler *node)
{
- /* If the GSource is in the process of being destroyed then
- * g_source_remove_poll() causes an assertion failure. Skip
- * removal in that case, because glib cleans up its state during
- * destruction anyway.
- */
- if (!g_source_is_destroyed(&ctx->source)) {
- g_source_remove_poll(&ctx->source, &node->pfd);
- }
-
node->pfd.revents = 0;
node->poll_ready = false;
@@ -153,7 +144,6 @@ void aio_set_fd_handler(AioContext *ctx,
} else {
new_node->pfd = node->pfd;
}
- g_source_add_poll(&ctx->source, &new_node->pfd);
new_node->pfd.events = (io_read ? G_IO_IN | G_IO_HUP | G_IO_ERR : 0);
new_node->pfd.events |= (io_write ? G_IO_OUT | G_IO_ERR : 0);
@@ -267,37 +257,13 @@ bool aio_prepare(AioContext *ctx)
poll_set_started(ctx, &ready_list, false);
/* TODO what to do with this list? */
+ ctx->fdmon_ops->gsource_prepare(ctx);
return false;
}
bool aio_pending(AioContext *ctx)
{
- AioHandler *node;
- bool result = false;
-
- /*
- * We have to walk very carefully in case aio_set_fd_handler is
- * called while we're walking.
- */
- qemu_lockcnt_inc(&ctx->list_lock);
-
- QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
- int revents;
-
- /* TODO should this check poll ready? */
- revents = node->pfd.revents & node->pfd.events;
- if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read) {
- result = true;
- break;
- }
- if (revents & (G_IO_OUT | G_IO_ERR) && node->io_write) {
- result = true;
- break;
- }
- }
- qemu_lockcnt_dec(&ctx->list_lock);
-
- return result;
+ return ctx->fdmon_ops->gsource_check(ctx);
}
static void aio_free_deleted_handlers(AioContext *ctx)
@@ -390,10 +356,6 @@ static bool aio_dispatch_handler(AioContext *ctx, AioHandler *node)
return progress;
}
-/*
- * If we have a list of ready handlers then this is more efficient than
- * scanning all handlers with aio_dispatch_handlers().
- */
static bool aio_dispatch_ready_handlers(AioContext *ctx,
AioHandlerList *ready_list,
int64_t block_ns)
@@ -417,24 +379,18 @@ static bool aio_dispatch_ready_handlers(AioContext *ctx,
return progress;
}
-/* Slower than aio_dispatch_ready_handlers() but only used via glib */
-static bool aio_dispatch_handlers(AioContext *ctx)
-{
- AioHandler *node, *tmp;
- bool progress = false;
-
- QLIST_FOREACH_SAFE_RCU(node, &ctx->aio_handlers, node, tmp) {
- progress = aio_dispatch_handler(ctx, node) || progress;
- }
-
- return progress;
-}
-
void aio_dispatch(AioContext *ctx)
{
+ AioHandlerList ready_list = QLIST_HEAD_INITIALIZER(ready_list);
+
qemu_lockcnt_inc(&ctx->list_lock);
aio_bh_poll(ctx);
- aio_dispatch_handlers(ctx);
+
+ ctx->fdmon_ops->gsource_dispatch(ctx, &ready_list);
+
+ /* block_ns is 0 because polling is disabled in the glib event loop */
+ aio_dispatch_ready_handlers(ctx, &ready_list, 0);
+
aio_free_deleted_handlers(ctx);
qemu_lockcnt_dec(&ctx->list_lock);
@@ -766,6 +722,7 @@ void aio_context_setup(AioContext *ctx)
{
ctx->fdmon_ops = &fdmon_poll_ops;
ctx->epollfd = -1;
+ ctx->epollfd_tag = NULL;
/* Use the fastest fd monitoring implementation if available */
if (fdmon_io_uring_setup(ctx)) {
@@ -778,7 +735,11 @@ void aio_context_setup(AioContext *ctx)
void aio_context_destroy(AioContext *ctx)
{
fdmon_io_uring_destroy(ctx);
+
+ qemu_lockcnt_lock(&ctx->list_lock);
fdmon_epoll_disable(ctx);
+ qemu_lockcnt_unlock(&ctx->list_lock);
+
aio_free_deleted_handlers(ctx);
}
diff --git a/util/fdmon-epoll.c b/util/fdmon-epoll.c
index 9fb8800dde..61118e1ee6 100644
--- a/util/fdmon-epoll.c
+++ b/util/fdmon-epoll.c
@@ -19,8 +19,12 @@ void fdmon_epoll_disable(AioContext *ctx)
ctx->epollfd = -1;
}
- /* Switch back */
- ctx->fdmon_ops = &fdmon_poll_ops;
+ if (ctx->epollfd_tag) {
+ g_source_remove_unix_fd(&ctx->source, ctx->epollfd_tag);
+ ctx->epollfd_tag = NULL;
+ }
+
+ fdmon_poll_downgrade(ctx);
}
static inline int epoll_events_from_pfd(int pfd_events)
@@ -93,10 +97,29 @@ out:
return ret;
}
+static void fdmon_epoll_gsource_prepare(AioContext *ctx)
+{
+ /* Do nothing */
+}
+
+static bool fdmon_epoll_gsource_check(AioContext *ctx)
+{
+ return g_source_query_unix_fd(&ctx->source, ctx->epollfd_tag) & G_IO_IN;
+}
+
+static void fdmon_epoll_gsource_dispatch(AioContext *ctx,
+ AioHandlerList *ready_list)
+{
+ fdmon_epoll_wait(ctx, ready_list, 0);
+}
+
static const FDMonOps fdmon_epoll_ops = {
.update = fdmon_epoll_update,
.wait = fdmon_epoll_wait,
.need_wait = aio_poll_disabled,
+ .gsource_prepare = fdmon_epoll_gsource_prepare,
+ .gsource_check = fdmon_epoll_gsource_check,
+ .gsource_dispatch = fdmon_epoll_gsource_dispatch,
};
static bool fdmon_epoll_try_enable(AioContext *ctx)
@@ -118,6 +141,8 @@ static bool fdmon_epoll_try_enable(AioContext *ctx)
}
ctx->fdmon_ops = &fdmon_epoll_ops;
+ ctx->epollfd_tag = g_source_add_unix_fd(&ctx->source, ctx->epollfd,
+ G_IO_IN);
return true;
}
@@ -139,12 +164,11 @@ bool fdmon_epoll_try_upgrade(AioContext *ctx, unsigned npfd)
}
ok = fdmon_epoll_try_enable(ctx);
-
- qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
-
if (!ok) {
fdmon_epoll_disable(ctx);
}
+
+ qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
return ok;
}
diff --git a/util/fdmon-io_uring.c b/util/fdmon-io_uring.c
index ad89160f31..4b6dc8903f 100644
--- a/util/fdmon-io_uring.c
+++ b/util/fdmon-io_uring.c
@@ -276,6 +276,11 @@ static int process_cq_ring(AioContext *ctx, AioHandlerList *ready_list)
unsigned num_ready = 0;
unsigned head;
+ /* If the CQ overflowed then fetch CQEs with a syscall */
+ if (io_uring_cq_has_overflow(ring)) {
+ io_uring_get_events(ring);
+ }
+
io_uring_for_each_cqe(ring, head, cqe) {
if (process_cqe(ctx, ready_list, cqe)) {
num_ready++;
@@ -288,6 +293,30 @@ static int process_cq_ring(AioContext *ctx, AioHandlerList *ready_list)
return num_ready;
}
+/* This is where SQEs are submitted in the glib event loop */
+static void fdmon_io_uring_gsource_prepare(AioContext *ctx)
+{
+ fill_sq_ring(ctx);
+ if (io_uring_sq_ready(&ctx->fdmon_io_uring)) {
+ while (io_uring_submit(&ctx->fdmon_io_uring) == -EINTR) {
+ /* Keep trying if syscall was interrupted */
+ }
+ }
+}
+
+static bool fdmon_io_uring_gsource_check(AioContext *ctx)
+{
+ gpointer tag = ctx->io_uring_fd_tag;
+ return g_source_query_unix_fd(&ctx->source, tag) & G_IO_IN;
+}
+
+/* This is where CQEs are processed in the glib event loop */
+static void fdmon_io_uring_gsource_dispatch(AioContext *ctx,
+ AioHandlerList *ready_list)
+{
+ process_cq_ring(ctx, ready_list);
+}
+
static int fdmon_io_uring_wait(AioContext *ctx, AioHandlerList *ready_list,
int64_t timeout)
{
@@ -335,12 +364,17 @@ static const FDMonOps fdmon_io_uring_ops = {
.update = fdmon_io_uring_update,
.wait = fdmon_io_uring_wait,
.need_wait = fdmon_io_uring_need_wait,
+ .gsource_prepare = fdmon_io_uring_gsource_prepare,
+ .gsource_check = fdmon_io_uring_gsource_check,
+ .gsource_dispatch = fdmon_io_uring_gsource_dispatch,
};
bool fdmon_io_uring_setup(AioContext *ctx)
{
int ret;
+ ctx->io_uring_fd_tag = NULL;
+
ret = io_uring_queue_init(FDMON_IO_URING_ENTRIES, &ctx->fdmon_io_uring, 0);
if (ret != 0) {
return false;
@@ -348,6 +382,9 @@ bool fdmon_io_uring_setup(AioContext *ctx)
QSLIST_INIT(&ctx->submit_list);
ctx->fdmon_ops = &fdmon_io_uring_ops;
+ ctx->io_uring_fd_tag = g_source_add_unix_fd(&ctx->source,
+ ctx->fdmon_io_uring.ring_fd, G_IO_IN);
+
return true;
}
@@ -375,6 +412,11 @@ void fdmon_io_uring_destroy(AioContext *ctx)
QSLIST_REMOVE_HEAD_RCU(&ctx->submit_list, node_submitted);
}
- ctx->fdmon_ops = &fdmon_poll_ops;
+ g_source_remove_unix_fd(&ctx->source, ctx->io_uring_fd_tag);
+ ctx->io_uring_fd_tag = NULL;
+
+ qemu_lockcnt_lock(&ctx->list_lock);
+ fdmon_poll_downgrade(ctx);
+ qemu_lockcnt_unlock(&ctx->list_lock);
}
}
diff --git a/util/fdmon-poll.c b/util/fdmon-poll.c
index 17df917cf9..0ae755cc13 100644
--- a/util/fdmon-poll.c
+++ b/util/fdmon-poll.c
@@ -72,6 +72,11 @@ static int fdmon_poll_wait(AioContext *ctx, AioHandlerList *ready_list,
/* epoll(7) is faster above a certain number of fds */
if (fdmon_epoll_try_upgrade(ctx, npfd)) {
+ QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
+ if (!QLIST_IS_INSERTED(node, node_deleted) && node->pfd.events) {
+ g_source_remove_poll(&ctx->source, &node->pfd);
+ }
+ }
npfd = 0; /* we won't need pollfds[], reset npfd */
return ctx->fdmon_ops->wait(ctx, ready_list, timeout);
}
@@ -97,11 +102,89 @@ static void fdmon_poll_update(AioContext *ctx,
AioHandler *old_node,
AioHandler *new_node)
{
- /* Do nothing, AioHandler already contains the state we'll need */
+ if (old_node) {
+ /*
+ * If the GSource is in the process of being destroyed then
+ * g_source_remove_poll() causes an assertion failure. Skip removal in
+ * that case, because glib cleans up its state during destruction
+ * anyway.
+ */
+ if (!g_source_is_destroyed(&ctx->source)) {
+ g_source_remove_poll(&ctx->source, &old_node->pfd);
+ }
+ }
+
+ if (new_node) {
+ g_source_add_poll(&ctx->source, &new_node->pfd);
+ }
+}
+
+static void fdmon_poll_gsource_prepare(AioContext *ctx)
+{
+ /* Do nothing */
+}
+
+static bool fdmon_poll_gsource_check(AioContext *ctx)
+{
+ AioHandler *node;
+ bool result = false;
+
+ /*
+ * We have to walk very carefully in case aio_set_fd_handler is
+ * called while we're walking.
+ */
+ qemu_lockcnt_inc(&ctx->list_lock);
+
+ QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
+ int revents = node->pfd.revents & node->pfd.events;
+
+ if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read) {
+ result = true;
+ break;
+ }
+ if (revents & (G_IO_OUT | G_IO_ERR) && node->io_write) {
+ result = true;
+ break;
+ }
+ }
+
+ qemu_lockcnt_dec(&ctx->list_lock);
+
+ return result;
+}
+
+static void fdmon_poll_gsource_dispatch(AioContext *ctx,
+ AioHandlerList *ready_list)
+{
+ AioHandler *node;
+
+ QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
+ int revents = node->pfd.revents;
+
+ if (revents) {
+ aio_add_ready_handler(ready_list, node, revents);
+ }
+ }
}
const FDMonOps fdmon_poll_ops = {
.update = fdmon_poll_update,
.wait = fdmon_poll_wait,
.need_wait = aio_poll_disabled,
+ .gsource_prepare = fdmon_poll_gsource_prepare,
+ .gsource_check = fdmon_poll_gsource_check,
+ .gsource_dispatch = fdmon_poll_gsource_dispatch,
};
+
+void fdmon_poll_downgrade(AioContext *ctx)
+{
+ AioHandler *node;
+
+ ctx->fdmon_ops = &fdmon_poll_ops;
+
+ QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
+ if (!QLIST_IS_INSERTED(node, node_deleted) && node->pfd.events) {
+ g_source_add_poll(&ctx->source, &node->pfd);
+ }
+ }
+}
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 05/13] aio: remove aio_context_use_g_source()
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (3 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 04/13] aio-posix: integrate fdmon into glib event loop Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 06/13] aio: free AioContext when aio_context_new() fails Stefan Hajnoczi
` (8 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
There is no need for aio_context_use_g_source() now that epoll(7) and
io_uring(7) file descriptor monitoring works with the glib event loop.
AioContext doesn't need to be notified that GSource is being used.
On hosts with io_uring support this now enables fdmon-io_uring.c by
default, replacing fdmon-poll.c and fdmon-epoll.c. In other words, the
event loop will use io_uring!
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
---
v5:
- Mention in commit message that fdmon-io_uring.c is the new default [Kevin]
---
include/block/aio.h | 3 ---
util/aio-posix.c | 12 ------------
util/aio-win32.c | 4 ----
util/async.c | 1 -
4 files changed, 20 deletions(-)
diff --git a/include/block/aio.h b/include/block/aio.h
index 39ed86d14d..1657740a0e 100644
--- a/include/block/aio.h
+++ b/include/block/aio.h
@@ -728,9 +728,6 @@ void aio_context_setup(AioContext *ctx);
*/
void aio_context_destroy(AioContext *ctx);
-/* Used internally, do not call outside AioContext code */
-void aio_context_use_g_source(AioContext *ctx);
-
/**
* aio_context_set_poll_params:
* @ctx: the aio context
diff --git a/util/aio-posix.c b/util/aio-posix.c
index 9de05ee7e8..bebd9ce3a2 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -743,18 +743,6 @@ void aio_context_destroy(AioContext *ctx)
aio_free_deleted_handlers(ctx);
}
-void aio_context_use_g_source(AioContext *ctx)
-{
- /*
- * Disable io_uring when the glib main loop is used because it doesn't
- * support mixed glib/aio_poll() usage. It relies on aio_poll() being
- * called regularly so that changes to the monitored file descriptors are
- * submitted, otherwise a list of pending fd handlers builds up.
- */
- fdmon_io_uring_destroy(ctx);
- aio_free_deleted_handlers(ctx);
-}
-
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
int64_t grow, int64_t shrink, Error **errp)
{
diff --git a/util/aio-win32.c b/util/aio-win32.c
index c6fbce64c2..18cc9fb7a9 100644
--- a/util/aio-win32.c
+++ b/util/aio-win32.c
@@ -427,10 +427,6 @@ void aio_context_destroy(AioContext *ctx)
{
}
-void aio_context_use_g_source(AioContext *ctx)
-{
-}
-
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
int64_t grow, int64_t shrink, Error **errp)
{
diff --git a/util/async.c b/util/async.c
index a736d2cd0d..cb72ad3777 100644
--- a/util/async.c
+++ b/util/async.c
@@ -433,7 +433,6 @@ static GSourceFuncs aio_source_funcs = {
GSource *aio_get_g_source(AioContext *ctx)
{
- aio_context_use_g_source(ctx);
g_source_ref(&ctx->source);
return &ctx->source;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 06/13] aio: free AioContext when aio_context_new() fails
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (4 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 05/13] aio: remove aio_context_use_g_source() Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 07/13] aio: add errp argument to aio_context_setup() Stefan Hajnoczi
` (7 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
g_source_destroy() only removes the GSource from the GMainContext it's
attached to, if any. It does not free it.
Use g_source_unref() instead so that the AioContext (which embeds a
GSource) is freed. There is no need to call g_source_destroy() in
aio_context_new() because the GSource isn't attached to a GMainContext
yet.
aio_ctx_finalize() expects everything to be set up already, so introduce
the new ctx->initialized boolean and do nothing when called with
!initialized. This also requires moving aio_context_setup() down after
event_notifier_init() since aio_ctx_finalize() won't release any
resources that aio_context_setup() acquired.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
v5:
- Added comments explaining how to clean up resources in error paths [Kevin]
v2:
- Fix spacing in aio_ctx_finalize() argument list [Eric]
---
include/block/aio.h | 3 +++
util/async.c | 31 ++++++++++++++++++++++++++++---
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/include/block/aio.h b/include/block/aio.h
index 1657740a0e..2760f308f5 100644
--- a/include/block/aio.h
+++ b/include/block/aio.h
@@ -291,6 +291,9 @@ struct AioContext {
gpointer epollfd_tag;
const FDMonOps *fdmon_ops;
+
+ /* Was aio_context_new() successful? */
+ bool initialized;
};
/**
diff --git a/util/async.c b/util/async.c
index cb72ad3777..7d06ff98f3 100644
--- a/util/async.c
+++ b/util/async.c
@@ -366,12 +366,16 @@ aio_ctx_dispatch(GSource *source,
}
static void
-aio_ctx_finalize(GSource *source)
+aio_ctx_finalize(GSource *source)
{
AioContext *ctx = (AioContext *) source;
QEMUBH *bh;
unsigned flags;
+ if (!ctx->initialized) {
+ return;
+ }
+
thread_pool_free_aio(ctx->thread_pool);
#ifdef CONFIG_LINUX_AIO
@@ -579,16 +583,35 @@ AioContext *aio_context_new(Error **errp)
int ret;
AioContext *ctx;
+ /*
+ * ctx is freed by g_source_unref() (e.g. aio_context_unref()). ctx's
+ * resources are freed as follows:
+ *
+ * 1. By aio_ctx_finalize() after aio_context_new() has returned and set
+ * ->initialized = true.
+ *
+ * 2. By manual cleanup code in this function's error paths before goto
+ * fail.
+ *
+ * Be careful to free resources in both cases!
+ */
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
QSLIST_INIT(&ctx->bh_list);
QSIMPLEQ_INIT(&ctx->bh_slice_list);
- aio_context_setup(ctx);
ret = event_notifier_init(&ctx->notifier, false);
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to initialize event notifier");
goto fail;
}
+
+ /*
+ * Resources cannot easily be freed manually after aio_context_setup(). If
+ * you add any new resources to AioContext, it's probably best to acquire
+ * them before aio_context_setup().
+ */
+ aio_context_setup(ctx);
+
g_source_set_can_recurse(&ctx->source, true);
qemu_lockcnt_init(&ctx->list_lock);
@@ -622,9 +645,11 @@ AioContext *aio_context_new(Error **errp)
register_aiocontext(ctx);
+ ctx->initialized = true;
+
return ctx;
fail:
- g_source_destroy(&ctx->source);
+ g_source_unref(&ctx->source);
return NULL;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 07/13] aio: add errp argument to aio_context_setup()
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (5 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 06/13] aio: free AioContext when aio_context_new() fails Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 08/13] aio-posix: gracefully handle io_uring_queue_init() failure Stefan Hajnoczi
` (6 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
When aio_context_new() -> aio_context_setup() fails at startup it
doesn't really matter whether errors are returned to the caller or the
process terminates immediately.
However, it is not acceptable to terminate when hotplugging --object
iothread at runtime. Refactor aio_context_setup() so that errors can be
propagated. The next commit will set errp when fdmon_io_uring_setup()
fails.
Suggested-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
---
v5:
- Indicate error in return value from function with Error *errp arg [Kevin]
---
include/block/aio.h | 5 ++++-
util/aio-posix.c | 5 +++--
util/aio-win32.c | 3 ++-
util/async.c | 6 +++++-
4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/include/block/aio.h b/include/block/aio.h
index 2760f308f5..9562733fa7 100644
--- a/include/block/aio.h
+++ b/include/block/aio.h
@@ -718,10 +718,13 @@ void qemu_set_current_aio_context(AioContext *ctx);
/**
* aio_context_setup:
* @ctx: the aio context
+ * @errp: error pointer
*
* Initialize the aio context.
+ *
+ * Returns: true on success, false otherwise
*/
-void aio_context_setup(AioContext *ctx);
+bool aio_context_setup(AioContext *ctx, Error **errp);
/**
* aio_context_destroy:
diff --git a/util/aio-posix.c b/util/aio-posix.c
index bebd9ce3a2..9806a75c12 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -718,7 +718,7 @@ bool aio_poll(AioContext *ctx, bool blocking)
return progress;
}
-void aio_context_setup(AioContext *ctx)
+bool aio_context_setup(AioContext *ctx, Error **errp)
{
ctx->fdmon_ops = &fdmon_poll_ops;
ctx->epollfd = -1;
@@ -726,10 +726,11 @@ void aio_context_setup(AioContext *ctx)
/* Use the fastest fd monitoring implementation if available */
if (fdmon_io_uring_setup(ctx)) {
- return;
+ return true;
}
fdmon_epoll_setup(ctx);
+ return true;
}
void aio_context_destroy(AioContext *ctx)
diff --git a/util/aio-win32.c b/util/aio-win32.c
index 18cc9fb7a9..6e6f699e4b 100644
--- a/util/aio-win32.c
+++ b/util/aio-win32.c
@@ -419,8 +419,9 @@ bool aio_poll(AioContext *ctx, bool blocking)
return progress;
}
-void aio_context_setup(AioContext *ctx)
+bool aio_context_setup(AioContext *ctx, Error **errp)
{
+ return true;
}
void aio_context_destroy(AioContext *ctx)
diff --git a/util/async.c b/util/async.c
index 7d06ff98f3..00e46b99f9 100644
--- a/util/async.c
+++ b/util/async.c
@@ -580,6 +580,7 @@ static void co_schedule_bh_cb(void *opaque)
AioContext *aio_context_new(Error **errp)
{
+ ERRP_GUARD();
int ret;
AioContext *ctx;
@@ -610,7 +611,10 @@ AioContext *aio_context_new(Error **errp)
* you add any new resources to AioContext, it's probably best to acquire
* them before aio_context_setup().
*/
- aio_context_setup(ctx);
+ if (!aio_context_setup(ctx, errp)) {
+ event_notifier_cleanup(&ctx->notifier);
+ goto fail;
+ }
g_source_set_can_recurse(&ctx->source, true);
qemu_lockcnt_init(&ctx->list_lock);
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 08/13] aio-posix: gracefully handle io_uring_queue_init() failure
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (6 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 07/13] aio: add errp argument to aio_context_setup() Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 09/13] aio-posix: unindent fdmon_io_uring_destroy() Stefan Hajnoczi
` (5 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
io_uring may not be available at runtime due to system policies (e.g.
the io_uring_disabled sysctl) or creation could fail due to file
descriptor resource limits.
Handle failure scenarios as follows:
If another AioContext already has io_uring, then fail AioContext
creation so that the aio_add_sqe() API is available uniformly from all
QEMU threads. Otherwise fall back to epoll(7) if io_uring is
unavailable.
Notes:
- Update the comment about selecting the fastest fdmon implementation.
At this point it's not about speed anymore, it's about aio_add_sqe()
API availability.
- Uppercase the error message when converting from error_report() to
error_setg_errno() for consistency (but there are instances of
lowercase in the codebase).
- It's easier to move the #ifdefs from aio-posix.h to aio-posix.c.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
---
v5:
- Indicate error in return value from function with Error *errp arg [Kevin]
---
util/aio-posix.h | 12 ++----------
util/aio-posix.c | 28 +++++++++++++++++++++++++---
util/fdmon-io_uring.c | 5 +++--
3 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/util/aio-posix.h b/util/aio-posix.h
index f9994ed79e..dfa1a51c0b 100644
--- a/util/aio-posix.h
+++ b/util/aio-posix.h
@@ -18,6 +18,7 @@
#define AIO_POSIX_H
#include "block/aio.h"
+#include "qapi/error.h"
struct AioHandler {
GPollFD pfd;
@@ -72,17 +73,8 @@ static inline void fdmon_epoll_disable(AioContext *ctx)
#endif /* !CONFIG_EPOLL_CREATE1 */
#ifdef CONFIG_LINUX_IO_URING
-bool fdmon_io_uring_setup(AioContext *ctx);
+bool fdmon_io_uring_setup(AioContext *ctx, Error **errp);
void fdmon_io_uring_destroy(AioContext *ctx);
-#else
-static inline bool fdmon_io_uring_setup(AioContext *ctx)
-{
- return false;
-}
-
-static inline void fdmon_io_uring_destroy(AioContext *ctx)
-{
-}
#endif /* !CONFIG_LINUX_IO_URING */
#endif /* AIO_POSIX_H */
diff --git a/util/aio-posix.c b/util/aio-posix.c
index 9806a75c12..c0285a26a3 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -16,6 +16,7 @@
#include "qemu/osdep.h"
#include "block/block.h"
#include "block/thread-pool.h"
+#include "qapi/error.h"
#include "qemu/main-loop.h"
#include "qemu/lockcnt.h"
#include "qemu/rcu.h"
@@ -724,10 +725,29 @@ bool aio_context_setup(AioContext *ctx, Error **errp)
ctx->epollfd = -1;
ctx->epollfd_tag = NULL;
- /* Use the fastest fd monitoring implementation if available */
- if (fdmon_io_uring_setup(ctx)) {
- return true;
+#ifdef CONFIG_LINUX_IO_URING
+ {
+ static bool need_io_uring;
+ Error *local_err = NULL; /* ERRP_GUARD() doesn't handle error_abort */
+
+ /* io_uring takes precedence because it provides aio_add_sqe() support */
+ if (fdmon_io_uring_setup(ctx, &local_err)) {
+ /*
+ * If one AioContext gets io_uring, then all AioContexts need io_uring
+ * so that aio_add_sqe() support is available across all threads.
+ */
+ need_io_uring = true;
+ return true;
+ }
+ if (need_io_uring) {
+ error_propagate(errp, local_err);
+ return false;
+ }
+
+ /* Silently fall back on systems where io_uring is unavailable */
+ error_free(local_err);
}
+#endif /* CONFIG_LINUX_IO_URING */
fdmon_epoll_setup(ctx);
return true;
@@ -735,7 +755,9 @@ bool aio_context_setup(AioContext *ctx, Error **errp)
void aio_context_destroy(AioContext *ctx)
{
+#ifdef CONFIG_LINUX_IO_URING
fdmon_io_uring_destroy(ctx);
+#endif
qemu_lockcnt_lock(&ctx->list_lock);
fdmon_epoll_disable(ctx);
diff --git a/util/fdmon-io_uring.c b/util/fdmon-io_uring.c
index 4b6dc8903f..730c1ee205 100644
--- a/util/fdmon-io_uring.c
+++ b/util/fdmon-io_uring.c
@@ -45,6 +45,7 @@
#include "qemu/osdep.h"
#include <poll.h>
+#include "qapi/error.h"
#include "qemu/rcu_queue.h"
#include "aio-posix.h"
@@ -369,7 +370,7 @@ static const FDMonOps fdmon_io_uring_ops = {
.gsource_dispatch = fdmon_io_uring_gsource_dispatch,
};
-bool fdmon_io_uring_setup(AioContext *ctx)
+bool fdmon_io_uring_setup(AioContext *ctx, Error **errp)
{
int ret;
@@ -377,6 +378,7 @@ bool fdmon_io_uring_setup(AioContext *ctx)
ret = io_uring_queue_init(FDMON_IO_URING_ENTRIES, &ctx->fdmon_io_uring, 0);
if (ret != 0) {
+ error_setg_errno(errp, -ret, "Failed to initialize io_uring");
return false;
}
@@ -384,7 +386,6 @@ bool fdmon_io_uring_setup(AioContext *ctx)
ctx->fdmon_ops = &fdmon_io_uring_ops;
ctx->io_uring_fd_tag = g_source_add_unix_fd(&ctx->source,
ctx->fdmon_io_uring.ring_fd, G_IO_IN);
-
return true;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 09/13] aio-posix: unindent fdmon_io_uring_destroy()
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (7 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 08/13] aio-posix: gracefully handle io_uring_queue_init() failure Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 10/13] aio-posix: add fdmon_ops->dispatch() Stefan Hajnoczi
` (4 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
Reduce the level of indentation to make further code changes easier to
read.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v5:
- Add patch to unindent fdmon_io_uring_destroy() [Kevin]
---
util/fdmon-io_uring.c | 46 ++++++++++++++++++++++---------------------
1 file changed, 24 insertions(+), 22 deletions(-)
diff --git a/util/fdmon-io_uring.c b/util/fdmon-io_uring.c
index 730c1ee205..663e95d777 100644
--- a/util/fdmon-io_uring.c
+++ b/util/fdmon-io_uring.c
@@ -391,33 +391,35 @@ bool fdmon_io_uring_setup(AioContext *ctx, Error **errp)
void fdmon_io_uring_destroy(AioContext *ctx)
{
- if (ctx->fdmon_ops == &fdmon_io_uring_ops) {
- AioHandler *node;
+ AioHandler *node;
- io_uring_queue_exit(&ctx->fdmon_io_uring);
+ if (ctx->fdmon_ops != &fdmon_io_uring_ops) {
+ return;
+ }
- /* Move handlers due to be removed onto the deleted list */
- while ((node = QSLIST_FIRST_RCU(&ctx->submit_list))) {
- unsigned flags = qatomic_fetch_and(&node->flags,
- ~(FDMON_IO_URING_PENDING |
- FDMON_IO_URING_ADD |
- FDMON_IO_URING_REMOVE |
- FDMON_IO_URING_DELETE_AIO_HANDLER));
+ io_uring_queue_exit(&ctx->fdmon_io_uring);
- if ((flags & FDMON_IO_URING_REMOVE) ||
- (flags & FDMON_IO_URING_DELETE_AIO_HANDLER)) {
- QLIST_INSERT_HEAD_RCU(&ctx->deleted_aio_handlers,
- node, node_deleted);
- }
+ /* Move handlers due to be removed onto the deleted list */
+ while ((node = QSLIST_FIRST_RCU(&ctx->submit_list))) {
+ unsigned flags = qatomic_fetch_and(&node->flags,
+ ~(FDMON_IO_URING_PENDING |
+ FDMON_IO_URING_ADD |
+ FDMON_IO_URING_REMOVE |
+ FDMON_IO_URING_DELETE_AIO_HANDLER));
- QSLIST_REMOVE_HEAD_RCU(&ctx->submit_list, node_submitted);
+ if ((flags & FDMON_IO_URING_REMOVE) ||
+ (flags & FDMON_IO_URING_DELETE_AIO_HANDLER)) {
+ QLIST_INSERT_HEAD_RCU(&ctx->deleted_aio_handlers,
+ node, node_deleted);
}
- g_source_remove_unix_fd(&ctx->source, ctx->io_uring_fd_tag);
- ctx->io_uring_fd_tag = NULL;
-
- qemu_lockcnt_lock(&ctx->list_lock);
- fdmon_poll_downgrade(ctx);
- qemu_lockcnt_unlock(&ctx->list_lock);
+ QSLIST_REMOVE_HEAD_RCU(&ctx->submit_list, node_submitted);
}
+
+ g_source_remove_unix_fd(&ctx->source, ctx->io_uring_fd_tag);
+ ctx->io_uring_fd_tag = NULL;
+
+ qemu_lockcnt_lock(&ctx->list_lock);
+ fdmon_poll_downgrade(ctx);
+ qemu_lockcnt_unlock(&ctx->list_lock);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 10/13] aio-posix: add fdmon_ops->dispatch()
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (8 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 09/13] aio-posix: unindent fdmon_io_uring_destroy() Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 11/13] aio-posix: add aio_add_sqe() API for user-defined io_uring requests Stefan Hajnoczi
` (3 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
The ppoll and epoll file descriptor monitoring implementations rely on
the event loop's generic file descriptor, timer, and BH dispatch code to
invoke user callbacks.
The io_uring file descriptor monitoring implementation will need
io_uring-specific dispatch logic for CQE handlers for custom SQEs.
Introduce a new FDMonOps ->dispatch() callback that allows file
descriptor monitoring implementations to invoke user callbacks. The next
patch will use this new callback.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v5:
- Add patch to introduce FDMonOps->dispatch() callback [Kevin]
---
include/block/aio.h | 19 +++++++++++++++++++
util/aio-posix.c | 9 +++++++++
2 files changed, 28 insertions(+)
diff --git a/include/block/aio.h b/include/block/aio.h
index 9562733fa7..b266daa58f 100644
--- a/include/block/aio.h
+++ b/include/block/aio.h
@@ -107,6 +107,25 @@ typedef struct {
*/
bool (*need_wait)(AioContext *ctx);
+ /*
+ * dispatch:
+ * @ctx: the AioContext
+ *
+ * Dispatch any work that is specific to this file descriptor monitoring
+ * implementation. Usually the event loop's generic file descriptor
+ * monitoring, BH, and timer dispatching code is sufficient, but file
+ * descriptor monitoring implementations offering additional functionality
+ * may need to implement this function for custom behavior. Called at a
+ * point in the event loop when it is safe to invoke user-defined
+ * callbacks.
+ *
+ * This function is optional and may be NULL.
+ *
+ * Returns: true if progress was made (see aio_poll()'s return value),
+ * false otherwise.
+ */
+ bool (*dispatch)(AioContext *ctx);
+
/*
* gsource_prepare:
* @ctx: the AioContext
diff --git a/util/aio-posix.c b/util/aio-posix.c
index c0285a26a3..6ff36b6e51 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -385,10 +385,15 @@ void aio_dispatch(AioContext *ctx)
AioHandlerList ready_list = QLIST_HEAD_INITIALIZER(ready_list);
qemu_lockcnt_inc(&ctx->list_lock);
+
aio_bh_poll(ctx);
ctx->fdmon_ops->gsource_dispatch(ctx, &ready_list);
+ if (ctx->fdmon_ops->dispatch) {
+ ctx->fdmon_ops->dispatch(ctx);
+ }
+
/* block_ns is 0 because polling is disabled in the glib event loop */
aio_dispatch_ready_handlers(ctx, &ready_list, 0);
@@ -707,6 +712,10 @@ bool aio_poll(AioContext *ctx, bool blocking)
block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start;
}
+ if (ctx->fdmon_ops->dispatch) {
+ progress |= ctx->fdmon_ops->dispatch(ctx);
+ }
+
progress |= aio_bh_poll(ctx);
progress |= aio_dispatch_ready_handlers(ctx, &ready_list, block_ns);
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 11/13] aio-posix: add aio_add_sqe() API for user-defined io_uring requests
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (9 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 10/13] aio-posix: add fdmon_ops->dispatch() Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 12/13] block/io_uring: use aio_add_sqe() Stefan Hajnoczi
` (2 subsequent siblings)
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
Introduce the aio_add_sqe() API for submitting io_uring requests in the
current AioContext. This allows other components in QEMU, like the block
layer, to take advantage of io_uring features without creating their own
io_uring context.
This API supports nested event loops just like file descriptor
monitoring and BHs do. This comes at a complexity cost: CQE callbacks
must be placed on a list so that nested event loops can invoke pending
CQE callbacks from parent event loops. If you're wondering why
CqeHandler exists instead of just a callback function pointer, this is
why.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
v5:
- Replace cqe_handler_bh with FDMonOps->dispatch() [Kevin]
- Rename AioHandler->cqe_handler field to ->internal_cqe_handler [Kevin]
- Consolidate fdmon-io_uring.c trace-events changes into this commit
v2:
- Fix pre_sqe -> prep_sqe typo [Eric]
- Add #endif terminator comment [Eric]
---
include/block/aio.h | 83 +++++++++++++++++++++++++++++++-
util/aio-posix.h | 1 +
util/aio-posix.c | 9 ++++
util/fdmon-io_uring.c | 109 ++++++++++++++++++++++++++++++++++++------
util/trace-events | 4 ++
5 files changed, 190 insertions(+), 16 deletions(-)
diff --git a/include/block/aio.h b/include/block/aio.h
index b266daa58f..05d1bf4036 100644
--- a/include/block/aio.h
+++ b/include/block/aio.h
@@ -61,6 +61,27 @@ typedef struct LuringState LuringState;
/* Is polling disabled? */
bool aio_poll_disabled(AioContext *ctx);
+#ifdef CONFIG_LINUX_IO_URING
+/*
+ * Each io_uring request must have a unique CqeHandler that processes the cqe.
+ * The lifetime of a CqeHandler must be at least from aio_add_sqe() until
+ * ->cb() invocation.
+ */
+typedef struct CqeHandler CqeHandler;
+struct CqeHandler {
+ /* Called by the AioContext when the request has completed */
+ void (*cb)(CqeHandler *handler);
+
+ /* Used internally, do not access this */
+ QSIMPLEQ_ENTRY(CqeHandler) next;
+
+ /* This field is filled in before ->cb() is called */
+ struct io_uring_cqe cqe;
+};
+
+typedef QSIMPLEQ_HEAD(, CqeHandler) CqeHandlerSimpleQ;
+#endif /* CONFIG_LINUX_IO_URING */
+
/* Callbacks for file descriptor monitoring implementations */
typedef struct {
/*
@@ -157,6 +178,27 @@ typedef struct {
* Called with list_lock incremented.
*/
void (*gsource_dispatch)(AioContext *ctx, AioHandlerList *ready_list);
+
+#ifdef CONFIG_LINUX_IO_URING
+ /**
+ * add_sqe: Add an io_uring sqe for submission.
+ * @prep_sqe: invoked with an sqe that should be prepared for submission
+ * @opaque: user-defined argument to @prep_sqe()
+ * @cqe_handler: the unique cqe handler associated with this request
+ *
+ * The caller's @prep_sqe() function is invoked to fill in the details of
+ * the sqe. Do not call io_uring_sqe_set_data() on this sqe.
+ *
+ * The kernel may see the sqe as soon as @prep_sqe() returns or it may take
+ * until the next event loop iteration.
+ *
+ * This function is called from the current AioContext and is not
+ * thread-safe.
+ */
+ void (*add_sqe)(AioContext *ctx,
+ void (*prep_sqe)(struct io_uring_sqe *sqe, void *opaque),
+ void *opaque, CqeHandler *cqe_handler);
+#endif /* CONFIG_LINUX_IO_URING */
} FDMonOps;
/*
@@ -274,7 +316,10 @@ struct AioContext {
struct io_uring fdmon_io_uring;
AioHandlerSList submit_list;
gpointer io_uring_fd_tag;
-#endif
+
+ /* Pending callback state for cqe handlers */
+ CqeHandlerSimpleQ cqe_handler_ready_list;
+#endif /* CONFIG_LINUX_IO_URING */
/* TimerLists for calling timers - one per clock type. Has its own
* locking.
@@ -782,4 +827,40 @@ void aio_context_set_aio_params(AioContext *ctx, int64_t max_batch);
*/
void aio_context_set_thread_pool_params(AioContext *ctx, int64_t min,
int64_t max, Error **errp);
+
+#ifdef CONFIG_LINUX_IO_URING
+/**
+ * aio_has_io_uring: Return whether io_uring is available.
+ *
+ * io_uring is either available in all AioContexts or in none, so this only
+ * needs to be called once from within any thread's AioContext.
+ */
+static inline bool aio_has_io_uring(void)
+{
+ AioContext *ctx = qemu_get_current_aio_context();
+ return ctx->fdmon_ops->add_sqe;
+}
+
+/**
+ * aio_add_sqe: Add an io_uring sqe for submission.
+ * @prep_sqe: invoked with an sqe that should be prepared for submission
+ * @opaque: user-defined argument to @prep_sqe()
+ * @cqe_handler: the unique cqe handler associated with this request
+ *
+ * The caller's @prep_sqe() function is invoked to fill in the details of the
+ * sqe. Do not call io_uring_sqe_set_data() on this sqe.
+ *
+ * The sqe is submitted by the current AioContext. The kernel may see the sqe
+ * as soon as @prep_sqe() returns or it may take until the next event loop
+ * iteration.
+ *
+ * When the AioContext is destroyed, pending sqes are ignored and their
+ * CqeHandlers are not invoked.
+ *
+ * This function must be called only when aio_has_io_uring() returns true.
+ */
+void aio_add_sqe(void (*prep_sqe)(struct io_uring_sqe *sqe, void *opaque),
+ void *opaque, CqeHandler *cqe_handler);
+#endif /* CONFIG_LINUX_IO_URING */
+
#endif
diff --git a/util/aio-posix.h b/util/aio-posix.h
index dfa1a51c0b..babbfa8314 100644
--- a/util/aio-posix.h
+++ b/util/aio-posix.h
@@ -36,6 +36,7 @@ struct AioHandler {
#ifdef CONFIG_LINUX_IO_URING
QSLIST_ENTRY(AioHandler) node_submitted;
unsigned flags; /* see fdmon-io_uring.c */
+ CqeHandler internal_cqe_handler; /* used for POLL_ADD/POLL_REMOVE */
#endif
int64_t poll_idle_timeout; /* when to stop userspace polling */
bool poll_ready; /* has polling detected an event? */
diff --git a/util/aio-posix.c b/util/aio-posix.c
index 6ff36b6e51..e24b955fd9 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -806,3 +806,12 @@ void aio_context_set_aio_params(AioContext *ctx, int64_t max_batch)
aio_notify(ctx);
}
+
+#ifdef CONFIG_LINUX_IO_URING
+void aio_add_sqe(void (*prep_sqe)(struct io_uring_sqe *sqe, void *opaque),
+ void *opaque, CqeHandler *cqe_handler)
+{
+ AioContext *ctx = qemu_get_current_aio_context();
+ ctx->fdmon_ops->add_sqe(ctx, prep_sqe, opaque, cqe_handler);
+}
+#endif /* CONFIG_LINUX_IO_URING */
diff --git a/util/fdmon-io_uring.c b/util/fdmon-io_uring.c
index 663e95d777..6aae27e31e 100644
--- a/util/fdmon-io_uring.c
+++ b/util/fdmon-io_uring.c
@@ -46,8 +46,10 @@
#include "qemu/osdep.h"
#include <poll.h>
#include "qapi/error.h"
+#include "qemu/defer-call.h"
#include "qemu/rcu_queue.h"
#include "aio-posix.h"
+#include "trace.h"
enum {
FDMON_IO_URING_ENTRIES = 128, /* sq/cq ring size */
@@ -76,8 +78,8 @@ static inline int pfd_events_from_poll(int poll_events)
}
/*
- * Returns an sqe for submitting a request. Only be called within
- * fdmon_io_uring_wait().
+ * Returns an sqe for submitting a request. Only called from the AioContext
+ * thread.
*/
static struct io_uring_sqe *get_sqe(AioContext *ctx)
{
@@ -168,23 +170,46 @@ static void fdmon_io_uring_update(AioContext *ctx,
}
}
+static void fdmon_io_uring_add_sqe(AioContext *ctx,
+ void (*prep_sqe)(struct io_uring_sqe *sqe, void *opaque),
+ void *opaque, CqeHandler *cqe_handler)
+{
+ struct io_uring_sqe *sqe = get_sqe(ctx);
+
+ prep_sqe(sqe, opaque);
+ io_uring_sqe_set_data(sqe, cqe_handler);
+
+ trace_fdmon_io_uring_add_sqe(ctx, opaque, sqe->opcode, sqe->fd, sqe->off,
+ cqe_handler);
+}
+
+static void fdmon_special_cqe_handler(CqeHandler *cqe_handler)
+{
+ /*
+ * This is an empty function that is never called. It is used as a function
+ * pointer to distinguish it from ordinary cqe handlers.
+ */
+}
+
static void add_poll_add_sqe(AioContext *ctx, AioHandler *node)
{
struct io_uring_sqe *sqe = get_sqe(ctx);
int events = poll_events_from_pfd(node->pfd.events);
io_uring_prep_poll_add(sqe, node->pfd.fd, events);
- io_uring_sqe_set_data(sqe, node);
+ node->internal_cqe_handler.cb = fdmon_special_cqe_handler;
+ io_uring_sqe_set_data(sqe, &node->internal_cqe_handler);
}
static void add_poll_remove_sqe(AioContext *ctx, AioHandler *node)
{
struct io_uring_sqe *sqe = get_sqe(ctx);
+ CqeHandler *cqe_handler = &node->internal_cqe_handler;
#ifdef LIBURING_HAVE_DATA64
- io_uring_prep_poll_remove(sqe, (uintptr_t)node);
+ io_uring_prep_poll_remove(sqe, (uintptr_t)cqe_handler);
#else
- io_uring_prep_poll_remove(sqe, node);
+ io_uring_prep_poll_remove(sqe, cqe_handler);
#endif
io_uring_sqe_set_data(sqe, NULL);
}
@@ -233,19 +258,13 @@ static void fill_sq_ring(AioContext *ctx)
}
}
-/* Returns true if a handler became ready */
-static bool process_cqe(AioContext *ctx,
- AioHandlerList *ready_list,
- struct io_uring_cqe *cqe)
+static bool process_cqe_aio_handler(AioContext *ctx,
+ AioHandlerList *ready_list,
+ AioHandler *node,
+ struct io_uring_cqe *cqe)
{
- AioHandler *node = io_uring_cqe_get_data(cqe);
unsigned flags;
- /* poll_timeout and poll_remove have a zero user_data field */
- if (!node) {
- return false;
- }
-
/*
* Deletion can only happen when IORING_OP_POLL_ADD completes. If we race
* with enqueue() here then we can safely clear the FDMON_IO_URING_REMOVE
@@ -269,6 +288,35 @@ static bool process_cqe(AioContext *ctx,
return true;
}
+/* Returns true if a handler became ready */
+static bool process_cqe(AioContext *ctx,
+ AioHandlerList *ready_list,
+ struct io_uring_cqe *cqe)
+{
+ CqeHandler *cqe_handler = io_uring_cqe_get_data(cqe);
+
+ /* poll_timeout and poll_remove have a zero user_data field */
+ if (!cqe_handler) {
+ return false;
+ }
+
+ /*
+ * Special handling for AioHandler cqes. They need ready_list and have a
+ * return value.
+ */
+ if (cqe_handler->cb == fdmon_special_cqe_handler) {
+ AioHandler *node = container_of(cqe_handler, AioHandler,
+ internal_cqe_handler);
+ return process_cqe_aio_handler(ctx, ready_list, node, cqe);
+ }
+
+ cqe_handler->cqe = *cqe;
+
+ /* Handlers are invoked later by fdmon_io_uring_dispatch() */
+ QSIMPLEQ_INSERT_TAIL(&ctx->cqe_handler_ready_list, cqe_handler, next);
+ return false;
+}
+
static int process_cq_ring(AioContext *ctx, AioHandlerList *ready_list)
{
struct io_uring *ring = &ctx->fdmon_io_uring;
@@ -311,6 +359,32 @@ static bool fdmon_io_uring_gsource_check(AioContext *ctx)
return g_source_query_unix_fd(&ctx->source, tag) & G_IO_IN;
}
+/* Dispatch CQE handlers that are ready */
+static bool fdmon_io_uring_dispatch(AioContext *ctx)
+{
+ CqeHandlerSimpleQ *ready_list = &ctx->cqe_handler_ready_list;
+ bool progress = false;
+
+ /* Handlers may use defer_call() to coalesce frequent operations */
+ defer_call_begin();
+
+ while (!QSIMPLEQ_EMPTY(ready_list)) {
+ CqeHandler *cqe_handler = QSIMPLEQ_FIRST(ready_list);
+
+ QSIMPLEQ_REMOVE_HEAD(ready_list, next);
+
+ trace_fdmon_io_uring_cqe_handler(ctx, cqe_handler,
+ cqe_handler->cqe.res);
+ cqe_handler->cb(cqe_handler);
+ progress = true;
+ }
+
+ defer_call_end();
+
+ return progress;
+}
+
+
/* This is where CQEs are processed in the glib event loop */
static void fdmon_io_uring_gsource_dispatch(AioContext *ctx,
AioHandlerList *ready_list)
@@ -365,9 +439,11 @@ static const FDMonOps fdmon_io_uring_ops = {
.update = fdmon_io_uring_update,
.wait = fdmon_io_uring_wait,
.need_wait = fdmon_io_uring_need_wait,
+ .dispatch = fdmon_io_uring_dispatch,
.gsource_prepare = fdmon_io_uring_gsource_prepare,
.gsource_check = fdmon_io_uring_gsource_check,
.gsource_dispatch = fdmon_io_uring_gsource_dispatch,
+ .add_sqe = fdmon_io_uring_add_sqe,
};
bool fdmon_io_uring_setup(AioContext *ctx, Error **errp)
@@ -383,6 +459,7 @@ bool fdmon_io_uring_setup(AioContext *ctx, Error **errp)
}
QSLIST_INIT(&ctx->submit_list);
+ QSIMPLEQ_INIT(&ctx->cqe_handler_ready_list);
ctx->fdmon_ops = &fdmon_io_uring_ops;
ctx->io_uring_fd_tag = g_source_add_unix_fd(&ctx->source,
ctx->fdmon_io_uring.ring_fd, G_IO_IN);
@@ -419,6 +496,8 @@ void fdmon_io_uring_destroy(AioContext *ctx)
g_source_remove_unix_fd(&ctx->source, ctx->io_uring_fd_tag);
ctx->io_uring_fd_tag = NULL;
+ assert(QSIMPLEQ_EMPTY(&ctx->cqe_handler_ready_list));
+
qemu_lockcnt_lock(&ctx->list_lock);
fdmon_poll_downgrade(ctx);
qemu_lockcnt_unlock(&ctx->list_lock);
diff --git a/util/trace-events b/util/trace-events
index bd8f25fb59..540d662507 100644
--- a/util/trace-events
+++ b/util/trace-events
@@ -24,6 +24,10 @@ buffer_move_empty(const char *buf, size_t len, const char *from) "%s: %zd bytes
buffer_move(const char *buf, size_t len, const char *from) "%s: %zd bytes from %s"
buffer_free(const char *buf, size_t len) "%s: capacity %zd"
+# fdmon-io_uring.c
+fdmon_io_uring_add_sqe(void *ctx, void *opaque, int opcode, int fd, uint64_t off, void *cqe_handler) "ctx %p opaque %p opcode %d fd %d off %"PRId64" cqe_handler %p"
+fdmon_io_uring_cqe_handler(void *ctx, void *cqe_handler, int cqe_res) "ctx %p cqe_handler %p cqe_res %d"
+
# filemonitor-inotify.c
qemu_file_monitor_add_watch(void *mon, const char *dirpath, const char *filename, void *cb, void *opaque, int64_t id) "File monitor %p add watch dir='%s' file='%s' cb=%p opaque=%p id=%" PRId64
qemu_file_monitor_remove_watch(void *mon, const char *dirpath, int64_t id) "File monitor %p remove watch dir='%s' id=%" PRId64
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 12/13] block/io_uring: use aio_add_sqe()
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (10 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 11/13] aio-posix: add aio_add_sqe() API for user-defined io_uring requests Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 13/13] block/io_uring: use non-vectored read/write when possible Stefan Hajnoczi
2025-10-30 18:11 ` [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Kevin Wolf
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
AioContext has its own io_uring instance for file descriptor monitoring.
The disk I/O io_uring code was developed separately. Originally I
thought the characteristics of file descriptor monitoring and disk I/O
were too different, requiring separate io_uring instances.
Now it has become clear to me that it's feasible to share a single
io_uring instance for file descriptor monitoring and disk I/O. We're not
using io_uring's IOPOLL feature or anything else that would require a
separate instance.
Unify block/io_uring.c and util/fdmon-io_uring.c using the new
aio_add_sqe() API that allows user-defined io_uring sqe submission. Now
block/io_uring.c just needs to submit readv/writev/fsync and most of the
io_uring-specific logic is handled by fdmon-io_uring.c.
There are two immediate advantages:
1. Fewer system calls. There is no need to monitor the disk I/O io_uring
ring fd from the file descriptor monitoring io_uring instance. Disk
I/O completions are now picked up directly. Also, sqes are
accumulated in the sq ring until the end of the event loop iteration
and there are fewer io_uring_enter(2) syscalls.
2. Less code duplication.
Note that error_setg() messages are not supposed to end with
punctuation, so I removed a '.' for the non-io_uring build error
message.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
include/block/aio.h | 7 -
include/block/raw-aio.h | 5 -
block/file-posix.c | 40 ++--
block/io_uring.c | 489 ++++++++++------------------------------
stubs/io_uring.c | 32 ---
util/async.c | 35 ---
block/trace-events | 12 +-
stubs/meson.build | 3 -
8 files changed, 130 insertions(+), 493 deletions(-)
delete mode 100644 stubs/io_uring.c
diff --git a/include/block/aio.h b/include/block/aio.h
index 05d1bf4036..540bbc5d60 100644
--- a/include/block/aio.h
+++ b/include/block/aio.h
@@ -310,8 +310,6 @@ struct AioContext {
struct LinuxAioState *linux_aio;
#endif
#ifdef CONFIG_LINUX_IO_URING
- LuringState *linux_io_uring;
-
/* State for file descriptor monitoring using Linux io_uring */
struct io_uring fdmon_io_uring;
AioHandlerSList submit_list;
@@ -615,11 +613,6 @@ struct LinuxAioState *aio_setup_linux_aio(AioContext *ctx, Error **errp);
/* Return the LinuxAioState bound to this AioContext */
struct LinuxAioState *aio_get_linux_aio(AioContext *ctx);
-/* Setup the LuringState bound to this AioContext */
-LuringState *aio_setup_linux_io_uring(AioContext *ctx, Error **errp);
-
-/* Return the LuringState bound to this AioContext */
-LuringState *aio_get_linux_io_uring(AioContext *ctx);
/**
* aio_timer_new_with_attrs:
* @ctx: the aio context
diff --git a/include/block/raw-aio.h b/include/block/raw-aio.h
index 6570244496..30e5fc9a9f 100644
--- a/include/block/raw-aio.h
+++ b/include/block/raw-aio.h
@@ -74,15 +74,10 @@ static inline bool laio_has_fua(void)
#endif
/* io_uring.c - Linux io_uring implementation */
#ifdef CONFIG_LINUX_IO_URING
-LuringState *luring_init(Error **errp);
-void luring_cleanup(LuringState *s);
-
/* luring_co_submit: submit I/O requests in the thread's current AioContext. */
int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset,
QEMUIOVector *qiov, int type,
BdrvRequestFlags flags);
-void luring_detach_aio_context(LuringState *s, AioContext *old_context);
-void luring_attach_aio_context(LuringState *s, AioContext *new_context);
bool luring_has_fua(void);
#else
static inline bool luring_has_fua(void)
diff --git a/block/file-posix.c b/block/file-posix.c
index 8c738674ce..8b7c02d19a 100644
--- a/block/file-posix.c
+++ b/block/file-posix.c
@@ -755,14 +755,23 @@ static int raw_open_common(BlockDriverState *bs, QDict *options,
}
#endif /* !defined(CONFIG_LINUX_AIO) */
-#ifndef CONFIG_LINUX_IO_URING
if (s->use_linux_io_uring) {
+#ifdef CONFIG_LINUX_IO_URING
+ if (!aio_has_io_uring()) {
+ error_setg(errp, "aio=io_uring was specified, but is not "
+ "available (disabled via io_uring_disabled "
+ "sysctl or blocked by container runtime "
+ "seccomp policy?)");
+ ret = -EINVAL;
+ goto fail;
+ }
+#else
error_setg(errp, "aio=io_uring was specified, but is not supported "
- "in this build.");
+ "in this build");
ret = -EINVAL;
goto fail;
- }
#endif /* !defined(CONFIG_LINUX_IO_URING) */
+ }
s->has_discard = true;
s->has_write_zeroes = true;
@@ -2522,27 +2531,6 @@ static bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov)
return true;
}
-#ifdef CONFIG_LINUX_IO_URING
-static inline bool raw_check_linux_io_uring(BDRVRawState *s)
-{
- Error *local_err = NULL;
- AioContext *ctx;
-
- if (!s->use_linux_io_uring) {
- return false;
- }
-
- ctx = qemu_get_current_aio_context();
- if (unlikely(!aio_setup_linux_io_uring(ctx, &local_err))) {
- error_reportf_err(local_err, "Unable to use linux io_uring, "
- "falling back to thread pool: ");
- s->use_linux_io_uring = false;
- return false;
- }
- return true;
-}
-#endif
-
#ifdef CONFIG_LINUX_AIO
static inline bool raw_check_linux_aio(BDRVRawState *s)
{
@@ -2595,7 +2583,7 @@ raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr, uint64_t bytes,
if (s->needs_alignment && !bdrv_qiov_is_aligned(bs, qiov)) {
type |= QEMU_AIO_MISALIGNED;
#ifdef CONFIG_LINUX_IO_URING
- } else if (raw_check_linux_io_uring(s)) {
+ } else if (s->use_linux_io_uring) {
assert(qiov->size == bytes);
ret = luring_co_submit(bs, s->fd, offset, qiov, type, flags);
goto out;
@@ -2692,7 +2680,7 @@ static int coroutine_fn raw_co_flush_to_disk(BlockDriverState *bs)
};
#ifdef CONFIG_LINUX_IO_URING
- if (raw_check_linux_io_uring(s)) {
+ if (s->use_linux_io_uring) {
return luring_co_submit(bs, s->fd, 0, NULL, QEMU_AIO_FLUSH, 0);
}
#endif
diff --git a/block/io_uring.c b/block/io_uring.c
index dd4f304910..dd930ee57e 100644
--- a/block/io_uring.c
+++ b/block/io_uring.c
@@ -11,28 +11,20 @@
#include "qemu/osdep.h"
#include <liburing.h>
#include "block/aio.h"
-#include "qemu/queue.h"
#include "block/block.h"
#include "block/raw-aio.h"
#include "qemu/coroutine.h"
-#include "qemu/defer-call.h"
-#include "qapi/error.h"
#include "system/block-backend.h"
#include "trace.h"
-/* Only used for assertions. */
-#include "qemu/coroutine_int.h"
-
-/* io_uring ring size */
-#define MAX_ENTRIES 128
-
-typedef struct LuringAIOCB {
+typedef struct {
Coroutine *co;
- struct io_uring_sqe sqeq;
- ssize_t ret;
QEMUIOVector *qiov;
- bool is_read;
- QSIMPLEQ_ENTRY(LuringAIOCB) next;
+ uint64_t offset;
+ ssize_t ret;
+ int type;
+ int fd;
+ BdrvRequestFlags flags;
/*
* Buffered reads may require resubmission, see
@@ -40,36 +32,51 @@ typedef struct LuringAIOCB {
*/
int total_read;
QEMUIOVector resubmit_qiov;
-} LuringAIOCB;
-typedef struct LuringQueue {
- unsigned int in_queue;
- unsigned int in_flight;
- bool blocked;
- QSIMPLEQ_HEAD(, LuringAIOCB) submit_queue;
-} LuringQueue;
+ CqeHandler cqe_handler;
+} LuringRequest;
-struct LuringState {
- AioContext *aio_context;
-
- struct io_uring ring;
-
- /* No locking required, only accessed from AioContext home thread */
- LuringQueue io_q;
-
- QEMUBH *completion_bh;
-};
-
-/**
- * luring_resubmit:
- *
- * Resubmit a request by appending it to submit_queue. The caller must ensure
- * that ioq_submit() is called later so that submit_queue requests are started.
- */
-static void luring_resubmit(LuringState *s, LuringAIOCB *luringcb)
+static void luring_prep_sqe(struct io_uring_sqe *sqe, void *opaque)
{
- QSIMPLEQ_INSERT_TAIL(&s->io_q.submit_queue, luringcb, next);
- s->io_q.in_queue++;
+ LuringRequest *req = opaque;
+ QEMUIOVector *qiov = req->qiov;
+ uint64_t offset = req->offset;
+ int fd = req->fd;
+ BdrvRequestFlags flags = req->flags;
+
+ switch (req->type) {
+ case QEMU_AIO_WRITE:
+#ifdef HAVE_IO_URING_PREP_WRITEV2
+ {
+ int luring_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0;
+ io_uring_prep_writev2(sqe, fd, qiov->iov,
+ qiov->niov, offset, luring_flags);
+ }
+#else
+ assert(flags == 0);
+ io_uring_prep_writev(sqe, fd, qiov->iov, qiov->niov, offset);
+#endif
+ break;
+ case QEMU_AIO_ZONE_APPEND:
+ io_uring_prep_writev(sqe, fd, qiov->iov, qiov->niov, offset);
+ break;
+ case QEMU_AIO_READ:
+ {
+ if (req->resubmit_qiov.iov != NULL) {
+ qiov = &req->resubmit_qiov;
+ }
+ io_uring_prep_readv(sqe, fd, qiov->iov, qiov->niov,
+ offset + req->total_read);
+ break;
+ }
+ case QEMU_AIO_FLUSH:
+ io_uring_prep_fsync(sqe, fd, IORING_FSYNC_DATASYNC);
+ break;
+ default:
+ fprintf(stderr, "%s: invalid AIO request type, aborting 0x%x.\n",
+ __func__, req->type);
+ abort();
+ }
}
/**
@@ -78,385 +85,115 @@ static void luring_resubmit(LuringState *s, LuringAIOCB *luringcb)
* Short reads are rare but may occur. The remaining read request needs to be
* resubmitted.
*/
-static void luring_resubmit_short_read(LuringState *s, LuringAIOCB *luringcb,
- int nread)
+static void luring_resubmit_short_read(LuringRequest *req, int nread)
{
QEMUIOVector *resubmit_qiov;
size_t remaining;
- trace_luring_resubmit_short_read(s, luringcb, nread);
+ trace_luring_resubmit_short_read(req, nread);
/* Update read position */
- luringcb->total_read += nread;
- remaining = luringcb->qiov->size - luringcb->total_read;
+ req->total_read += nread;
+ remaining = req->qiov->size - req->total_read;
/* Shorten qiov */
- resubmit_qiov = &luringcb->resubmit_qiov;
+ resubmit_qiov = &req->resubmit_qiov;
if (resubmit_qiov->iov == NULL) {
- qemu_iovec_init(resubmit_qiov, luringcb->qiov->niov);
+ qemu_iovec_init(resubmit_qiov, req->qiov->niov);
} else {
qemu_iovec_reset(resubmit_qiov);
}
- qemu_iovec_concat(resubmit_qiov, luringcb->qiov, luringcb->total_read,
- remaining);
+ qemu_iovec_concat(resubmit_qiov, req->qiov, req->total_read, remaining);
- /* Update sqe */
- luringcb->sqeq.off += nread;
- luringcb->sqeq.addr = (uintptr_t)luringcb->resubmit_qiov.iov;
- luringcb->sqeq.len = luringcb->resubmit_qiov.niov;
-
- luring_resubmit(s, luringcb);
+ aio_add_sqe(luring_prep_sqe, req, &req->cqe_handler);
}
-/**
- * luring_process_completions:
- * @s: AIO state
- *
- * Fetches completed I/O requests, consumes cqes and invokes their callbacks
- * The function is somewhat tricky because it supports nested event loops, for
- * example when a request callback invokes aio_poll().
- *
- * Function schedules BH completion so it can be called again in a nested
- * event loop. When there are no events left to complete the BH is being
- * canceled.
- *
- */
-static void luring_process_completions(LuringState *s)
+static void luring_cqe_handler(CqeHandler *cqe_handler)
{
- struct io_uring_cqe *cqes;
- int total_bytes;
+ LuringRequest *req = container_of(cqe_handler, LuringRequest, cqe_handler);
+ int ret = cqe_handler->cqe.res;
- defer_call_begin();
+ trace_luring_cqe_handler(req, ret);
- /*
- * Request completion callbacks can run the nested event loop.
- * Schedule ourselves so the nested event loop will "see" remaining
- * completed requests and process them. Without this, completion
- * callbacks that wait for other requests using a nested event loop
- * would hang forever.
- *
- * This workaround is needed because io_uring uses poll_wait, which
- * is woken up when new events are added to the uring, thus polling on
- * the same uring fd will block unless more events are received.
- *
- * Other leaf block drivers (drivers that access the data themselves)
- * are networking based, so they poll sockets for data and run the
- * correct coroutine.
- */
- qemu_bh_schedule(s->completion_bh);
-
- while (io_uring_peek_cqe(&s->ring, &cqes) == 0) {
- LuringAIOCB *luringcb;
- int ret;
-
- if (!cqes) {
- break;
+ if (ret < 0) {
+ /*
+ * Only writev/readv/fsync requests on regular files or host block
+ * devices are submitted. Therefore -EAGAIN is not expected but it's
+ * known to happen sometimes with Linux SCSI. Submit again and hope
+ * the request completes successfully.
+ *
+ * For more information, see:
+ * https://lore.kernel.org/io-uring/20210727165811.284510-3-axboe@kernel.dk/T/#u
+ *
+ * If the code is changed to submit other types of requests in the
+ * future, then this workaround may need to be extended to deal with
+ * genuine -EAGAIN results that should not be resubmitted
+ * immediately.
+ */
+ if (ret == -EINTR || ret == -EAGAIN) {
+ aio_add_sqe(luring_prep_sqe, req, &req->cqe_handler);
+ return;
}
-
- luringcb = io_uring_cqe_get_data(cqes);
- ret = cqes->res;
- io_uring_cqe_seen(&s->ring, cqes);
- cqes = NULL;
-
- /* Change counters one-by-one because we can be nested. */
- s->io_q.in_flight--;
- trace_luring_process_completion(s, luringcb, ret);
-
+ } else if (req->qiov) {
/* total_read is non-zero only for resubmitted read requests */
- total_bytes = ret + luringcb->total_read;
+ int total_bytes = ret + req->total_read;
- if (ret < 0) {
- /*
- * Only writev/readv/fsync requests on regular files or host block
- * devices are submitted. Therefore -EAGAIN is not expected but it's
- * known to happen sometimes with Linux SCSI. Submit again and hope
- * the request completes successfully.
- *
- * For more information, see:
- * https://lore.kernel.org/io-uring/20210727165811.284510-3-axboe@kernel.dk/T/#u
- *
- * If the code is changed to submit other types of requests in the
- * future, then this workaround may need to be extended to deal with
- * genuine -EAGAIN results that should not be resubmitted
- * immediately.
- */
- if (ret == -EINTR || ret == -EAGAIN) {
- luring_resubmit(s, luringcb);
- continue;
- }
- } else if (!luringcb->qiov) {
- goto end;
- } else if (total_bytes == luringcb->qiov->size) {
+ if (total_bytes == req->qiov->size) {
ret = 0;
- /* Only read/write */
} else {
/* Short Read/Write */
- if (luringcb->is_read) {
+ if (req->type == QEMU_AIO_READ) {
if (ret > 0) {
- luring_resubmit_short_read(s, luringcb, ret);
- continue;
- } else {
- /* Pad with zeroes */
- qemu_iovec_memset(luringcb->qiov, total_bytes, 0,
- luringcb->qiov->size - total_bytes);
- ret = 0;
+ luring_resubmit_short_read(req, ret);
+ return;
}
+
+ /* Pad with zeroes */
+ qemu_iovec_memset(req->qiov, total_bytes, 0,
+ req->qiov->size - total_bytes);
+ ret = 0;
} else {
ret = -ENOSPC;
}
}
-end:
- luringcb->ret = ret;
- qemu_iovec_destroy(&luringcb->resubmit_qiov);
-
- /*
- * If the coroutine is already entered it must be in ioq_submit()
- * and will notice luringcb->ret has been filled in when it
- * eventually runs later. Coroutines cannot be entered recursively
- * so avoid doing that!
- */
- assert(luringcb->co->ctx == s->aio_context);
- if (!qemu_coroutine_entered(luringcb->co)) {
- aio_co_wake(luringcb->co);
- }
}
- qemu_bh_cancel(s->completion_bh);
+ req->ret = ret;
+ qemu_iovec_destroy(&req->resubmit_qiov);
- defer_call_end();
-}
-
-static int ioq_submit(LuringState *s)
-{
- int ret = 0;
- LuringAIOCB *luringcb, *luringcb_next;
-
- while (s->io_q.in_queue > 0) {
- /*
- * Try to fetch sqes from the ring for requests waiting in
- * the overflow queue
- */
- QSIMPLEQ_FOREACH_SAFE(luringcb, &s->io_q.submit_queue, next,
- luringcb_next) {
- struct io_uring_sqe *sqes = io_uring_get_sqe(&s->ring);
- if (!sqes) {
- break;
- }
- /* Prep sqe for submission */
- *sqes = luringcb->sqeq;
- QSIMPLEQ_REMOVE_HEAD(&s->io_q.submit_queue, next);
- }
- ret = io_uring_submit(&s->ring);
- trace_luring_io_uring_submit(s, ret);
- /* Prevent infinite loop if submission is refused */
- if (ret <= 0) {
- if (ret == -EAGAIN || ret == -EINTR) {
- continue;
- }
- break;
- }
- s->io_q.in_flight += ret;
- s->io_q.in_queue -= ret;
- }
- s->io_q.blocked = (s->io_q.in_queue > 0);
-
- if (s->io_q.in_flight) {
- /*
- * We can try to complete something just right away if there are
- * still requests in-flight.
- */
- luring_process_completions(s);
- }
- return ret;
-}
-
-static void luring_process_completions_and_submit(LuringState *s)
-{
- luring_process_completions(s);
-
- if (s->io_q.in_queue > 0) {
- ioq_submit(s);
+ /*
+ * If the coroutine is already entered it must be in luring_co_submit() and
+ * will notice req->ret has been filled in when it eventually runs later.
+ * Coroutines cannot be entered recursively so avoid doing that!
+ */
+ if (!qemu_coroutine_entered(req->co)) {
+ aio_co_wake(req->co);
}
}
-static void qemu_luring_completion_bh(void *opaque)
+int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd,
+ uint64_t offset, QEMUIOVector *qiov,
+ int type, BdrvRequestFlags flags)
{
- LuringState *s = opaque;
- luring_process_completions_and_submit(s);
-}
-
-static void qemu_luring_completion_cb(void *opaque)
-{
- LuringState *s = opaque;
- luring_process_completions_and_submit(s);
-}
-
-static bool qemu_luring_poll_cb(void *opaque)
-{
- LuringState *s = opaque;
-
- return io_uring_cq_ready(&s->ring);
-}
-
-static void qemu_luring_poll_ready(void *opaque)
-{
- LuringState *s = opaque;
-
- luring_process_completions_and_submit(s);
-}
-
-static void ioq_init(LuringQueue *io_q)
-{
- QSIMPLEQ_INIT(&io_q->submit_queue);
- io_q->in_queue = 0;
- io_q->in_flight = 0;
- io_q->blocked = false;
-}
-
-static void luring_deferred_fn(void *opaque)
-{
- LuringState *s = opaque;
- trace_luring_unplug_fn(s, s->io_q.blocked, s->io_q.in_queue,
- s->io_q.in_flight);
- if (!s->io_q.blocked && s->io_q.in_queue > 0) {
- ioq_submit(s);
- }
-}
-
-/**
- * luring_do_submit:
- * @fd: file descriptor for I/O
- * @luringcb: AIO control block
- * @s: AIO state
- * @offset: offset for request
- * @type: type of request
- *
- * Fetches sqes from ring, adds to pending queue and preps them
- *
- */
-static int luring_do_submit(int fd, LuringAIOCB *luringcb, LuringState *s,
- uint64_t offset, int type, BdrvRequestFlags flags)
-{
- int ret;
- struct io_uring_sqe *sqes = &luringcb->sqeq;
-
- switch (type) {
- case QEMU_AIO_WRITE:
-#ifdef HAVE_IO_URING_PREP_WRITEV2
- {
- int luring_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0;
- io_uring_prep_writev2(sqes, fd, luringcb->qiov->iov,
- luringcb->qiov->niov, offset, luring_flags);
- }
-#else
- assert(flags == 0);
- io_uring_prep_writev(sqes, fd, luringcb->qiov->iov,
- luringcb->qiov->niov, offset);
-#endif
- break;
- case QEMU_AIO_ZONE_APPEND:
- io_uring_prep_writev(sqes, fd, luringcb->qiov->iov,
- luringcb->qiov->niov, offset);
- break;
- case QEMU_AIO_READ:
- io_uring_prep_readv(sqes, fd, luringcb->qiov->iov,
- luringcb->qiov->niov, offset);
- break;
- case QEMU_AIO_FLUSH:
- io_uring_prep_fsync(sqes, fd, IORING_FSYNC_DATASYNC);
- break;
- default:
- fprintf(stderr, "%s: invalid AIO request type, aborting 0x%x.\n",
- __func__, type);
- abort();
- }
- io_uring_sqe_set_data(sqes, luringcb);
-
- QSIMPLEQ_INSERT_TAIL(&s->io_q.submit_queue, luringcb, next);
- s->io_q.in_queue++;
- trace_luring_do_submit(s, s->io_q.blocked, s->io_q.in_queue,
- s->io_q.in_flight);
- if (!s->io_q.blocked) {
- if (s->io_q.in_flight + s->io_q.in_queue >= MAX_ENTRIES) {
- ret = ioq_submit(s);
- trace_luring_do_submit_done(s, ret);
- return ret;
- }
-
- defer_call(luring_deferred_fn, s);
- }
- return 0;
-}
-
-int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd, uint64_t offset,
- QEMUIOVector *qiov, int type,
- BdrvRequestFlags flags)
-{
- int ret;
- AioContext *ctx = qemu_get_current_aio_context();
- LuringState *s = aio_get_linux_io_uring(ctx);
- LuringAIOCB luringcb = {
+ LuringRequest req = {
.co = qemu_coroutine_self(),
- .ret = -EINPROGRESS,
.qiov = qiov,
- .is_read = (type == QEMU_AIO_READ),
+ .ret = -EINPROGRESS,
+ .type = type,
+ .fd = fd,
+ .offset = offset,
+ .flags = flags,
};
- trace_luring_co_submit(bs, s, &luringcb, fd, offset, qiov ? qiov->size : 0,
- type);
- ret = luring_do_submit(fd, &luringcb, s, offset, type, flags);
- if (ret < 0) {
- return ret;
- }
+ req.cqe_handler.cb = luring_cqe_handler;
- if (luringcb.ret == -EINPROGRESS) {
+ trace_luring_co_submit(bs, &req, fd, offset, qiov ? qiov->size : 0, type);
+ aio_add_sqe(luring_prep_sqe, &req, &req.cqe_handler);
+
+ if (req.ret == -EINPROGRESS) {
qemu_coroutine_yield();
}
- return luringcb.ret;
-}
-
-void luring_detach_aio_context(LuringState *s, AioContext *old_context)
-{
- aio_set_fd_handler(old_context, s->ring.ring_fd,
- NULL, NULL, NULL, NULL, s);
- qemu_bh_delete(s->completion_bh);
- s->aio_context = NULL;
-}
-
-void luring_attach_aio_context(LuringState *s, AioContext *new_context)
-{
- s->aio_context = new_context;
- s->completion_bh = aio_bh_new(new_context, qemu_luring_completion_bh, s);
- aio_set_fd_handler(s->aio_context, s->ring.ring_fd,
- qemu_luring_completion_cb, NULL,
- qemu_luring_poll_cb, qemu_luring_poll_ready, s);
-}
-
-LuringState *luring_init(Error **errp)
-{
- int rc;
- LuringState *s = g_new0(LuringState, 1);
- struct io_uring *ring = &s->ring;
-
- trace_luring_init_state(s, sizeof(*s));
-
- rc = io_uring_queue_init(MAX_ENTRIES, ring, 0);
- if (rc < 0) {
- error_setg_errno(errp, -rc, "failed to init linux io_uring ring");
- g_free(s);
- return NULL;
- }
-
- ioq_init(&s->io_q);
- return s;
-
-}
-
-void luring_cleanup(LuringState *s)
-{
- io_uring_queue_exit(&s->ring);
- trace_luring_cleanup_state(s);
- g_free(s);
+ return req.ret;
}
bool luring_has_fua(void)
diff --git a/stubs/io_uring.c b/stubs/io_uring.c
deleted file mode 100644
index 622d1e4648..0000000000
--- a/stubs/io_uring.c
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Linux io_uring support.
- *
- * Copyright (C) 2009 IBM, Corp.
- * Copyright (C) 2009 Red Hat, Inc.
- *
- * This work is licensed under the terms of the GNU GPL, version 2 or later.
- * See the COPYING file in the top-level directory.
- */
-#include "qemu/osdep.h"
-#include "block/aio.h"
-#include "block/raw-aio.h"
-
-void luring_detach_aio_context(LuringState *s, AioContext *old_context)
-{
- abort();
-}
-
-void luring_attach_aio_context(LuringState *s, AioContext *new_context)
-{
- abort();
-}
-
-LuringState *luring_init(Error **errp)
-{
- abort();
-}
-
-void luring_cleanup(LuringState *s)
-{
- abort();
-}
diff --git a/util/async.c b/util/async.c
index 00e46b99f9..a216cf8695 100644
--- a/util/async.c
+++ b/util/async.c
@@ -386,14 +386,6 @@ aio_ctx_finalize(GSource *source)
}
#endif
-#ifdef CONFIG_LINUX_IO_URING
- if (ctx->linux_io_uring) {
- luring_detach_aio_context(ctx->linux_io_uring, ctx);
- luring_cleanup(ctx->linux_io_uring);
- ctx->linux_io_uring = NULL;
- }
-#endif
-
assert(QSLIST_EMPTY(&ctx->scheduled_coroutines));
qemu_bh_delete(ctx->co_schedule_bh);
@@ -468,29 +460,6 @@ LinuxAioState *aio_get_linux_aio(AioContext *ctx)
}
#endif
-#ifdef CONFIG_LINUX_IO_URING
-LuringState *aio_setup_linux_io_uring(AioContext *ctx, Error **errp)
-{
- if (ctx->linux_io_uring) {
- return ctx->linux_io_uring;
- }
-
- ctx->linux_io_uring = luring_init(errp);
- if (!ctx->linux_io_uring) {
- return NULL;
- }
-
- luring_attach_aio_context(ctx->linux_io_uring, ctx);
- return ctx->linux_io_uring;
-}
-
-LuringState *aio_get_linux_io_uring(AioContext *ctx)
-{
- assert(ctx->linux_io_uring);
- return ctx->linux_io_uring;
-}
-#endif
-
void aio_notify(AioContext *ctx)
{
/*
@@ -630,10 +599,6 @@ AioContext *aio_context_new(Error **errp)
ctx->linux_aio = NULL;
#endif
-#ifdef CONFIG_LINUX_IO_URING
- ctx->linux_io_uring = NULL;
-#endif
-
ctx->thread_pool = NULL;
qemu_rec_mutex_init(&ctx->lock);
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
diff --git a/block/trace-events b/block/trace-events
index 8e789e1f12..c9b4736ff8 100644
--- a/block/trace-events
+++ b/block/trace-events
@@ -62,15 +62,9 @@ qmp_block_stream(void *bs) "bs %p"
file_paio_submit(void *acb, void *opaque, int64_t offset, int count, int type) "acb %p opaque %p offset %"PRId64" count %d type %d"
# io_uring.c
-luring_init_state(void *s, size_t size) "s %p size %zu"
-luring_cleanup_state(void *s) "%p freed"
-luring_unplug_fn(void *s, int blocked, int queued, int inflight) "LuringState %p blocked %d queued %d inflight %d"
-luring_do_submit(void *s, int blocked, int queued, int inflight) "LuringState %p blocked %d queued %d inflight %d"
-luring_do_submit_done(void *s, int ret) "LuringState %p submitted to kernel %d"
-luring_co_submit(void *bs, void *s, void *luringcb, int fd, uint64_t offset, size_t nbytes, int type) "bs %p s %p luringcb %p fd %d offset %" PRId64 " nbytes %zd type %d"
-luring_process_completion(void *s, void *aiocb, int ret) "LuringState %p luringcb %p ret %d"
-luring_io_uring_submit(void *s, int ret) "LuringState %p ret %d"
-luring_resubmit_short_read(void *s, void *luringcb, int nread) "LuringState %p luringcb %p nread %d"
+luring_cqe_handler(void *req, int ret) "req %p ret %d"
+luring_co_submit(void *bs, void *req, int fd, uint64_t offset, size_t nbytes, int type) "bs %p req %p fd %d offset %" PRId64 " nbytes %zd type %d"
+luring_resubmit_short_read(void *req, int nread) "req %p nread %d"
# qcow2.c
qcow2_add_task(void *co, void *bs, void *pool, const char *action, int cluster_type, uint64_t host_offset, uint64_t offset, uint64_t bytes, void *qiov, size_t qiov_offset) "co %p bs %p pool %p: %s: cluster_type %d file_cluster_offset %" PRIu64 " offset %" PRIu64 " bytes %" PRIu64 " qiov %p qiov_offset %zu"
diff --git a/stubs/meson.build b/stubs/meson.build
index 5d577467bf..3ba9c04ba4 100644
--- a/stubs/meson.build
+++ b/stubs/meson.build
@@ -32,9 +32,6 @@ if have_block or have_ga
stub_ss.add(files('cpus-virtual-clock.c'))
stub_ss.add(files('icount.c'))
stub_ss.add(files('graph-lock.c'))
- if linux_io_uring.found()
- stub_ss.add(files('io_uring.c'))
- endif
if libaio.found()
stub_ss.add(files('linux-aio.c'))
endif
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [RESEND PATCH v5 13/13] block/io_uring: use non-vectored read/write when possible
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (11 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 12/13] block/io_uring: use aio_add_sqe() Stefan Hajnoczi
@ 2025-10-30 15:21 ` Stefan Hajnoczi
2025-10-30 18:11 ` [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Kevin Wolf
13 siblings, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-10-30 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
Stefan Hajnoczi, Kevin Wolf, qemu-block
The io_uring_prep_readv2/writev2() man pages recommend using the
non-vectored read/write operations when possible for performance
reasons.
I didn't measure a significant difference but it doesn't hurt to have
this optimization in place.
Suggested-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v5:
- Reduce #ifdef HAVE_IO_URING_PREP_WRITEV2 code duplication [Kevin]
---
block/io_uring.c | 34 ++++++++++++++++++++++++++--------
1 file changed, 26 insertions(+), 8 deletions(-)
diff --git a/block/io_uring.c b/block/io_uring.c
index dd930ee57e..f1514cf024 100644
--- a/block/io_uring.c
+++ b/block/io_uring.c
@@ -46,17 +46,28 @@ static void luring_prep_sqe(struct io_uring_sqe *sqe, void *opaque)
switch (req->type) {
case QEMU_AIO_WRITE:
-#ifdef HAVE_IO_URING_PREP_WRITEV2
{
int luring_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0;
- io_uring_prep_writev2(sqe, fd, qiov->iov,
- qiov->niov, offset, luring_flags);
- }
+ if (luring_flags != 0 || qiov->niov > 1) {
+#ifdef HAVE_IO_URING_PREP_WRITEV2
+ io_uring_prep_writev2(sqe, fd, qiov->iov,
+ qiov->niov, offset, luring_flags);
#else
- assert(flags == 0);
- io_uring_prep_writev(sqe, fd, qiov->iov, qiov->niov, offset);
+ /*
+ * FUA should only be enabled with HAVE_IO_URING_PREP_WRITEV2, see
+ * luring_has_fua().
+ */
+ assert(luring_flags == 0);
+
+ io_uring_prep_writev(sqe, fd, qiov->iov, qiov->niov, offset);
#endif
+ } else {
+ /* The man page says non-vectored is faster than vectored */
+ struct iovec *iov = qiov->iov;
+ io_uring_prep_write(sqe, fd, iov->iov_base, iov->iov_len, offset);
+ }
break;
+ }
case QEMU_AIO_ZONE_APPEND:
io_uring_prep_writev(sqe, fd, qiov->iov, qiov->niov, offset);
break;
@@ -65,8 +76,15 @@ static void luring_prep_sqe(struct io_uring_sqe *sqe, void *opaque)
if (req->resubmit_qiov.iov != NULL) {
qiov = &req->resubmit_qiov;
}
- io_uring_prep_readv(sqe, fd, qiov->iov, qiov->niov,
- offset + req->total_read);
+ if (qiov->niov > 1) {
+ io_uring_prep_readv(sqe, fd, qiov->iov, qiov->niov,
+ offset + req->total_read);
+ } else {
+ /* The man page says non-vectored is faster than vectored */
+ struct iovec *iov = qiov->iov;
+ io_uring_prep_read(sqe, fd, iov->iov_base, iov->iov_len,
+ offset + req->total_read);
+ }
break;
}
case QEMU_AIO_FLUSH:
--
2.51.0
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
` (12 preceding siblings ...)
2025-10-30 15:21 ` [RESEND PATCH v5 13/13] block/io_uring: use non-vectored read/write when possible Stefan Hajnoczi
@ 2025-10-30 18:11 ` Kevin Wolf
2025-11-03 10:40 ` Kevin Wolf
13 siblings, 1 reply; 19+ messages in thread
From: Kevin Wolf @ 2025-10-30 18:11 UTC (permalink / raw)
To: Stefan Hajnoczi
Cc: qemu-devel, Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
qemu-block
Am 30.10.2025 um 16:21 hat Stefan Hajnoczi geschrieben:
> v5:
> - Explain how fdmon-io_uring.c differs from other fdmon implementations
> in commit message [Kevin]
> - Move test-nested-aio-poll aio_get_g_source() removal into commit that touches test case [Kevin]
> - Avoid g_source_add_poll() use-after-free in fdmon_poll_update() [Kevin]
> - Avoid duplication in fdmon_epoll_gsource_dispatch(), use fdmon_epoll_wait() [Kevin]
> - Drop unnecessary revents checks in fdmon_poll_gsource_dispatch() [Kevin]
> - Mention in commit message that fdmon-io_uring.c is the new default [Kevin]
> - Add comments explaining how to clean up resources in error paths [Kevin]
> - Indicate error in return value from function with Error *errp arg [Kevin]
> - Add patch to unindent fdmon_io_uring_destroy() [Kevin]
> - Add patch to introduce FDMonOps->dispatch() callback [Kevin]
> - Drop patch with hacky BH optimization for fdmon-io_uring.c [Kevin]
> - Replace cqe_handler_bh with FDMonOps->dispatch() [Kevin]
> - Rename AioHandler->cqe_handler field to ->internal_cqe_handler [Kevin]
> - Consolidate fdmon-io_uring.c trace-events changes into this commit
> - Reduce #ifdef HAVE_IO_URING_PREP_WRITEV2 code duplication [Kevin]
The changes look good to me.
However, the test cases are still failing. I just tried to see where
test-aio is stuck, and while I looked for a backtrace first, I noticed
that just attaching gdb to the process and immediately detaching again
makes the test unstuck. Very strange.
This is the backtrace, maybe a bit unsurpring:
(gdb) bt
#0 0x00007ffff7e6fec6 in __io_uring_submit () from /lib64/liburing.so.2
#1 0x00005555556f4394 in fdmon_io_uring_wait (ctx=0x555556409950, ready_list=0x7fffffffcda0, timeout=749993088) at ../util/fdmon-io_uring.c:410
#2 0x00005555556ed29f in aio_poll (ctx=0x555556409950, blocking=true) at ../util/aio-posix.c:699
#3 0x0000555555681547 in test_timer_schedule () at ../tests/unit/test-aio.c:413
#4 0x00007ffff6f30e7e in test_case_run (tc=0x55555640d340, test_run_name=0x55555640de10 "/aio/timer/schedule", path=<optimized out>) at ../glib/gtestutils.c:3115
#5 g_test_run_suite_internal (suite=suite@entry=0x5555558696d0, path=path@entry=0x0) at ../glib/gtestutils.c:3210
#6 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867480, path=path@entry=0x0) at ../glib/gtestutils.c:3229
#7 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867720, path=path@entry=0x0) at ../glib/gtestutils.c:3229
#8 0x00007ffff6f313aa in g_test_run_suite (suite=suite@entry=0x555555867720) at ../glib/gtestutils.c:3310
#9 0x00007ffff6f31440 in g_test_run () at ../glib/gtestutils.c:2379
#10 g_test_run () at ../glib/gtestutils.c:2366
#11 0x000055555567e204 in main (argc=1, argv=0x7fffffffd488) at ../tests/unit/test-aio.c:872
And running it under strace shows that we're indeed hanging in the
syscall:
write(1, "# Start of timer tests\n", 23) = 23
eventfd2(0, EFD_CLOEXEC|EFD_NONBLOCK) = 9
io_uring_enter(7, 1, 0, 0, NULL, 8) = 1
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffc239bec80) = 0
io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8
Of course, if I start the test without strace and then attach strace to
the running process, that gets it unstuck like attaching gdb (not very
surprising, I guess, it's both just ptrace).
Finally I tried Ctrl-C while having strace logging to a file, and now
the io_uring_enter() returns 1 (rather than EINTR or 0 or whatever):
io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++
Not sure what to make of this.
I think you already said you run the same kernel version, but just to be
sure, I'm running 6.17.5-200.fc42.x86_64.
Kevin
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API
2025-10-30 18:11 ` [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Kevin Wolf
@ 2025-11-03 10:40 ` Kevin Wolf
2025-11-03 13:30 ` Kevin Wolf
0 siblings, 1 reply; 19+ messages in thread
From: Kevin Wolf @ 2025-11-03 10:40 UTC (permalink / raw)
To: Stefan Hajnoczi
Cc: qemu-devel, Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
qemu-block
Am 30.10.2025 um 19:11 hat Kevin Wolf geschrieben:
> Am 30.10.2025 um 16:21 hat Stefan Hajnoczi geschrieben:
> > v5:
> > - Explain how fdmon-io_uring.c differs from other fdmon implementations
> > in commit message [Kevin]
> > - Move test-nested-aio-poll aio_get_g_source() removal into commit that touches test case [Kevin]
> > - Avoid g_source_add_poll() use-after-free in fdmon_poll_update() [Kevin]
> > - Avoid duplication in fdmon_epoll_gsource_dispatch(), use fdmon_epoll_wait() [Kevin]
> > - Drop unnecessary revents checks in fdmon_poll_gsource_dispatch() [Kevin]
> > - Mention in commit message that fdmon-io_uring.c is the new default [Kevin]
> > - Add comments explaining how to clean up resources in error paths [Kevin]
> > - Indicate error in return value from function with Error *errp arg [Kevin]
> > - Add patch to unindent fdmon_io_uring_destroy() [Kevin]
> > - Add patch to introduce FDMonOps->dispatch() callback [Kevin]
> > - Drop patch with hacky BH optimization for fdmon-io_uring.c [Kevin]
> > - Replace cqe_handler_bh with FDMonOps->dispatch() [Kevin]
> > - Rename AioHandler->cqe_handler field to ->internal_cqe_handler [Kevin]
> > - Consolidate fdmon-io_uring.c trace-events changes into this commit
> > - Reduce #ifdef HAVE_IO_URING_PREP_WRITEV2 code duplication [Kevin]
>
> The changes look good to me.
>
> However, the test cases are still failing. I just tried to see where
> test-aio is stuck, and while I looked for a backtrace first, I noticed
> that just attaching gdb to the process and immediately detaching again
> makes the test unstuck. Very strange.
>
> This is the backtrace, maybe a bit unsurpring:
>
> (gdb) bt
> #0 0x00007ffff7e6fec6 in __io_uring_submit () from /lib64/liburing.so.2
> #1 0x00005555556f4394 in fdmon_io_uring_wait (ctx=0x555556409950, ready_list=0x7fffffffcda0, timeout=749993088) at ../util/fdmon-io_uring.c:410
> #2 0x00005555556ed29f in aio_poll (ctx=0x555556409950, blocking=true) at ../util/aio-posix.c:699
> #3 0x0000555555681547 in test_timer_schedule () at ../tests/unit/test-aio.c:413
> #4 0x00007ffff6f30e7e in test_case_run (tc=0x55555640d340, test_run_name=0x55555640de10 "/aio/timer/schedule", path=<optimized out>) at ../glib/gtestutils.c:3115
> #5 g_test_run_suite_internal (suite=suite@entry=0x5555558696d0, path=path@entry=0x0) at ../glib/gtestutils.c:3210
> #6 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867480, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> #7 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867720, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> #8 0x00007ffff6f313aa in g_test_run_suite (suite=suite@entry=0x555555867720) at ../glib/gtestutils.c:3310
> #9 0x00007ffff6f31440 in g_test_run () at ../glib/gtestutils.c:2379
> #10 g_test_run () at ../glib/gtestutils.c:2366
> #11 0x000055555567e204 in main (argc=1, argv=0x7fffffffd488) at ../tests/unit/test-aio.c:872
>
> And running it under strace shows that we're indeed hanging in the
> syscall:
>
> write(1, "# Start of timer tests\n", 23) = 23
> eventfd2(0, EFD_CLOEXEC|EFD_NONBLOCK) = 9
> io_uring_enter(7, 1, 0, 0, NULL, 8) = 1
> clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffc239bec80) = 0
> io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8
>
> Of course, if I start the test without strace and then attach strace to
> the running process, that gets it unstuck like attaching gdb (not very
> surprising, I guess, it's both just ptrace).
>
> Finally I tried Ctrl-C while having strace logging to a file, and now
> the io_uring_enter() returns 1 (rather than EINTR or 0 or whatever):
>
> io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1
> --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
> +++ killed by SIGINT +++
>
> Not sure what to make of this.
>
> I think you already said you run the same kernel version, but just to be
> sure, I'm running 6.17.5-200.fc42.x86_64.
I'm at the point where I'm bisecting compiler flags...
I have seen three different outcomes from test-aio:
1. It hangs. This is what I saw in my normal clang build. This configure
line seems to be enough to trigger it:
../configure '--target-list=x86_64-softmmu' '--cc=clang' '--cxx=clang++'
2. An assertion failure. I haven't seen this in the actual QEMU tree
with clang. With gcc, it seems to happen if you use -O0:
../configure '--target-list=x86_64-softmmu' '--enable-debug'
Outside of the QEMU tree with a manual Makefile, I saw this behaviour
with clang and -fstack-protector-strong, but without
-ftrivial-auto-var-init=zero. Add the latter turns it into the hang.
3. It just passes. This is what I saw in my default gcc build without
--enable-debug. The test also passes with --disable-stack-protector
added to both configure lines in 1 and 2.
Not sure yet where the flags make the difference, but I guess it does
hint at something going wrong on the stack.
Kevin
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API
2025-11-03 10:40 ` Kevin Wolf
@ 2025-11-03 13:30 ` Kevin Wolf
2025-11-03 16:45 ` Stefan Hajnoczi
2025-11-04 2:08 ` Stefan Hajnoczi
0 siblings, 2 replies; 19+ messages in thread
From: Kevin Wolf @ 2025-11-03 13:30 UTC (permalink / raw)
To: Stefan Hajnoczi
Cc: qemu-devel, Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
qemu-block
Am 03.11.2025 um 11:40 hat Kevin Wolf geschrieben:
> Am 30.10.2025 um 19:11 hat Kevin Wolf geschrieben:
> > Am 30.10.2025 um 16:21 hat Stefan Hajnoczi geschrieben:
> > > v5:
> > > - Explain how fdmon-io_uring.c differs from other fdmon implementations
> > > in commit message [Kevin]
> > > - Move test-nested-aio-poll aio_get_g_source() removal into commit that touches test case [Kevin]
> > > - Avoid g_source_add_poll() use-after-free in fdmon_poll_update() [Kevin]
> > > - Avoid duplication in fdmon_epoll_gsource_dispatch(), use fdmon_epoll_wait() [Kevin]
> > > - Drop unnecessary revents checks in fdmon_poll_gsource_dispatch() [Kevin]
> > > - Mention in commit message that fdmon-io_uring.c is the new default [Kevin]
> > > - Add comments explaining how to clean up resources in error paths [Kevin]
> > > - Indicate error in return value from function with Error *errp arg [Kevin]
> > > - Add patch to unindent fdmon_io_uring_destroy() [Kevin]
> > > - Add patch to introduce FDMonOps->dispatch() callback [Kevin]
> > > - Drop patch with hacky BH optimization for fdmon-io_uring.c [Kevin]
> > > - Replace cqe_handler_bh with FDMonOps->dispatch() [Kevin]
> > > - Rename AioHandler->cqe_handler field to ->internal_cqe_handler [Kevin]
> > > - Consolidate fdmon-io_uring.c trace-events changes into this commit
> > > - Reduce #ifdef HAVE_IO_URING_PREP_WRITEV2 code duplication [Kevin]
> >
> > The changes look good to me.
> >
> > However, the test cases are still failing. I just tried to see where
> > test-aio is stuck, and while I looked for a backtrace first, I noticed
> > that just attaching gdb to the process and immediately detaching again
> > makes the test unstuck. Very strange.
> >
> > This is the backtrace, maybe a bit unsurpring:
> >
> > (gdb) bt
> > #0 0x00007ffff7e6fec6 in __io_uring_submit () from /lib64/liburing.so.2
> > #1 0x00005555556f4394 in fdmon_io_uring_wait (ctx=0x555556409950, ready_list=0x7fffffffcda0, timeout=749993088) at ../util/fdmon-io_uring.c:410
> > #2 0x00005555556ed29f in aio_poll (ctx=0x555556409950, blocking=true) at ../util/aio-posix.c:699
> > #3 0x0000555555681547 in test_timer_schedule () at ../tests/unit/test-aio.c:413
> > #4 0x00007ffff6f30e7e in test_case_run (tc=0x55555640d340, test_run_name=0x55555640de10 "/aio/timer/schedule", path=<optimized out>) at ../glib/gtestutils.c:3115
> > #5 g_test_run_suite_internal (suite=suite@entry=0x5555558696d0, path=path@entry=0x0) at ../glib/gtestutils.c:3210
> > #6 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867480, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> > #7 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867720, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> > #8 0x00007ffff6f313aa in g_test_run_suite (suite=suite@entry=0x555555867720) at ../glib/gtestutils.c:3310
> > #9 0x00007ffff6f31440 in g_test_run () at ../glib/gtestutils.c:2379
> > #10 g_test_run () at ../glib/gtestutils.c:2366
> > #11 0x000055555567e204 in main (argc=1, argv=0x7fffffffd488) at ../tests/unit/test-aio.c:872
> >
> > And running it under strace shows that we're indeed hanging in the
> > syscall:
> >
> > write(1, "# Start of timer tests\n", 23) = 23
> > eventfd2(0, EFD_CLOEXEC|EFD_NONBLOCK) = 9
> > io_uring_enter(7, 1, 0, 0, NULL, 8) = 1
> > clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffc239bec80) = 0
> > io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8
> >
> > Of course, if I start the test without strace and then attach strace to
> > the running process, that gets it unstuck like attaching gdb (not very
> > surprising, I guess, it's both just ptrace).
> >
> > Finally I tried Ctrl-C while having strace logging to a file, and now
> > the io_uring_enter() returns 1 (rather than EINTR or 0 or whatever):
> >
> > io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1
> > --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
> > +++ killed by SIGINT +++
> >
> > Not sure what to make of this.
> >
> > I think you already said you run the same kernel version, but just to be
> > sure, I'm running 6.17.5-200.fc42.x86_64.
>
> I'm at the point where I'm bisecting compiler flags...
>
> I have seen three different outcomes from test-aio:
>
> 1. It hangs. This is what I saw in my normal clang build. This configure
> line seems to be enough to trigger it:
> ../configure '--target-list=x86_64-softmmu' '--cc=clang' '--cxx=clang++'
>
> 2. An assertion failure. I haven't seen this in the actual QEMU tree
> with clang. With gcc, it seems to happen if you use -O0:
> ../configure '--target-list=x86_64-softmmu' '--enable-debug'
>
> Outside of the QEMU tree with a manual Makefile, I saw this behaviour
> with clang and -fstack-protector-strong, but without
> -ftrivial-auto-var-init=zero. Add the latter turns it into the hang.
>
> 3. It just passes. This is what I saw in my default gcc build without
> --enable-debug. The test also passes with --disable-stack-protector
> added to both configure lines in 1 and 2.
>
> Not sure yet where the flags make the difference, but I guess it does
> hint at something going wrong on the stack.
Ok, that was quite some debugging, but I think I have it. The problem is
add_timeout_sqe():
static void add_timeout_sqe(AioContext *ctx, int64_t ns)
{
struct io_uring_sqe *sqe;
ts = (struct __kernel_timespec) {
.tv_sec = ns / NANOSECONDS_PER_SECOND,
.tv_nsec = ns % NANOSECONDS_PER_SECOND,
};
sqe = get_sqe(ctx);
io_uring_prep_timeout(sqe, &ts, 1, 0);
io_uring_sqe_set_data(sqe, NULL);
}
What io_uring_prep_timeout() does is that it just stores the ts pointer
in the SQE, the timeout is never copied anywhere. Obviously, by the time
that we submit the SQE, ts has been out of scope for a long time, so the
kernel reads random data as a timeout.
# bpftrace -e 'kfunc:io_timeout { printf("%s: io_timeout %lld s + %lld ns\n", comm, ((struct io_timeout_data *)args.req->async_data)->ts.tv_sec, ((struct io_timeout_data *)args.req->async_data)->ts.tv_nsec ) }'
Attaching 1 probe...
test-aio: io_timeout 0 s + 140736377549872 ns
>>> hex(140736377549872)
'0x7fffbdca7430'
That looked a bit suspicious for a timeout. :-)
After fixing this, we still have the problem that io_uring_enter() can
return early without failing with EINTR when something like a signal
arrives. This means that a blocking aio_poll(true) can actually return
without any progress. Not sure if it matters in practice, but it can
make test cases fail.
Not completely sure when this happens, though. When running the aio-test
under strace, kill -CONT makes it return early and fail the assertion,
but without strace, I can't seem to reproduce the problem at the moment.
Attaching strace or gdb to the running process that is waiting for the
timeout also makes it return early and fail the assertion.
Kevin
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API
2025-11-03 13:30 ` Kevin Wolf
@ 2025-11-03 16:45 ` Stefan Hajnoczi
2025-11-04 2:08 ` Stefan Hajnoczi
1 sibling, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-11-03 16:45 UTC (permalink / raw)
To: Kevin Wolf
Cc: qemu-devel, Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
qemu-block
[-- Attachment #1: Type: text/plain, Size: 7527 bytes --]
On Mon, Nov 03, 2025 at 02:30:34PM +0100, Kevin Wolf wrote:
> Am 03.11.2025 um 11:40 hat Kevin Wolf geschrieben:
> > Am 30.10.2025 um 19:11 hat Kevin Wolf geschrieben:
> > > Am 30.10.2025 um 16:21 hat Stefan Hajnoczi geschrieben:
> > > > v5:
> > > > - Explain how fdmon-io_uring.c differs from other fdmon implementations
> > > > in commit message [Kevin]
> > > > - Move test-nested-aio-poll aio_get_g_source() removal into commit that touches test case [Kevin]
> > > > - Avoid g_source_add_poll() use-after-free in fdmon_poll_update() [Kevin]
> > > > - Avoid duplication in fdmon_epoll_gsource_dispatch(), use fdmon_epoll_wait() [Kevin]
> > > > - Drop unnecessary revents checks in fdmon_poll_gsource_dispatch() [Kevin]
> > > > - Mention in commit message that fdmon-io_uring.c is the new default [Kevin]
> > > > - Add comments explaining how to clean up resources in error paths [Kevin]
> > > > - Indicate error in return value from function with Error *errp arg [Kevin]
> > > > - Add patch to unindent fdmon_io_uring_destroy() [Kevin]
> > > > - Add patch to introduce FDMonOps->dispatch() callback [Kevin]
> > > > - Drop patch with hacky BH optimization for fdmon-io_uring.c [Kevin]
> > > > - Replace cqe_handler_bh with FDMonOps->dispatch() [Kevin]
> > > > - Rename AioHandler->cqe_handler field to ->internal_cqe_handler [Kevin]
> > > > - Consolidate fdmon-io_uring.c trace-events changes into this commit
> > > > - Reduce #ifdef HAVE_IO_URING_PREP_WRITEV2 code duplication [Kevin]
> > >
> > > The changes look good to me.
> > >
> > > However, the test cases are still failing. I just tried to see where
> > > test-aio is stuck, and while I looked for a backtrace first, I noticed
> > > that just attaching gdb to the process and immediately detaching again
> > > makes the test unstuck. Very strange.
> > >
> > > This is the backtrace, maybe a bit unsurpring:
> > >
> > > (gdb) bt
> > > #0 0x00007ffff7e6fec6 in __io_uring_submit () from /lib64/liburing.so.2
> > > #1 0x00005555556f4394 in fdmon_io_uring_wait (ctx=0x555556409950, ready_list=0x7fffffffcda0, timeout=749993088) at ../util/fdmon-io_uring.c:410
> > > #2 0x00005555556ed29f in aio_poll (ctx=0x555556409950, blocking=true) at ../util/aio-posix.c:699
> > > #3 0x0000555555681547 in test_timer_schedule () at ../tests/unit/test-aio.c:413
> > > #4 0x00007ffff6f30e7e in test_case_run (tc=0x55555640d340, test_run_name=0x55555640de10 "/aio/timer/schedule", path=<optimized out>) at ../glib/gtestutils.c:3115
> > > #5 g_test_run_suite_internal (suite=suite@entry=0x5555558696d0, path=path@entry=0x0) at ../glib/gtestutils.c:3210
> > > #6 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867480, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> > > #7 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867720, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> > > #8 0x00007ffff6f313aa in g_test_run_suite (suite=suite@entry=0x555555867720) at ../glib/gtestutils.c:3310
> > > #9 0x00007ffff6f31440 in g_test_run () at ../glib/gtestutils.c:2379
> > > #10 g_test_run () at ../glib/gtestutils.c:2366
> > > #11 0x000055555567e204 in main (argc=1, argv=0x7fffffffd488) at ../tests/unit/test-aio.c:872
> > >
> > > And running it under strace shows that we're indeed hanging in the
> > > syscall:
> > >
> > > write(1, "# Start of timer tests\n", 23) = 23
> > > eventfd2(0, EFD_CLOEXEC|EFD_NONBLOCK) = 9
> > > io_uring_enter(7, 1, 0, 0, NULL, 8) = 1
> > > clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffc239bec80) = 0
> > > io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8
> > >
> > > Of course, if I start the test without strace and then attach strace to
> > > the running process, that gets it unstuck like attaching gdb (not very
> > > surprising, I guess, it's both just ptrace).
> > >
> > > Finally I tried Ctrl-C while having strace logging to a file, and now
> > > the io_uring_enter() returns 1 (rather than EINTR or 0 or whatever):
> > >
> > > io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1
> > > --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
> > > +++ killed by SIGINT +++
> > >
> > > Not sure what to make of this.
> > >
> > > I think you already said you run the same kernel version, but just to be
> > > sure, I'm running 6.17.5-200.fc42.x86_64.
> >
> > I'm at the point where I'm bisecting compiler flags...
> >
> > I have seen three different outcomes from test-aio:
> >
> > 1. It hangs. This is what I saw in my normal clang build. This configure
> > line seems to be enough to trigger it:
> > ../configure '--target-list=x86_64-softmmu' '--cc=clang' '--cxx=clang++'
> >
> > 2. An assertion failure. I haven't seen this in the actual QEMU tree
> > with clang. With gcc, it seems to happen if you use -O0:
> > ../configure '--target-list=x86_64-softmmu' '--enable-debug'
> >
> > Outside of the QEMU tree with a manual Makefile, I saw this behaviour
> > with clang and -fstack-protector-strong, but without
> > -ftrivial-auto-var-init=zero. Add the latter turns it into the hang.
> >
> > 3. It just passes. This is what I saw in my default gcc build without
> > --enable-debug. The test also passes with --disable-stack-protector
> > added to both configure lines in 1 and 2.
> >
> > Not sure yet where the flags make the difference, but I guess it does
> > hint at something going wrong on the stack.
>
> Ok, that was quite some debugging, but I think I have it. The problem is
> add_timeout_sqe():
>
> static void add_timeout_sqe(AioContext *ctx, int64_t ns)
> {
> struct io_uring_sqe *sqe;
> ts = (struct __kernel_timespec) {
> .tv_sec = ns / NANOSECONDS_PER_SECOND,
> .tv_nsec = ns % NANOSECONDS_PER_SECOND,
> };
>
> sqe = get_sqe(ctx);
> io_uring_prep_timeout(sqe, &ts, 1, 0);
> io_uring_sqe_set_data(sqe, NULL);
> }
>
> What io_uring_prep_timeout() does is that it just stores the ts pointer
> in the SQE, the timeout is never copied anywhere. Obviously, by the time
> that we submit the SQE, ts has been out of scope for a long time, so the
> kernel reads random data as a timeout.
>
> # bpftrace -e 'kfunc:io_timeout { printf("%s: io_timeout %lld s + %lld ns\n", comm, ((struct io_timeout_data *)args.req->async_data)->ts.tv_sec, ((struct io_timeout_data *)args.req->async_data)->ts.tv_nsec ) }'
> Attaching 1 probe...
> test-aio: io_timeout 0 s + 140736377549872 ns
>
> >>> hex(140736377549872)
> '0x7fffbdca7430'
>
> That looked a bit suspicious for a timeout. :-)
>
> After fixing this, we still have the problem that io_uring_enter() can
> return early without failing with EINTR when something like a signal
> arrives. This means that a blocking aio_poll(true) can actually return
> without any progress. Not sure if it matters in practice, but it can
> make test cases fail.
>
> Not completely sure when this happens, though. When running the aio-test
> under strace, kill -CONT makes it return early and fail the assertion,
> but without strace, I can't seem to reproduce the problem at the moment.
> Attaching strace or gdb to the running process that is waiting for the
> timeout also makes it return early and fail the assertion.
Hi Kevin,
Thank you for going through the effort of debugging this!
I'll see if I can track down the issue with io_uring_enter() returning
early.
Stefan
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API
2025-11-03 13:30 ` Kevin Wolf
2025-11-03 16:45 ` Stefan Hajnoczi
@ 2025-11-04 2:08 ` Stefan Hajnoczi
1 sibling, 0 replies; 19+ messages in thread
From: Stefan Hajnoczi @ 2025-11-04 2:08 UTC (permalink / raw)
To: Kevin Wolf
Cc: qemu-devel, Hanna Czenczek, Paolo Bonzini, hibriansong, eblake,
qemu-block
[-- Attachment #1: Type: text/plain, Size: 9187 bytes --]
On Mon, Nov 03, 2025 at 02:30:34PM +0100, Kevin Wolf wrote:
> Am 03.11.2025 um 11:40 hat Kevin Wolf geschrieben:
> > Am 30.10.2025 um 19:11 hat Kevin Wolf geschrieben:
> > > Am 30.10.2025 um 16:21 hat Stefan Hajnoczi geschrieben:
> > > > v5:
> > > > - Explain how fdmon-io_uring.c differs from other fdmon implementations
> > > > in commit message [Kevin]
> > > > - Move test-nested-aio-poll aio_get_g_source() removal into commit that touches test case [Kevin]
> > > > - Avoid g_source_add_poll() use-after-free in fdmon_poll_update() [Kevin]
> > > > - Avoid duplication in fdmon_epoll_gsource_dispatch(), use fdmon_epoll_wait() [Kevin]
> > > > - Drop unnecessary revents checks in fdmon_poll_gsource_dispatch() [Kevin]
> > > > - Mention in commit message that fdmon-io_uring.c is the new default [Kevin]
> > > > - Add comments explaining how to clean up resources in error paths [Kevin]
> > > > - Indicate error in return value from function with Error *errp arg [Kevin]
> > > > - Add patch to unindent fdmon_io_uring_destroy() [Kevin]
> > > > - Add patch to introduce FDMonOps->dispatch() callback [Kevin]
> > > > - Drop patch with hacky BH optimization for fdmon-io_uring.c [Kevin]
> > > > - Replace cqe_handler_bh with FDMonOps->dispatch() [Kevin]
> > > > - Rename AioHandler->cqe_handler field to ->internal_cqe_handler [Kevin]
> > > > - Consolidate fdmon-io_uring.c trace-events changes into this commit
> > > > - Reduce #ifdef HAVE_IO_URING_PREP_WRITEV2 code duplication [Kevin]
> > >
> > > The changes look good to me.
> > >
> > > However, the test cases are still failing. I just tried to see where
> > > test-aio is stuck, and while I looked for a backtrace first, I noticed
> > > that just attaching gdb to the process and immediately detaching again
> > > makes the test unstuck. Very strange.
> > >
> > > This is the backtrace, maybe a bit unsurpring:
> > >
> > > (gdb) bt
> > > #0 0x00007ffff7e6fec6 in __io_uring_submit () from /lib64/liburing.so.2
> > > #1 0x00005555556f4394 in fdmon_io_uring_wait (ctx=0x555556409950, ready_list=0x7fffffffcda0, timeout=749993088) at ../util/fdmon-io_uring.c:410
> > > #2 0x00005555556ed29f in aio_poll (ctx=0x555556409950, blocking=true) at ../util/aio-posix.c:699
> > > #3 0x0000555555681547 in test_timer_schedule () at ../tests/unit/test-aio.c:413
> > > #4 0x00007ffff6f30e7e in test_case_run (tc=0x55555640d340, test_run_name=0x55555640de10 "/aio/timer/schedule", path=<optimized out>) at ../glib/gtestutils.c:3115
> > > #5 g_test_run_suite_internal (suite=suite@entry=0x5555558696d0, path=path@entry=0x0) at ../glib/gtestutils.c:3210
> > > #6 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867480, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> > > #7 0x00007ffff6f30df3 in g_test_run_suite_internal (suite=suite@entry=0x555555867720, path=path@entry=0x0) at ../glib/gtestutils.c:3229
> > > #8 0x00007ffff6f313aa in g_test_run_suite (suite=suite@entry=0x555555867720) at ../glib/gtestutils.c:3310
> > > #9 0x00007ffff6f31440 in g_test_run () at ../glib/gtestutils.c:2379
> > > #10 g_test_run () at ../glib/gtestutils.c:2366
> > > #11 0x000055555567e204 in main (argc=1, argv=0x7fffffffd488) at ../tests/unit/test-aio.c:872
> > >
> > > And running it under strace shows that we're indeed hanging in the
> > > syscall:
> > >
> > > write(1, "# Start of timer tests\n", 23) = 23
> > > eventfd2(0, EFD_CLOEXEC|EFD_NONBLOCK) = 9
> > > io_uring_enter(7, 1, 0, 0, NULL, 8) = 1
> > > clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffc239bec80) = 0
> > > io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8
> > >
> > > Of course, if I start the test without strace and then attach strace to
> > > the running process, that gets it unstuck like attaching gdb (not very
> > > surprising, I guess, it's both just ptrace).
> > >
> > > Finally I tried Ctrl-C while having strace logging to a file, and now
> > > the io_uring_enter() returns 1 (rather than EINTR or 0 or whatever):
> > >
> > > io_uring_enter(7, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1
> > > --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
> > > +++ killed by SIGINT +++
> > >
> > > Not sure what to make of this.
> > >
> > > I think you already said you run the same kernel version, but just to be
> > > sure, I'm running 6.17.5-200.fc42.x86_64.
> >
> > I'm at the point where I'm bisecting compiler flags...
> >
> > I have seen three different outcomes from test-aio:
> >
> > 1. It hangs. This is what I saw in my normal clang build. This configure
> > line seems to be enough to trigger it:
> > ../configure '--target-list=x86_64-softmmu' '--cc=clang' '--cxx=clang++'
> >
> > 2. An assertion failure. I haven't seen this in the actual QEMU tree
> > with clang. With gcc, it seems to happen if you use -O0:
> > ../configure '--target-list=x86_64-softmmu' '--enable-debug'
> >
> > Outside of the QEMU tree with a manual Makefile, I saw this behaviour
> > with clang and -fstack-protector-strong, but without
> > -ftrivial-auto-var-init=zero. Add the latter turns it into the hang.
> >
> > 3. It just passes. This is what I saw in my default gcc build without
> > --enable-debug. The test also passes with --disable-stack-protector
> > added to both configure lines in 1 and 2.
> >
> > Not sure yet where the flags make the difference, but I guess it does
> > hint at something going wrong on the stack.
>
> Ok, that was quite some debugging, but I think I have it. The problem is
> add_timeout_sqe():
>
> static void add_timeout_sqe(AioContext *ctx, int64_t ns)
> {
> struct io_uring_sqe *sqe;
> ts = (struct __kernel_timespec) {
> .tv_sec = ns / NANOSECONDS_PER_SECOND,
> .tv_nsec = ns % NANOSECONDS_PER_SECOND,
> };
>
> sqe = get_sqe(ctx);
> io_uring_prep_timeout(sqe, &ts, 1, 0);
> io_uring_sqe_set_data(sqe, NULL);
> }
>
> What io_uring_prep_timeout() does is that it just stores the ts pointer
> in the SQE, the timeout is never copied anywhere. Obviously, by the time
> that we submit the SQE, ts has been out of scope for a long time, so the
> kernel reads random data as a timeout.
>
> # bpftrace -e 'kfunc:io_timeout { printf("%s: io_timeout %lld s + %lld ns\n", comm, ((struct io_timeout_data *)args.req->async_data)->ts.tv_sec, ((struct io_timeout_data *)args.req->async_data)->ts.tv_nsec ) }'
> Attaching 1 probe...
> test-aio: io_timeout 0 s + 140736377549872 ns
>
> >>> hex(140736377549872)
> '0x7fffbdca7430'
>
> That looked a bit suspicious for a timeout. :-)
>
> After fixing this, we still have the problem that io_uring_enter() can
> return early without failing with EINTR when something like a signal
> arrives. This means that a blocking aio_poll(true) can actually return
> without any progress. Not sure if it matters in practice, but it can
> make test cases fail.
>
> Not completely sure when this happens, though. When running the aio-test
> under strace, kill -CONT makes it return early and fail the assertion,
> but without strace, I can't seem to reproduce the problem at the moment.
> Attaching strace or gdb to the running process that is waiting for the
> timeout also makes it return early and fail the assertion.
Thanks again for debugging the test failures. I am sending a new
revision with the timeout variable lifetime fixed and a stronger loop
condition that retries io_uring_submit_and_wait() when the awaited CQE
is still missing.
Thoughts on what you found:
I've upgraded to Fedora 43 (kernel-6.17.5-300.fc43.x86_64) but I think
the behavior is still the same.
The difference in ptraced vs normal SIGCONT behavior is because the
signal only actually interrupts processes that are stopped or ptraced.
Otherwise SIGCONT disappears without interrupting the system call.
When ptraced, io_uring_enter()'s return value will be 1 and not -EINTR
because of how io_uring/io_uring.c:io_uring_enter() is implemented.
There are two variables: ret is the return value from submitting SQEs
and ret2 is the return value from waiting for CQEs. The first variable
takes precendence, so even though ret2 can be -EINTR, it is overriden:
if (!ret) {
ret = ret2;
...
}
}
out:
...
return ret;
In our case ret is be >0, so ret2 = -EINTR is ignored and the
io_uring_enter(2) return value is 1. This explains the behavior when
SIGCONT is received while a ptraced io_uring_enter(2) is waiting.
I confirmed in userspace that there are no CQEs ready when
io_uring_enter(2) returns 1 after SIGCONT. This can be fixed in
userspace by retrying when the expected CQE is missing.
Regarding aio_poll(ctx, true) returning false: it happens when
aio_notify() is called while aio_poll(ctx, true) is waiting. The tests
rely on the internal details of when exactly false can be returned, so
it will help if fdmon-io_uring.c avoids returning false in situations
where other fdmon implementations wait longer.
Stefan
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2025-11-04 2:09 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-30 15:21 [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 01/13] aio-posix: fix race between io_uring CQE and AioHandler deletion Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 02/13] aio-posix: keep polling enabled with fdmon-io_uring.c Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 03/13] tests/unit: skip test-nested-aio-poll with io_uring Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 04/13] aio-posix: integrate fdmon into glib event loop Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 05/13] aio: remove aio_context_use_g_source() Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 06/13] aio: free AioContext when aio_context_new() fails Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 07/13] aio: add errp argument to aio_context_setup() Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 08/13] aio-posix: gracefully handle io_uring_queue_init() failure Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 09/13] aio-posix: unindent fdmon_io_uring_destroy() Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 10/13] aio-posix: add fdmon_ops->dispatch() Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 11/13] aio-posix: add aio_add_sqe() API for user-defined io_uring requests Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 12/13] block/io_uring: use aio_add_sqe() Stefan Hajnoczi
2025-10-30 15:21 ` [RESEND PATCH v5 13/13] block/io_uring: use non-vectored read/write when possible Stefan Hajnoczi
2025-10-30 18:11 ` [RESEND PATCH v5 00/13] aio: add the aio_add_sqe() io_uring API Kevin Wolf
2025-11-03 10:40 ` Kevin Wolf
2025-11-03 13:30 ` Kevin Wolf
2025-11-03 16:45 ` Stefan Hajnoczi
2025-11-04 2:08 ` Stefan Hajnoczi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).