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

Iox 36 untyped api #58

Merged
merged 12 commits into from Jul 24, 2022
Merged

Iox 36 untyped api #58

merged 12 commits into from Jul 24, 2022

Conversation

elBoberido
Copy link
Member

@elBoberido elBoberido commented Jul 15, 2022

Pre-Review Checklist for the PR Author

  1. Code follows the Rust coding style and is formatted with rustfmt
  2. Branch follows the naming format (iox-123-this-is-a-branch)
  3. Commits messages are according to this guideline
  4. Update the PR title
    • Follow the same conventions as for commit messages
    • Link to the relevant issue
  5. Relevant issues are linked
  6. Add sensible notes for the reviewer
  7. All checks have passed (except task-list-completed)
  8. Assign PR to reviewer

Notes for Reviewer

Checklist for the PR Reviewer

  • Commits are properly organized and messages are according to the guideline
  • Code according to our coding style and naming conventions
  • PR title describes the changes

Post-review Checklist for the PR Author

  1. All open points are addressed and tracked via issues

References

Comment on lines 21 to 40
let mut sample = publisher.loan(std::mem::size_of::<u32>(), std::mem::align_of::<u32>())?;
sample.as_mut().put_u32(counter);
publisher.publish(sample);
Copy link
Member Author

Choose a reason for hiding this comment

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

@neilisaac does this fit your use case?

I think I will introduce a loan_uninitialized and let loan zero the memory.

Choose a reason for hiding this comment

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

Yes, this looks great!

For reference, what I had was:

pub struct PubChunkHandle<'a> {
    data: *mut ffi::c_void,
    publisher: &'a Publisher,
    sent: bool,  // used in Drop
    size: usize,
}

impl PubChunkHandle<'_> {
    /// Send the data via the associated Publisher
    pub fn publish(mut self) {
        unsafe { sys::iox_pub_publish_chunk(self.publisher.0, self.data) };
        self.sent = true;
    }

    /// Safe interface to write into a shared memory chunk.
    pub fn as_uninit_slice(&mut self) -> &mut UninitSlice {
        unsafe { UninitSlice::from_raw_parts_mut(self.data.cast(), self.size) }
    }

    /// Expose the payload portion of the chunk as a mutable byte slice.
    /// This can be written to conveniently using bytes::BufMut.
    /// # Safety
    /// Despite this function not being marked as unsafe, the data returned by as_slice is uninitialized.
    pub fn as_slice(&mut self) -> &mut [u8] {
        unsafe { std::slice::from_raw_parts_mut(self.data.cast(), self.size) }
    }

    /// Cast the chunk to a given type T.
    /// This may be useful with a #[repr(C)] type for example.
    /// # Safety
    /// This function is marked as unsafe because it is the caller's responsibility
    /// to ensure that T can safely be written into the chunk.
    /// T must fit in the allocated chunk and must not reference data outside the chunk (ex. via pointers/references)
    pub unsafe fn as_uninitialized<T>(&mut self) -> &mut std::mem::MaybeUninit<T> {
        &mut *self.data.cast()
    }
}

The main design difference there I think is the PubChunkHandle borrows the Publisher, but otherwise very similar.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice. Currently I plan to have this methods

pub fn loan_zeroed_slice(&self, len: usize) -> Result<SampleMut<[u8]>, IceoryxError> {
    unimplemented!()
}

pub fn loan_aligned_zeroed_slice(&self, len: usize, alignment: usize) -> Result<SampleMut<[u8]>, IceoryxError> {
    unimplemented!()
}

pub fn loan_uninit_slice(&self, len: usize) -> Result<SampleMut<[MaybeUninit<u8>]>, IceoryxError> {
    unimplemented!()
}

pub fn loan_aligned_uninit_slice(&self, len: usize, alignment: usize) -> Result<SampleMut<[MaybeUninit<u8>]>, IceoryxError> {
    unimplemented!()
}

I have to check the ergonomics with [MaybeUninit<u8>]. Maybe this needs to be done in a different way.

It would also be possible to not restrict the API to [u8] slices but to allow all types with T: ShmSend. With this one can have dynamic data at runtime. At least the most simple one.

@elfenpiff what do you think about having a specialization Sample<cxx::span<T>> for the typed C++ API?

@@ -10,15 +10,15 @@ use crate::IceoryxError;
use std::marker::PhantomData;
use std::mem::MaybeUninit;

