mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
Short writes can happen, too, not just short reads. The difference to
aio=native is that the kernel will actually retry the tail of short
requests internally already -- so it is harder to reproduce. But if the
tail of a short request returns an error to the kernel, we will see it
in userspace still. To reproduce this, apply the following patch on top
of the one shown in HEAD^ (again %s/escaped // to apply):
escaped diff --git a/block/export/fuse.c b/block/export/fuse.c
escaped index 67dc50a412..2b98489a32 100644
escaped --- a/block/export/fuse.c
escaped +++ b/block/export/fuse.c
@@ -1059,8 +1059,15 @@ fuse_co_read(FuseExport *exp, void **bufptr, uint64_t offset, uint32_t size)
int64_t blk_len;
void *buf;
int ret;
+ static uint32_t error_size;
- size = MIN(size, 4096);
+ if (error_size == size) {
+ error_size = 0;
+ return -EIO;
+ } else if (size > 4096) {
+ error_size = size - 4096;
+ size = 4096;
+ }
/* Limited by max_read, should not happen */
if (size > FUSE_MAX_READ_BYTES) {
@@ -1111,8 +1118,15 @@ fuse_co_write(FuseExport *exp, struct fuse_write_out *out,
{
int64_t blk_len;
int ret;
+ static uint32_t error_size;
- size = MIN(size, 4096);
+ if (error_size == size) {
+ error_size = 0;
+ return -EIO;
+ } else if (size > 4096) {
+ error_size = size - 4096;
+ size = 4096;
+ }
QEMU_BUILD_BUG_ON(FUSE_MAX_WRITE_BYTES > BDRV_REQUEST_MAX_BYTES);
/* Limited by max_write, should not happen */
I know this is a bit artificial because to produce this, there must be
an I/O error somewhere anyway, but if it does happen, qemu will
understand it to mean ENOSPC for short writes, which is incorrect. So I
believe we need to resubmit the tail to maybe have it succeed now, or at
least get the correct error code.
Reproducer as before:
$ ./qemu-img create -f raw test.raw 8k
Formatting 'test.raw', fmt=raw size=8192
$ ./qemu-io -f raw -c 'write -P 42 0 8k' test.raw
wrote 8192/8192 bytes at offset 0
8 KiB, 1 ops; 00.00 sec (64.804 MiB/sec and 8294.9003 ops/sec)
$ hexdump -C test.raw
00000000 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a |****************|
*
00002000
$ storage-daemon/qemu-storage-daemon \
--blockdev file,node-name=test,filename=test.raw \
--export fuse,id=exp,node-name=test,mountpoint=test.raw,writable=true
$ ./qemu-io --image-opts -c 'read -P 23 0 8k' \
driver=file,filename=test.raw,cache.direct=on,aio=io_uring
read 8192/8192 bytes at offset 0
8 KiB, 1 ops; 00.00 sec (58.481 MiB/sec and 7485.5342 ops/sec)
$ ./qemu-io --image-opts -c 'write -P 23 0 8k' \
driver=file,filename=test.raw,cache.direct=on,aio=io_uring
write failed: No space left on device
$ hexdump -C test.raw
00000000 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 |................|
*
00001000 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a |****************|
*
00002000
So short reads already work (because there is code for that), but short
writes incorrectly produce ENOSPC. This patch fixes that by
resubmitting not only the tail of short reads but short writes also.
(And this patch uses the opportunity to make it so qemu_iovec_destroy()
is called only if req->resubmit_qiov.iov is non-NULL. Functionally a
non-op, but this is how the code generally checks whether the
resubmit_qiov has been set up or not.)
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
Message-ID: <20260324084338.37453-4-hreitz@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
233 lines
7.0 KiB
C
233 lines
7.0 KiB
C
/*
|
|
* Linux io_uring support.
|
|
*
|
|
* Copyright (C) 2009 IBM, Corp.
|
|
* Copyright (C) 2009 Red Hat, Inc.
|
|
* Copyright (C) 2019 Aarushi Mehta
|
|
*
|
|
* 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 <liburing.h>
|
|
#include "qemu/aio.h"
|
|
#include "block/block.h"
|
|
#include "block/raw-aio.h"
|
|
#include "qemu/coroutine.h"
|
|
#include "system/block-backend.h"
|
|
#include "trace.h"
|
|
|
|
typedef struct {
|
|
Coroutine *co;
|
|
QEMUIOVector *qiov;
|
|
uint64_t offset;
|
|
ssize_t ret;
|
|
int type;
|
|
int fd;
|
|
BdrvRequestFlags flags;
|
|
|
|
/*
|
|
* Short reads/writes require resubmission, see
|
|
* luring_resubmit_short_io().
|
|
*/
|
|
int total_done;
|
|
QEMUIOVector resubmit_qiov;
|
|
|
|
CqeHandler cqe_handler;
|
|
} LuringRequest;
|
|
|
|
static void luring_prep_sqe(struct io_uring_sqe *sqe, void *opaque)
|
|
{
|
|
LuringRequest *req = opaque;
|
|
QEMUIOVector *qiov = req->qiov;
|
|
uint64_t offset = req->offset + req->total_done;
|
|
int fd = req->fd;
|
|
BdrvRequestFlags flags = req->flags;
|
|
|
|
if (req->resubmit_qiov.iov) {
|
|
qiov = &req->resubmit_qiov;
|
|
}
|
|
|
|
switch (req->type) {
|
|
case QEMU_AIO_WRITE:
|
|
{
|
|
int luring_flags = (flags & BDRV_REQ_FUA) ? RWF_DSYNC : 0;
|
|
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
|
|
/*
|
|
* 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;
|
|
case QEMU_AIO_READ:
|
|
{
|
|
if (qiov->niov > 1) {
|
|
io_uring_prep_readv(sqe, fd, qiov->iov, qiov->niov, offset);
|
|
} 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);
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* luring_resubmit_short_io:
|
|
*
|
|
* Short reads and writes are rare but may occur. The remaining request needs
|
|
* to be resubmitted.
|
|
*
|
|
* For example, short reads can be reproduced by a FUSE export deliberately
|
|
* executing short reads. The tail of short writes is generally resubmitted by
|
|
* io-uring in the kernel, but if that resubmission encounters an I/O error, the
|
|
* already submitted portion will be returned as a short write.
|
|
*/
|
|
static void luring_resubmit_short_io(LuringRequest *req, int ndone)
|
|
{
|
|
QEMUIOVector *resubmit_qiov;
|
|
size_t remaining;
|
|
|
|
trace_luring_resubmit_short_io(req, ndone);
|
|
|
|
/* Update I/O position */
|
|
req->total_done += ndone;
|
|
remaining = req->qiov->size - req->total_done;
|
|
|
|
/* Shorten qiov */
|
|
resubmit_qiov = &req->resubmit_qiov;
|
|
if (resubmit_qiov->iov == NULL) {
|
|
qemu_iovec_init(resubmit_qiov, req->qiov->niov);
|
|
} else {
|
|
qemu_iovec_reset(resubmit_qiov);
|
|
}
|
|
qemu_iovec_concat(resubmit_qiov, req->qiov, req->total_done, remaining);
|
|
|
|
aio_add_sqe(luring_prep_sqe, req, &req->cqe_handler);
|
|
}
|
|
|
|
static void luring_cqe_handler(CqeHandler *cqe_handler)
|
|
{
|
|
LuringRequest *req = container_of(cqe_handler, LuringRequest, cqe_handler);
|
|
int ret = cqe_handler->cqe.res;
|
|
|
|
trace_luring_cqe_handler(req, ret);
|
|
|
|
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;
|
|
}
|
|
} else if (req->qiov) {
|
|
/* total_done is non-zero only for resubmitted requests */
|
|
int total_bytes = ret + req->total_done;
|
|
|
|
if (total_bytes == req->qiov->size) {
|
|
ret = 0;
|
|
} else if (ret > 0 && (req->type == QEMU_AIO_READ ||
|
|
req->type == QEMU_AIO_WRITE)) {
|
|
/* Short Read/Write */
|
|
luring_resubmit_short_io(req, ret);
|
|
return;
|
|
} else if (req->type == QEMU_AIO_READ) {
|
|
/* Read ret == 0: EOF, pad with zeroes */
|
|
qemu_iovec_memset(req->qiov, total_bytes, 0,
|
|
req->qiov->size - total_bytes);
|
|
ret = 0;
|
|
} else {
|
|
/*
|
|
* Normal write ret == 0 means ENOSPC.
|
|
* For zone-append, we treat any 0 <= ret < qiov->size as ENOSPC,
|
|
* too, because resubmitting the tail seems a little unsafe.
|
|
*/
|
|
ret = -ENOSPC;
|
|
}
|
|
}
|
|
|
|
req->ret = ret;
|
|
if (req->resubmit_qiov.iov) {
|
|
qemu_iovec_destroy(&req->resubmit_qiov);
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
int coroutine_fn luring_co_submit(BlockDriverState *bs, int fd,
|
|
uint64_t offset, QEMUIOVector *qiov,
|
|
int type, BdrvRequestFlags flags)
|
|
{
|
|
LuringRequest req = {
|
|
.co = qemu_coroutine_self(),
|
|
.qiov = qiov,
|
|
.ret = -EINPROGRESS,
|
|
.type = type,
|
|
.fd = fd,
|
|
.offset = offset,
|
|
.flags = flags,
|
|
};
|
|
|
|
req.cqe_handler.cb = luring_cqe_handler;
|
|
|
|
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 req.ret;
|
|
}
|
|
|
|
bool luring_has_fua(void)
|
|
{
|
|
#ifdef HAVE_IO_URING_PREP_WRITEV2
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|