-
When I cancel a sqe, I usually want to free the buffers used by this sqe. This is rather critical in languages which applies RAII pattern. Following pseudo code is written in C++ because C++ has destructors. io_uring ring;
struct async_read {
async_read(int fd) {
buf = (char *)malloc(512);
io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, 512, 0);
io_uring_sqe_set_data(sqe, this);
io_uring_submit(&ring);
complete = false;
}
// destructor, will be called when async_read instance runs out of scope, or an exception is thrown
~async_read() {
if (!complete) {
io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_cancel(sqe, this, 0);
io_uring_submit(&ring);
// How can I make sure io_uring_prep_read is canceled or finished here?
}
free(buf);
}
char* buf;
bool complete;
}; I must make sure that all resources allocated in constructors be freed before destructors return. If the destructor is called when the async_read task is still pending, I must make sure that the task is settled ( completed or errored / canceled ) before freeing the buffer or use-after-free will happen.
My questions:
BTW: I always think that cancelation should not be async. It should either a seperate blocking syscall, or an op code for existing blocking syscall ( for example |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 19 replies
-
A CQE is always generated by the time the cancel request has been submitted, that part isn't async at all. But as noted, it may indeed return For all cases, you cannot rely on cancelation until the request targeted for cancelation has posted the CQE associated with it. For cancelations that return If you need sync cancelation, wrap it in something that submits the cancelation request for a specific user_data and then also waits for the completion event of that user_data to be posted? That would be one way to add a sync cancelation interface on top of what io_uring presents. io_uring doesn't provide any way to do that, and I don't think it should as you can implement it on top of the existing interface. |
Beta Was this translation helpful? Give feedback.
-
How can I do that? Only I can come up with is an ugly io_uring_submit - for_each_cqe loop. What if CQ is full? It is way too hard to handle despite that all these problems wont exist if it is a blocking call |
Beta Was this translation helpful? Give feedback.
-
Great! We should better write it in the man page. It is very useful in certain cases |
Beta Was this translation helpful? Give feedback.
-
You don't necessarily need to reap the particular event, you just need to know if it arrived or not. But yes, it does depend on how you handle completions. If another thread does that, it becomes more complicated and you'd have to communicate with that. CQ full is an application thing, there's nothing the kernel can do about that. That said, it would be possible to implement sync cancelations in the kernel, but it'd be overhead added to basically everything. I'm not so sure this is worth the investment in time and added overhead, to be honest. When we find a request that would cause us to return -EALREADY, we could flag it as such and check for this flag when the completion is posted. That posting would then trigger the sync cancelation request to finish. This might be more appropriately done through not an SQE for cancelation, but an io_uring_register() method that requests cancelation based on similar criteria as the SQE based one. That seems more natural than using an SQE for sync cancelation, exactly because of the same situation where it's hard to wait on a specific CQE rather than just "any CQE" being posted. |
Beta Was this translation helpful? Give feedback.
-
Yes, I think that's possible and I've been thinking about a way to do it that is basically free. Here's the idea:
With that approach, we have all the bits needed for a sync cancelation API through |
Beta Was this translation helpful? Give feedback.
-
Some easy code for liburing commit 0dc983591c2c1bd77ec952d9ebe2a0dc7618a828
Author: 李通洲 <[email protected]>
Date: Sun Jun 19 01:46:30 2022 +0800
[WIP] Add io_uring_register_sync_cancel()
Signed-off-by: 李通洲 <[email protected]>
diff --git a/man/io_uring_register.2 b/man/io_uring_register.2
index 1e91caf..1f4517f 100644
--- a/man/io_uring_register.2
+++ b/man/io_uring_register.2
@@ -5,7 +5,7 @@
.\"
.TH IO_URING_REGISTER 2 2019-01-17 "Linux" "Linux Programmer's Manual"
.SH NAME
-io_uring_register \- register files or user buffers for asynchronous I/O
+io_uring_register \- register files or user buffers for asynchronous I/O
.SH SYNOPSIS
.nf
.BR "#include <linux/io_uring.h>"
@@ -692,3 +692,8 @@ undergoing file or buffer registration, or is being torn down.
.TP
.B EOPNOTSUPP
User buffers point to file-backed memory.
+
+.TP
+.B IORING_REGISTER_SYNC_CANCEL
+
+Available since 5.20.
diff --git a/man/io_uring_register_sync_cancel.3 b/man/io_uring_register_sync_cancel.3
new file mode 100644
index 0000000..d78b85a
--- /dev/null
+++ b/man/io_uring_register_sync_cancel.3
@@ -0,0 +1,21 @@
+.\" Copyright (C) 2022 Jens Axboe <[email protected]>
+.\"
+.\" SPDX-License-Identifier: LGPL-2.0-or-later
+.\"
+.TH io_uring_register_sync_cancel 3 "June 19, 2022" "liburing-2.1" "liburing Manual"
+.SH NAME
+io_uring_register_sync_cancel \- cancel requests synchronously
+.SH SYNOPSIS
+.nf
+.B #include <liburing.h>
+.PP
+.BI "int io_uring_register_sync_cancel(struct io_uring *" ring ","
+.BI " struct io_uring_sync_cancel_reg *" reg ");"
+.fi
+.SH DESCRIPTION
+.PP
+
+.SH RETURN VALUE
+
+.SH SEE ALSO
+.BR io_uring_prep_cancel (3)
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h
index 2f391c9..34a49cc 100644
--- a/src/include/liburing/io_uring.h
+++ b/src/include/liburing/io_uring.h
@@ -411,6 +411,9 @@ enum {
IORING_REGISTER_PBUF_RING = 22,
IORING_UNREGISTER_PBUF_RING = 23,
+ /* sync cancelation API */
+ IORING_REGISTER_SYNC_CANCEL = 24,
+
/* this goes last */
IORING_REGISTER_LAST
};
@@ -520,6 +523,15 @@ struct io_uring_buf_reg {
__u64 resv[3];
};
+/* argument for IORING_REGISTER_SYNC_CANCEL */
+struct io_uring_sync_cancel_reg {
+ __u64 addr;
+ __s32 fd;
+ __u32 flags;
+ struct __kernel_timespec timeout;
+ __u64 pad[2];
+};
+
/*
* io_uring_restriction->opcode values
*/
diff --git a/src/register.c b/src/register.c
index 993c450..f2b1026 100644
--- a/src/register.c
+++ b/src/register.c
@@ -345,3 +345,10 @@ int io_uring_unregister_buf_ring(struct io_uring *ring, int bgid)
return ____sys_io_uring_register(ring->ring_fd,
IORING_UNREGISTER_PBUF_RING, ®, 1);
}
+
+int io_uring_register_sync_cancel(struct io_uring *ring,
+ struct io_uring_sync_cancel_reg *reg)
+{
+ return ____sys_io_uring_register(ring->ring_fd,
+ IORING_REGISTER_SYNC_CANCEL, reg, 1);
+} It's about 2am in my timezone. I'm going to go to bed. See you tomorrow. |
Beta Was this translation helpful? Give feedback.
Yes, I think that's possible and I've been thinking about a way to do it that is basically free. Here's the idea:
0
or-ENOENT
, these are already sync. Doing that offIORING_REGISTER_CANCEL
would work the exact same way and no changes are needed.-EALREADY
, if we get that return, then we need to add ourselves to the completion wait queue. That one is woken every time a completion posts, if it's active. Then it's simply the case of retrying the cancelation every time we get woken until we get-ENOENT
. Once we do, the request is no longer active and we can return.With that approach, we have all the bits needed for a sync cancelation API through
io_uring_regi…