Skip to content

Add sendmmsg #2

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Add sendmmsg #2

wants to merge 1 commit into from

Conversation

Hasan6979
Copy link
Collaborator

@Hasan6979 Hasan6979 commented May 22, 2025

This PR adds support in socket2 crate for *mmsg syscalls which would allow to send/recv multiple messages in a single syscall.

Since there already is a draft PR on the same issue in socket2 repo of someone else, hence it doesn't make sense to add another one there. Not sure when that PR would be merged into main, hence for now keeping a separate repo to further neptun improvement optimizations.

Copy link

@jjanowsk jjanowsk left a comment

Choose a reason for hiding this comment

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

+1

macro_rules! define_mmsg_if_supported {
($($item:item)*) => {
$(
#[cfg(all(

Choose a reason for hiding this comment

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

all() is not needed here.

@@ -172,6 +172,20 @@ macro_rules! man_links {
};
}

macro_rules! define_mmsg_if_supported {

Choose a reason for hiding this comment

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

Sorry to be a nag about macros here, but is it needed at all? From the looks of it, it replaces the #[cfg()] directives but that looks not a standard thing in Rust(we just sprinkle cfg() around the blocks we need usually

}

/// Number of received bytes.
pub fn recieved_bytes(&self) -> u32 {

Choose a reason for hiding this comment

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

Suggested change
pub fn recieved_bytes(&self) -> u32 {
pub fn received_bytes(&self) -> u32 {

Choose a reason for hiding this comment

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

By the way, this method is duplicated by transmitted_bytes on non-mutable implementation, maybe worth to extract such methods to the base impl?


assert!(recvd == sent);

assert!(recv_batched_msgs[0].recieved_bytes() == DATA.len().try_into().unwrap());

Choose a reason for hiding this comment

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

The testcase makes sense, I just have few questions/suggestions but they can be argued with:

  • what happens if the target value is not 10, but 1,000,000 - where is the upper bound defined, can it be retrieved via API or else how do we decide the batch size?
  • The value 20 for the recv_batched_msgs is used so it would store the "Hello World!" but I wonder - can we pass an exact len of that string(13bytes) in there? Basically to leave no gaps in the usage and make it very clear that the buffer needs to be just big enough to hold all the data so no misinterpretation would happen(I find test code very useful to understand the usage of code).
  • Can we annotate the sendmmsg() along the lines that This emits all the messages in a single syscall and same for recvmmsg()?

That's all!

pub struct MMsgHdr<'addr, 'bufs, 'control> {
inner: sys::mmsghdr,
#[allow(clippy::type_complexity)]
_lifetimes: PhantomData<(&'addr SockAddr, &'bufs IoSlice<'bufs>, &'control [u8])>,

Choose a reason for hiding this comment

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

😅 I have hard time understanding that but I can match it to rust-lang#583 which may have been used as a reference but.. could you explain it a bit?

Copy link

@jjanowsk jjanowsk Jun 3, 2025

Choose a reason for hiding this comment

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

You use PantomData to tell the compiler that you hold some data even if you don't see it directly in the code. In that case mmsghdr holds msghdr which inside keeps pointers to the address, buffers and and control buffer. This way you tell the compiler that those 3 things must outlive MsgHdr and MMsgHdr even though you don't hold rust references directly.
For example this is UB:

use libc;

struct MsgHdr {
    inner: libc::msghdr
}

impl MsgHdr {
    pub fn new() -> Self {
        unsafe { std::mem::zeroed() }
    }
    
    pub fn with_control(mut self, buf: &[u8]) -> Self {
        self.inner.msg_control = buf.as_ptr() as *mut _;
        self.inner.msg_controllen = buf.len() as _;
        self
    }
}

fn foo(_m: &MsgHdr) {
    
}

fn main() {
    let ctrl = Box::new([0; 6]);
    let m = MsgHdr::new().with_control(ctrl.as_ref());
    drop(ctrl);
    // UB! _m still holds pointer to control even though it has been released.
    foo(&m)
}

With PhantomData this will not compile:

use libc;

struct MsgHdr<'control>  {
    inner: libc::msghdr,
    _control: std::marker::PhantomData<&'control [u8]>
}

impl<'control> MsgHdr<'control> {
    pub fn new() -> Self {
        unsafe { std::mem::zeroed() }
    }
    
    pub fn with_control(mut self, buf: &'control [u8]) -> Self {
        self.inner.msg_control = buf.as_ptr() as *mut _;
        self.inner.msg_controllen = buf.len() as _;
        self
    }
}

fn foo(_m: &MsgHdr) {
    
}

fn main() {
    let ctrl = Box::new([0; 6]);
    let m = MsgHdr::new().with_control(ctrl.as_ref());
    drop(ctrl);
    foo(&m)
}

You will get:

error[E0505]: cannot move out of `ctrl` because it is borrowed
  --> src/main.rs:27:10
   |
25 |     let ctrl = Box::new([0; 6]);
   |         ---- binding `ctrl` declared here
26 |     let m = MsgHdr::new().with_control(ctrl.as_ref());
   |                                        ---- borrow of `ctrl` occurs here
27 |     drop(ctrl);
   |          ^^^^ move out of `ctrl` occurs here
28 |     foo(&m)
   |         -- borrow later used here
   |

Without PhantomData you could just store the reference to the thing it holds, but then your type keeps unnecessary 8 bytes per reference or 16 bytes per slice reference for no reason.
This is example without PhantomData. MsgHdr is 72 bytes instead of 56:

use libc;

struct MsgHdr<'control>  {
    inner: libc::msghdr,
    _control: std::mem::MaybeUninit<&'control [u8]>
}

impl<'control> MsgHdr<'control> {
    pub fn new() -> Self {
        Self {
            inner: unsafe { std::mem::zeroed() },
            _control: std::mem::MaybeUninit::uninit(),
        }
        
    }

    pub fn with_control(mut self, buf: &'control [u8]) -> Self {
        self.inner.msg_control = buf.as_ptr() as *mut _;
        self.inner.msg_controllen = buf.len() as _;
        self
    }
}

fn foo(_m: &MsgHdr) {
    
}

fn main() {
    let ctrl = Box::new([0; 6]);
    let m = MsgHdr::new().with_control(ctrl.as_ref());
    drop(ctrl);
    foo(&m);
}

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