pub struct PublisherBuilder<'a, T: ShmSend> {
pub struct PublisherBuilder<'a, T: ShmSend + ?Sized> {

Choose a reason for hiding this comment

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

is ?Sized accepted in a trait declaration ex. pub unsafe trait ShmSend: ?Sized {}, or is it special because it's an anti-constraint?

Copy link
Member Author

Choose a reason for hiding this comment

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

traits are ?Sized by default and one would have to set Sized as trait bounds. But since I want to have [u8] as type I cannot do it.

Comment on lines 21 to 40
let mut sample = publisher.loan(std::mem::size_of::<u32>(), std::mem::align_of::<u32>())?;
sample.as_mut().put_u32(counter);
publisher.publish(sample);

Choose a reason for hiding this comment

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

Yes, this looks great!

For reference, what I had was:

pub struct PubChunkHandle<'a> {
    data: *mut ffi::c_void,
    publisher: &'a Publisher,
    sent: bool,  // used in Drop
    size: usize,
}

impl PubChunkHandle<'_> {
    /// Send the data via the associated Publisher
    pub fn publish(mut self) {
        unsafe { sys::iox_pub_publish_chunk(self.publisher.0, self.data) };
        self.sent = true;
    }

    /// Safe interface to write into a shared memory chunk.
    pub fn as_uninit_slice(&mut self) -> &mut UninitSlice {
        unsafe { UninitSlice::from_raw_parts_mut(self.data.cast(), self.size) }
    }

    /// Expose the payload portion of the chunk as a mutable byte slice.
    /// This can be written to conveniently using bytes::BufMut.
    /// # Safety
    /// Despite this function not being marked as unsafe, the data returned by as_slice is uninitialized.
    pub fn as_slice(&mut self) -> &mut [u8] {
        unsafe { std::slice::from_raw_parts_mut(self.data.cast(), self.size) }
    }

    /// Cast the chunk to a given type T.
    /// This may be useful with a #[repr(C)] type for example.
    /// # Safety
    /// This function is marked as unsafe because it is the caller's responsibility
    /// to ensure that T can safely be written into the chunk.
    /// T must fit in the allocated chunk and must not reference data outside the chunk (ex. via pointers/references)
    pub unsafe fn as_uninitialized<T>(&mut self) -> &mut std::mem::MaybeUninit<T> {
        &mut *self.data.cast()
    }
}

The main design difference there I think is the PubChunkHandle borrows the Publisher, but otherwise very similar.

@codecov-commenter
Copy link

codecov-commenter commented Jul 15, 2022

Codecov Report

Merging #58 (1e0284e) into master (a37117a) will increase coverage by 3.73%.
The diff coverage is 78.57%.

@@            Coverage Diff             @@
##           master      #58      +/-   ##
==========================================
+ Coverage   51.29%   55.02%   +3.73%     
==========================================
  Files          19       20       +1     
  Lines         774      945     +171     
==========================================
+ Hits          397      520     +123     
- Misses        377      425      +48     
Impacted Files Coverage Δ
src/error.rs 0.00% <ø> (ø)
src/introspection/memory.rs 0.00% <ø> (ø)
src/introspection/port.rs 0.00% <ø> (ø)
src/introspection/process.rs 0.00% <ø> (ø)
src/subscriber.rs 47.22% <ø> (ø)
src/sample_mut.rs 70.00% <65.38%> (-17.50%) ⬇️
src/sample.rs 59.40% <69.56%> (+4.40%) ⬆️
src/publisher.rs 62.75% <76.78%> (+3.35%) ⬆️
iceoryx-sys/src/publisher.rs 81.96% <95.12%> (+1.57%) ⬆️
iceoryx-sys/src/chunk_header.rs 95.23% <95.23%> (ø)
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a37117a...1e0284e. Read the comment docs.

Comment on lines 21 to 40
let sample = if counter % 2 == 1 {
// with initialized slice
let mut sample = publisher.loan_slice(std::mem::size_of::<u32>())?;
sample.as_mut().put_u32(counter);
sample
} else {
// with uninitialized slice
let sample = publisher.loan_uninit_slice(std::mem::size_of::<u32>())?;
unsafe {
let mut sample = sample.assume_init();
sample.as_mut().put_u32(counter);
sample
}
};
publisher.publish(sample);
Copy link
Member Author

Choose a reason for hiding this comment

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

@neilisaac what is your opinion? Maybe a bit awkward to call assume_init before the buffer is written but not that bad.

Copy link
Member Author

Choose a reason for hiding this comment

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

