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

implement asynchronous bulk/control transfers #153

Closed
wants to merge 4 commits into from

Conversation

StephanvanSchaik
Copy link

@StephanvanSchaik StephanvanSchaik commented Nov 3, 2022

This PR mostly draws inspiration from: this comment, PR #143, Multi-threaded applications and asynchonrous I/O.

  • Implements InnerTransfer and Transfer like in this comment.
  • Wraps NonNull<libusb_transfer> and implements Send such it can be sent across threads, since it is effectively guarded by an Arc<Mutex<T>>.
  • Uses Rust's ownership model to claim and pin the buffer and return it to the user once the transfer completes, gets cancelled or errors out to avoid unnecessary allocations, i.e. to put the user in control of these allocations. This should allow for zero copy, as far as zero copy is possible with libusb.
  • Implements CancellationToken that can be constructed from Transfer, such that the transfer can be cancelled from other tasks.
  • Replaces UsbContext::handle_events() with a thread-safe version as implemented by PR First try rusb-async #143 (and as described by the libusb documentation).
  • Fix an issue in the original code where handle_events() does not make any progress when the timeout is zero.
  • Export LIBUSB_CONTROL_SETUP_SIZE as CONTROL_SETUP_SIZE as users of USB control packets need to allocate buffers with at least CONTROL_SETUP_SIZE bytes to store the control request setup header.
  • Implements an example showing how to asynchronously read from a bulk endpoint.

This integrates nicely with tokio, but should be agnostic of the run-time used since it just relies on the std crate for futures, i.e. any asynchronous executor should work.

I have tried this with a device that supports USB control packets and it seems to work nicely. However, I don't have any experience with isochronous packets, so this PR does not implement that part.

src/lib.rs Outdated Show resolved Hide resolved
@a1ien
Copy link
Owner

a1ien commented Nov 3, 2022

Can we split this in to two PR. I mostly OK with replace handle_events only one thing it's just use loop and break instead while

@StephanvanSchaik
Copy link
Author

StephanvanSchaik commented Nov 3, 2022

Can we split this in to two PR. I mostly OK with replace handle_events only one thing it's just use loop and break instead while

Sure, sounds good to me.

@a1ien
Copy link
Owner

a1ien commented Nov 3, 2022

About async. I want made async in separate crate(as in #143). So can you just create PR with separate crate. You can use
version = "0.0.1-alpha-2" and same name name = "rusb-async" in you PR.
This allow test and select best implementation without releasing many rusb version with different async api. Just publishing rusb-async
After we select best implementation I move async code into the rusb


fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
// Poll libusb for any complete events.
if let Err(e) = self.context.handle_events(Some(Duration::from_micros(0))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't belong here.

When the transfer is not ready, it is poll's responsibility to save the waker and arrange for it to be woken when the event loop should poll your future again. You save the waker on the transfer and wake it from libusb's completion callback. But where does libusb check for events and call the completion callback? Yep, that's what libusb_handle_events and variants are for. So you need to call it somewhere other than poll so that libusb can call the callback, callback can wake the waker, and let the executor know that it has to call poll.

You have two options:

  1. Require a dedicated background thread that loops calling libusb_handle_events in blocking mode.
  2. If you don't want a background thread and don't need Windows support, you can do the complicated dance to get a set of file descriptors from libusb in a thread safe manner, and arrange for tokio/mio to poll them. When that reports a ready fd, then call handle_events in nonblocking mode.

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