Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

types: add RecvMsgOut helper structure #192

Merged
merged 2 commits into from
Feb 16, 2023

Conversation

lucab
Copy link
Contributor

@lucab lucab commented Feb 13, 2023

This introduces a RecvMsgOut structure which allows consumers to
safely parse the buffered result from a multishot IORING_OP_RECVMSG
completion event.

@FrankReh
Copy link
Contributor

@lucab This is nice. Thanks for getting to it so quickly. Figuring out the struct being returned and how to test all that isn't a trivial job.

My preference would be for a new opcode: RecvMsgMulti but let @quininer weigh in.

Copy link
Contributor

@FrankReh FrankReh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That test is very nice. If you're sure you don't need to submit and wait for 3, it's fine with me.

Well, I do prefer a new opcode were used to convey this different operation to the user as a few things need to all be done correctly but it does work as you've shown.

io-uring-test/src/tests/net.rs Show resolved Hide resolved
src/types.rs Outdated
return Err(());
}

let header = sys::io_uring_recvmsg_out {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be better to use read_unaligned because the size of io_uring_recvmsg_out may change in future. I can imagine it might use u16 on some platforms.

src/types.rs Outdated
/// `buffer` is the whole buffer previously provided to the ring, while `name_len`
/// and `control_len` are equal to the input lengths provided through `msghdr` in
/// the corresponding SQE.
pub fn parse(buf: &'buf [u8], name_len: u32, control_len: u32) -> Result<Self, ()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I perfer to use msghdr as a parameter instead of its field (like liburing), to avoid user wrong input.

src/types.rs Outdated
let name_start = Self::DATA_START;
let name_size = u32::min(self.header.namelen, self.msghdr_name_len);
let name_end = name_start + name_size as usize;
&self.buf[name_start..name_end]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it would be better to save a reference while parse? splitting it into different methods might make compiler hard to optimize bounds check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some more details about what you are suggesting here?
My feeling is that what you want to do goes against the limit of having self-references within RecvMsgOut (the buffer, plus the subfields pointing within it), but maybe I misunderstood what you have in mind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the topic of performances, I did tweak the logic a bit so that all accessor methods can use unchecked/saturating math.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an immutable reference, there is no self-referencing problem. We also don't need to keep original buf, we can use split_at to split.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, thanks, initially I didn't understand what you were suggesting. I've changed the parsing logic so that the buffer is split upfront and the structure only keeps references to the three specific data slices.

src/types.rs Outdated

/// Message control data, with the same semantics as `msghdr.msg_control`.
pub fn control_data(&self) -> &[u8] {
let control_start = Self::DATA_START + self.msghdr_name_len as usize;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

398L maybe take header.namelen, but use msghdr_name_len unconditionally here, which is somewhat inconsistent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be correct as is, because the fixed length of the name field is msghdr_name_len, while the dynamic length of the name data is the minimum between the field length and the length of the actual result data. In this case we want to skip the whole field, whose length is fixed upfront regardless of the result.

But I agree this may be confusing. I've added a docstring on both msghdr_name_len and msghdr_control_len which hopefully could clarify the semantics.

@lucab lucab force-pushed the ups/recvmsg-multishot branch 2 times, most recently from 84e7323 to 2be3eeb Compare February 14, 2023 18:10
Copy link
Contributor

@FrankReh FrankReh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I didn't go through the unit test with a fine-tooth comb, but it looks like before only better.

let header: sys::io_uring_recvmsg_out =
unsafe { std::ptr::read_unaligned(buffer.as_ptr() as _) };

let msghdr_name_len = msghdr.msg_namelen as _;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to use .into() whenever possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a u32 -> usize conversion, which is always safe to perform on Linux but unfortunately not yet covered in Rust by Into (see rust-lang/rust#70460).

Additionally there are some mismatches across different libc, so that the source fields are u32 on some libc and usize on other libc (so actually not really casting). This is all very unfortunate, but as _ is pretty much a required hack to appease rustc and clippy in a portable way across libc's.

This introduces a `RecvMsgOut` structure which allows consumers to
safely parse the buffered result from a multishot `IORING_OP_RECVMSG`
completion event.
This adds a test entry which exercises `IORING_RECV_MULTISHOT` on
`IORING_OP_RECVMSG`, as well as `RecvMsgOut` parsing logic.
Copy link
Member

@quininer quininer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@FrankReh FrankReh merged commit 6785e66 into tokio-rs:master Feb 16, 2023
@FrankReh
Copy link
Contributor

Thanks everyone!

@FrankReh FrankReh mentioned this pull request Feb 16, 2023
3 tasks
@lucab lucab deleted the ups/recvmsg-multishot branch February 16, 2023 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants