Skip to content

Commit

Permalink
getting the fsm approach to work without async-trait
Browse files Browse the repository at this point in the history
  • Loading branch information
sminez committed Feb 25, 2025
1 parent a778457 commit b6eb058
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 640 deletions.
12 changes: 0 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions crates/ninep/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ninep"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["sminez <[email protected]>"]
license = "MIT"
Expand All @@ -15,13 +15,12 @@ include = [

[features]
default = ["tokio"]
tokio = ["dep:tokio", "dep:async-trait"]
tokio = ["dep:tokio"]

[dependencies]
async-trait = { version = "0.1.86", optional = true }
bitflags = "2.6"
tokio = { version = "1.43.0", features = ["net", "io-util", "sync"], optional = true }
tokio = { version = "1.43.0", features = ["macros", "net", "io-util", "rt", "sync"], optional = true }

[dev-dependencies]
simple_test_case = "1"
tokio = { version = "1.43.0", features = ["time", "macros", "rt-multi-thread", "net", "io-util", "sync"] }
tokio = { version = "1.43.0", features = ["macros", "net", "io-util", "rt", "sync", "time", "rt-multi-thread", "net", "io-util"] }
42 changes: 23 additions & 19 deletions crates/ninep/examples/async_fsm.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
//! This is a little exploration of using async/await + a dummy Waker to simplify writing sans-io
//! state machine code.
use std::{
cell::RefCell,
cell::UnsafeCell,
future::{Future, IntoFuture},
io::{Cursor, Read},
pin::{pin, Pin},
rc::Rc,
sync::Arc,
task::{Context, Poll, Wake, Waker},
};
Expand All @@ -24,17 +23,17 @@ async fn main() {
]);

println!(">> reading using std::io::Read");
let s: String = read_9p_sync_from_bytes(&mut cur);
let s: String = read_9p_sync(&mut cur);
println!(" got val: {s:?}\n");

cur.set_position(0);

println!(">> reading using tokio::io::AsyncRead");
let s: String = read_9p_async_from_bytes(&mut cur).await;
let s: String = read_9p_async(&mut cur).await;
println!(" got val: {s:?}");
}

fn read_9p_sync_from_bytes<T, R>(r: &mut R) -> T
fn read_9p_sync<T, R>(r: &mut R) -> T
where
T: Read9p,
R: Read,
Expand All @@ -48,18 +47,18 @@ where
loop {
match fut.as_mut().poll(&mut context) {
Poll::Ready(val) => return val,
Poll::Pending => {
let n = s.0.borrow().n;
Poll::Pending => unsafe {
let n = (*s.0.get()).n;
println!("{n} bytes requested");
let mut buf = vec![0; n];
r.read_exact(&mut buf).unwrap();
s.0.borrow_mut().buf = Some(buf);
}
(*s.0.get()).buf = Some(buf);
},
}
}
}

async fn read_9p_async_from_bytes<T, R>(r: &mut R) -> T
async fn read_9p_async<T, R>(r: &mut R) -> T
where
T: Read9p,
R: AsyncRead + Unpin,
Expand All @@ -73,13 +72,13 @@ where
loop {
match fut.as_mut().poll(&mut context) {
Poll::Ready(val) => return val,
Poll::Pending => {
let n = s.0.borrow().n;
Poll::Pending => unsafe {
let n = (*s.0.get()).n;
println!("{n} bytes requested");
let mut buf = vec![0; n];
r.read_exact(&mut buf).await.unwrap();
s.0.borrow_mut().buf = Some(buf);
}
(*s.0.get()).buf = Some(buf);
},
}
}
}
Expand All @@ -106,8 +105,14 @@ impl Future for Yield {
}
}

/// Shared state between a [NineP] impl and a parent read loop that is performing IO.
#[derive(Default, Debug, Clone)]
struct State(Rc<RefCell<StateInner>>);
pub struct State(pub(crate) Arc<UnsafeCell<StateInner>>);

// SAFETY: StateInner is only accessable in this crate
unsafe impl Send for State {}
// SAFETY: StateInner is only accessable in this crate
unsafe impl Sync for State {}

#[derive(Default, Debug)]
struct StateInner {
Expand All @@ -119,20 +124,19 @@ struct StateInner {
/// so it can perform IO and provide the requested data.
macro_rules! request_bytes {
($s:expr, $n:expr) => {{
$s.0.borrow_mut().n = $n;
(*$s.0.get()).n = $n;
Yield(false).await;
$s.0.borrow_mut().buf.take().unwrap()
(*$s.0.get()).buf.take().unwrap()
}};
}

/// # Safety
/// The read method of this trait requires that you only yield view the [request_bytes] macro.
#[allow(async_fn_in_trait)]
unsafe trait Read9p {
/// # Safety
/// Implementations of `read` need to ensure that the only await points they contain are
/// from calls to the [request_bytes] macro.
async unsafe fn read(state: State) -> Self;
unsafe fn read(state: State) -> impl Future<Output = Self> + Send;
}

#[allow(async_fn_in_trait)]
Expand Down
2 changes: 2 additions & 0 deletions crates/ninep/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
pub mod fs;
pub mod sansio;
pub mod sync;

#[cfg(feature = "tokio")]
pub mod tokio;

/// A simple result type for errors returned from this crate
Expand Down
15 changes: 15 additions & 0 deletions crates/ninep/src/sansio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use crate::{
sansio::protocol::{Rdata, Rmessage},
Result,
};
use std::{
sync::Arc,
task::{Wake, Waker},
};

pub mod protocol;
pub mod server;
Expand All @@ -17,3 +21,14 @@ impl From<(u16, Result<Rdata>)> for Rmessage {
}
}
}

struct StubWaker;
impl Wake for StubWaker {
fn wake(self: Arc<Self>) {}
fn wake_by_ref(self: &Arc<Self>) {}
}

/// A no-op waker that is just used to create a context for driving a NineP read loop.
pub(crate) fn stub_waker() -> Waker {
Waker::from(Arc::new(StubWaker))
}
Loading

0 comments on commit b6eb058

Please sign in to comment.