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

Add Safe Abstractions for FuriStreamBuffer #177

Merged
merged 17 commits into from
Nov 14, 2024

Conversation

cptpiepmatz
Copy link
Contributor

Hey 👋🏻,
In this PR, I added some safe abstractions for the FuriStreamBuffer.

While working on it, I came across this note in the stream buffer implementation:

 * ***NOTE***: Stream buffer implementation assumes there is only one task or
 * interrupt that will write to the buffer (the writer), and only one task or
 * interrupt that will read from the buffer (the reader).

So, I figured, why not enforce this constraint in the type system? The result is a split into a sender and receiver, similar to the mpsc channel in the standard library. They implement Send, so they can be moved between threads, but not Clone or Sync, making sure we only have one of each in a pair. Unfortunately, I had to use Arc to give ownership to both sides, which means this requires alloc. Other than that, I’m happy with how the API turned out.

I also added an example where data is sent between different threads.

Copy link
Contributor

@JarvisCraft JarvisCraft left a comment

Choose a reason for hiding this comment

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

The API looks great. Here is the first iteration of nitpicking, but generally the PR looks super accurate

crates/flipperzero/src/furi/stream_buffer.rs Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
Comment on lines 7 to 8
#[cfg(feature = "alloc")]
pub mod stream_buffer;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure about the following point, but I would prefer a separate stream-buffer feature which depends on alloc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It feels really bloated to a feature just because of the Arc I used. I could replace it with some custom counting logic using an AtomicUsize and sys::malloc to get a pointer but I'm not sure if that is better.

crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
}
}

unsafe impl Send for Receiver {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

@JarvisCraft
Copy link
Contributor

It will take me some time to think on the Arc-related part, but it looks good for the start

Copy link
Collaborator

@dcoles dcoles left a comment

Choose a reason for hiding this comment

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

Hi @cptpiepmatz!

Thanks for the PR! I really like the Sender / Receiver concept and how extensively you've documented everything 💖.

I think it would be useful to expose the low-level StreamBuffer API for no-alloc cases, but that's something we can certainly expose after merging this PR. Vast majority of comments are just little things for consistency with the other parts of the flipperzero-rs API.

crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
///
/// In an interrupt routine, this method behaves like [`send`](Self::send).
pub fn send_with_timeout(&self, data: &[u8], timeout: core::time::Duration) -> usize {
let timeout = sys::furi::duration_to_ticks(timeout);
Copy link
Collaborator

Choose a reason for hiding this comment

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

For the furi APIs we generally use furi::Duration rather than core::time::Duration to provide tick-level control over the timeout**.

(** Though we should really implement From/TryFrom<core::time::Duration> for furi::Duration)

crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
crates/flipperzero/src/furi/stream_buffer.rs Outdated Show resolved Hide resolved
@cptpiepmatz
Copy link
Contributor Author

cptpiepmatz commented Oct 20, 2024

I made the StreamBuffer public with all of its methods, the send and receive methods are unsafe as they need to uphold the guarantee that only writer and one reader can exists at a time.

The Sender and Receiver is moved in the private stream module. I re-exported the entire module but that allowed to just mark it as #[cfg(feature = "alloc")]. For that purpose I also removed the stream-buffer feature again. I like this setup now, you can use the StreamBuffer without alloc but if you don't want to manage send and receive manually, you can use Sender and Receiver by enabling the alloc feature.

Since the StreamBuffer itself is now public, I added some methods on the Sender and Receiver to access the StreamBuffer and removed all the redirect methods. I also heavily updated the documentation to incorporate the new API.

@cptpiepmatz cptpiepmatz requested a review from dcoles October 20, 2024 12:11
@dcoles
Copy link
Collaborator

dcoles commented Oct 21, 2024

Hi @cptpiepmatz. This looks great! I'd be happy to merge this once @JarvisCraft has taken a final look.

@cptpiepmatz
Copy link
Contributor Author

@dcoles could you please approve the workflow again? I added a SetTriggerLevelError to make clippy happy

@cptpiepmatz
Copy link
Contributor Author

@JarvisCraft is this ready to merge?

@JarvisCraft
Copy link
Contributor

@JarvisCraft is this ready to merge?

Hi there, apologies for the long reply. I will take a look at the PR this week.

Copy link
Contributor

@JarvisCraft JarvisCraft left a comment

Choose a reason for hiding this comment

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

Apologies for the delay on code-review.

The PR looks amazing and the detailed docs are just the chef's kiss.

Thanks a lot for your contribution <3

#[cfg_attr(feature = "alloc", doc = "[`into_stream`](Self::into_stream)")]
/// which is available using the `alloc` feature.
pub fn new(size: NonZeroUsize, trigger_level: usize) -> Self {
let size: usize = size.into();
Copy link
Contributor

Choose a reason for hiding this comment

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

Non-blocker nit (ptobably, for the future changes): given that type annotation is already used, from seems more appropriate:

Suggested change
let size: usize = size.into();
let size = usize::from(size);

use core::{cell::Cell, marker::PhantomData};

/// A zero-sized type used to mark types as `!Sync`.
type PhantomUnsync = PhantomData<Cell<()>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

For future work on our side: this should probably be moved to some internal utils module

@JarvisCraft JarvisCraft added the enhancement New feature or request label Nov 14, 2024
@JarvisCraft JarvisCraft added this to the v0.13.0 milestone Nov 14, 2024
@JarvisCraft JarvisCraft self-assigned this Nov 14, 2024
@JarvisCraft JarvisCraft merged commit b676f70 into flipperzero-rs:main Nov 14, 2024
9 checks passed
@JarvisCraft
Copy link
Contributor

Squashed the commits so that fix-named intermediate comments don't end up in main

@cptpiepmatz cptpiepmatz deleted the safe-stream-buffer branch November 14, 2024 23:14
@cptpiepmatz
Copy link
Contributor Author

Sweet!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants