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

Added non-blocking radio receive #485

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

eivindbergem
Copy link

@eivindbergem eivindbergem commented Aug 16, 2024

I've added a simple non-blocking receive. The method returns a struct that takes care of checking status and calling cancel_recv(). Now it calls cancel_recv() for every drop. I can add some state to avoid this if needed.

I didn't put much thought into the names, so please feel free to bikeshed.

@BartMassey
Copy link
Member

Thanks much for the contribution!

Sorry about the CI failure, which is new: PR #486 should fix it shortly, after which you'll need to rebase.

Should this be adapted to support the nb crate infrastructure?

@eivindbergem
Copy link
Author

Should this be adapted to support the nb crate infrastructure?

Yes, that's a good idea :) I'll get on to it.

@eivindbergem
Copy link
Author

If is_done() is called after having received an Ok() or a CRC error, you would get a WouldBlock for ever:

let recv = radio.recv_non_blocking(&mut packet);
recv.is_done(); // -> WouldBlock
recv.is_done(); // -> Ok(crc)
while recv.is_done() == Err(nb::Error::WouldBlock) {} // Would never leave loop

This is not great. I could add some internal state and another error to indicate that we already have received:

enum RecvError {
    CrcFailure(u16),
    AlreadyReceived,
}

Or, maybe subsequent calls to is_done() should just repeatedly return Ok(crc) or Err(nb::Error::Other(crc)).

@BartMassey
Copy link
Member

Is this with nb or without? I've lost track.

@eivindbergem
Copy link
Author

This applies to both, with and without nb.

I'm not sure if it matters that much anyway, I was just thinking out loud. Futures suffer from the same issue, and the documentation makes it clear that futures that have returned Ready should not be polled.

@eivindbergem eivindbergem force-pushed the non-blocking-recv branch 3 times, most recently from 8798adc to 8a8096a Compare August 18, 2024 18:08
@eivindbergem
Copy link
Author

I refactored recv() and recv_timeout() to use recv_non_blocking() under the hood. I removed Event::End as it is no longer used. The enum only has one member now, so not sure if it's needed anymore.

/// Receives one radio packet and copies its contents into the given `packet` buffer
///
/// This method is non-blocking
pub fn recv_non_blocking(&mut self, packet: &mut Packet) -> Recv<'_, 'c> {
// Start the read
// NOTE(unsafe) We block until reception completes or errors
Copy link
Member

Choose a reason for hiding this comment

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

This is no longer true. start_recv doesn't have its safety conditions documented, but I suspect it requires the packet reference passed in to live at least until the receive completes. You don't enforce that here, so recv_non_blocking is unsound. You either need to make it unsafe (and document that the caller must ensure the packet lives at least until the receive operation completes), or add some safe abstraction to ensure that it does, such as having the Radio handle allocation of the packet.

Copy link
Author

Choose a reason for hiding this comment

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

Good catch! I've added a mutable reference to the packet in Recv to enforce this.

Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately that's still not sound. The caller can call forget on the returned Recv and then drop the Packet that they passed a reference to, while the hardware still tries to write to it.

Copy link
Author

Choose a reason for hiding this comment

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

If we require a pinned pointer for Packet, we get a drop guarantee.

Copy link
Author

Choose a reason for hiding this comment

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

No, that won't help as we can still forget Recv and then drop the Packet. It's the Recv that needs to be dropped, not Packet. I guess unsafe is the best option then.

Copy link
Author

Choose a reason for hiding this comment

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

Tried something new. recv_non_blocking takes a closure that takes &Recv as argument.

Copy link
Member

Choose a reason for hiding this comment

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

Interesting approach, it's not a pattern I've seen much before for something like this but I think it's sound.

Copy link
Member

Choose a reason for hiding this comment

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

If it's a soundness issue we might want to get a second review before we merge. I can try, but I'm bad at that stuff: might want to tag somebody?

@eivindbergem eivindbergem force-pushed the non-blocking-recv branch 3 times, most recently from 9ac3659 to c1f33f2 Compare August 19, 2024 18:07
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