An alternative would be

let sample = publisher.loan_uninit_slice(std::mem::size_of::<u32>())?;
let sample = unsafe {
    sample.slice_assume_init_mut().put_u32(counter);
    sample.assume_init()
}
publisher.publish(sample);

Comment on lines 21 to 40
let sample = if counter % 2 == 1 {
// with initialized slice
let mut sample = publisher.loan_slice_with_alignment(
std::mem::size_of::<u32>(),
std::mem::align_of::<u32>(),
)?;
sample.as_mut().put_u32_le(counter);
sample
} else {
// with uninitialized slice
let mut sample = publisher.loan_uninit_slice_with_alignment(
std::mem::size_of::<u32>(),
std::mem::align_of::<u32>(),
)?;
unsafe {
sample.slice_assume_init_mut().put_u32_le(counter);
sample.assume_init()
}
};
publisher.publish(sample);
Copy link
Member Author

Choose a reason for hiding this comment

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

@neilisaac if there are no objections from you, I'll go with this API for the publisher

Comment on lines 17 to 28
let (subscriber, sample_receive_token) =
SubscriberBuilder::<[u8]>::new("Radar", "FrontLeft", "Counter")
.queue_capacity(5)
.create()?;

let sample_receiver = subscriber.get_sample_receiver(sample_receive_token);

loop {
if sample_receiver.has_data() {
while let Some(sample) = sample_receiver.take() {
println!("Receiving: {}", sample.as_ref().get_u32_le());
}
Copy link
Member Author

Choose a reason for hiding this comment

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

and with this for the subscriber

@elBoberido elBoberido marked this pull request as ready for review July 17, 2022 17:57
)?;
unsafe {
sample.slice_assume_init_mut().put_u32_le(counter);
sample.assume_init()

Choose a reason for hiding this comment

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

loan_slice_with_alignment seems sensible.

loan_uninit_slice_with_alignment doesn't seem to really provide a significant distinction here compared to loan_slice_with_alignment, since loan_slice_with_alignment returns an uninitialized slice without requiring unsafe handling.


The way I did it was also a little loose about initialization safety since it's always up to the user to populate the buffer with something meaningful before publishing regardless of how it's abstracted.

The publisher could loan a PubChunkHandle which owns the data and exposes different views for convenience (depending on how the user wants to write into the buffer). Regardless of which method is used, the PubChunkHandle owns the data and can publish it at will.

impl Publisher {
    pub fn loan_chunk(&self, size: usize) -> Result<PubChunkHandle, AllocationError>
}

pub struct PubChunkHandle<'a> {
    data: *mut ffi::c_void,
    publisher: &'a Publisher,
    sent: bool,
    size: usize,
}

impl PubChunkHandle<'_> {
    pub fn publish(mut self) 
    pub fn as_uninit_slice(&mut self) -> &mut UninitSlice 
    pub fn as_slice(&mut self) -> &mut [u8] 
    pub unsafe fn as_uninitialized<T>(&mut self) -> &mut std::mem::MaybeUninit<T> 
}

The last one is probably a bit sketchy since the user data could probably overflow, and it's easy to accidentally use a type the contains pointers.

Copy link
Member Author

Choose a reason for hiding this comment

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

@neilisaac I think you overlooked the .fill_with(|| T::default()); in loan_slice_with_alignment which initializes the slice. loan_uninit_slice_with_alignment doesn't do this but is unsafe. I tried to match the proposed API for uninitialized constructors of Box, Rc, and Arc.

I like the idea to expose a different view and will add this for [u8]

unsafe fn try_as_uninit<T: ShmSend>(&mut self) -> Option<&mut MaybeUninit<T>>

This would return None if the size and alignment do not match the requirements for T.

Copy link
Member Author

Choose a reason for hiding this comment

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

@neilisaac I added the convenience functions and if you do not have any concerns, I would merge the PR

Copy link

@neilisaac neilisaac left a comment

Choose a reason for hiding this comment

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

API looks good to me!

@elBoberido elBoberido merged commit ff36d1e into master Jul 24, 2022
@elBoberido elBoberido deleted the iox-36-untyped-API branch July 24, 2022 19:37
@elBoberido
Copy link
Member Author

@neilisaac I'm currently implementing the reactor pattern for the WaitSet/Listener. If you are interested, I can ping you once I have a draft PR.

@elBoberido elBoberido self-assigned this Jul 24, 2022
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.

Create an untyped API that works on [u8] slices
3 participants