Skip to content

Commit f44017e

Browse files
committed
cargo features for smol and tokio blocking executors
1 parent 413093e commit f44017e

File tree

4 files changed

+100
-11
lines changed

4 files changed

+100
-11
lines changed

.github/workflows/rust.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ jobs:
3535
- name: Build
3636
run: cargo build --verbose
3737
- name: Run tests
38-
run: cargo test --verbose
38+
run: |
39+
cargo test --verbose
40+
cargo test --verbose --features tokio
41+
cargo test --verbose --features smol
42+
cargo test --verbose --features smol,tokio
3943
4044
build_android:
4145
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,16 @@ core-foundation-sys = "0.8.4"
3434
io-kit-sys = "0.4.0"
3535

3636
[target.'cfg(any(target_os="linux", target_os="android", target_os="windows", target_os="macos"))'.dependencies]
37-
blocking ="1.6.1"
37+
blocking = { version = "1.6.1", optional = true }
38+
tokio = { version = "1", optional = true, features = ["rt"] }
39+
40+
[features]
41+
# Use the `blocking` crate for making blocking IO async
42+
smol = ["dep:blocking"]
43+
44+
# Use `tokio`'s IO threadpool for making blocking IO async
45+
tokio = ["dep:tokio"]
46+
3847

3948
[lints.rust]
4049
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }

src/lib.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
//! A new library for cross-platform low-level access to USB devices.
33
//!
44
//! `nusb` is comparable to the C library [libusb] and its Rust bindings [rusb],
5-
//! but written in pure Rust. It's built on and exposes async APIs by default,
6-
//! but can be made blocking using [`futures_lite::future::block_on`][block_on]
7-
//! or similar.
5+
//! but written in pure Rust. It supports usage from both async and
6+
//! blocking contexts, and transfers are natively async.
87
//!
98
//! [libusb]: https://libusb.info
109
//! [rusb]: https://docs.rs/rusb/
11-
//! [block_on]: https://docs.rs/futures-lite/latest/futures_lite/future/fn.block_on.html
1210
//!
1311
//! Use `nusb` to write user-space drivers in Rust for non-standard USB devices
1412
//! or those without kernel support. For devices implementing a standard USB
@@ -111,8 +109,29 @@
111109
//!
112110
//! `nusb` uses IOKit on macOS.
113111
//!
114-
//! Users have access to USB devices by default, with no permission configuration needed.
115-
//! Devices with a kernel driver are not accessible.
112+
//! Users have access to USB devices by default, with no permission
113+
//! configuration needed. Devices with a kernel driver are not accessible.
114+
//!
115+
//! ## Async support
116+
//!
117+
//! Many methods in `nusb` return a [`MaybeFuture`] type, which can either be
118+
//! `.await`ed (via `IntoFuture`) or `.wait()`ed (blocking the current thread).
119+
//! This allows for async usage in an async context, or blocking usage in a
120+
//! non-async context.
121+
//!
122+
//! Operations such as [`Device::open`], [`Device::set_configuration`],
123+
//! [`Device::reset`], [`Device::claim_interface`],
124+
//! [`Interface::set_alt_setting`], and [`Interface::clear_halt`] require
125+
//! blocking system calls. To use these in an asynchronous context, `nusb`
126+
//! relies on an async runtime to run these operations on an IO thread to avoid
127+
//! blocking in async code. Enable the cargo feature `tokio` or `smol` to use
128+
//! the corresponding runtime for blocking IO. If neither feature is enabled,
129+
//! `.await` on these methods will log a warning and block the calling thread.
130+
//! `.wait()` always runs the blocking operation directly without the overhead
131+
//! of handing off to an IO thread.
132+
//!
133+
//! These features do not affect and are not required for transfers, which are
134+
//! implemented on top of natively-async OS APIs.
116135
117136
use std::io;
118137

src/maybe_future.rs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ impl<T> NonWasmSend for T {}
4141
))]
4242
pub mod blocking {
4343
use super::MaybeFuture;
44-
use std::future::IntoFuture;
44+
use std::{
45+
future::{Future, IntoFuture},
46+
pin::Pin,
47+
task::{Context, Poll},
48+
};
4549

4650
/// Wrapper that invokes a FnOnce on a background thread when
4751
/// called asynchronously, or directly when called synchronously.
@@ -62,10 +66,10 @@ pub mod blocking {
6266
{
6367
type Output = R;
6468

65-
type IntoFuture = blocking::Task<R, ()>;
69+
type IntoFuture = BlockingTask<R>;
6670

6771
fn into_future(self) -> Self::IntoFuture {
68-
blocking::unblock(self.f)
72+
BlockingTask::spawn(self.f)
6973
}
7074
}
7175

@@ -78,6 +82,59 @@ pub mod blocking {
7882
(self.f)()
7983
}
8084
}
85+
86+
#[cfg(all(feature = "smol", not(feature = "tokio")))]
87+
pub struct BlockingTask<R>(blocking::Task<R, ()>);
88+
89+
#[cfg(feature = "tokio")]
90+
pub struct BlockingTask<R>(tokio::task::JoinHandle<R>);
91+
92+
#[cfg(not(any(feature = "smol", feature = "tokio")))]
93+
pub struct BlockingTask<R>(Option<R>);
94+
95+
impl<R: Send + 'static> BlockingTask<R> {
96+
#[cfg(all(feature = "smol", not(feature = "tokio")))]
97+
fn spawn(f: impl FnOnce() -> R + Send + 'static) -> Self {
98+
Self(blocking::unblock(f))
99+
}
100+
101+
#[cfg(feature = "tokio")]
102+
fn spawn(f: impl FnOnce() -> R + Send + 'static) -> Self {
103+
Self(tokio::task::spawn_blocking(f))
104+
}
105+
106+
#[cfg(not(any(feature = "smol", feature = "tokio")))]
107+
fn spawn(f: impl FnOnce() -> R + Send + 'static) -> Self {
108+
static ONCE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
109+
110+
if ONCE.swap(false, std::sync::atomic::Ordering::Relaxed) {
111+
log::warn!("Awaiting blocking syscall without an async runtime: enable the `smol` or `tokio` feature of `nusb` to avoid blocking the thread.")
112+
}
113+
114+
Self(Some(f()))
115+
}
116+
}
117+
118+
impl<R> Unpin for BlockingTask<R> {}
119+
120+
impl<R> Future for BlockingTask<R> {
121+
type Output = R;
122+
123+
#[cfg(all(feature = "smol", not(feature = "tokio")))]
124+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
125+
Pin::new(&mut self.0).poll(cx)
126+
}
127+
128+
#[cfg(feature = "tokio")]
129+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
130+
Pin::new(&mut self.0).poll(cx).map(|r| r.unwrap())
131+
}
132+
133+
#[cfg(not(any(feature = "smol", feature = "tokio")))]
134+
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
135+
Poll::Ready(self.0.take().expect("polled after completion"))
136+
}
137+
}
81138
}
82139

83140
pub(crate) struct Ready<T>(pub(crate) T);

0 commit comments

Comments
 (0)