What is the runtime in Rust? In order to completely understand it, I implemented a toy runtime without using third-party crates. It is called a toy because it has no complex scheduling algorithm (only one global task queue)
Except for mpmc (multi-producer, multi-consumer), which uses a third-party crate crossbeam , the rest of the code is hand-written.
You can play like this
fn main() {
let toy = Toy::new();
for i in 1..=20 {
toy.spawn(async move {
let ret = FakeIO::new(Duration::from_secs(i)).await;
println!("{:?}: {:?}", thread::current().id(), ret);
})
}
toy.run(4); // 4 threads
}
Among them, FakeIO is also simple enough
pub struct FakeIO {
finished: Arc<AtomicBool>,
duration: Duration,
}
impl Future for FakeIO {
type Output = Duration;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
if self.finished.load(Ordering::Acquire) {
return Poll::Ready(self.duration);
}
let finished = self.finished.clone();
let waker = cx.waker().clone();
let duration = self.duration;
thread::spawn(move || {
thread::sleep(duration);
finished.store(true, Ordering::Release);
waker.wake();
});
Poll::Pending
}
}
data structure
The data structures are as follows (referring to Tokio ’s design)
struct Task {
raw: RawTask,
}
unsafe impl Send for Task {}
unsafe impl Sync for Task {}
struct RawTask {
ptr: NonNull<Header>, // pointer to Cell<T> where T: Future
}
struct Header {
// todo: maybe replace the Mutex<State> with AtomicUsize
state: Mutex<State>,
vtable: &'static Vtable,
sender: crossbeam::channel::Sender<Task>,
}
#[derive(Default)]
struct State {
running: bool,
notified: bool,
completed: bool,
}
/// #[repr(C)] make sure `*mut Cell<T>` can cast to valid `*mut Header`, and backwards.
/// In the default situation, the data layout may not be the same as the order in which the fields are specified in the declaration of the type
/// 默认情况下 Rust 的数据布局不一定会按照 field 的声明顺序排列
/// [The Default Representation](https://doc.rust-lang.org/reference/type-layout.html?#the-default-representation)
///
/// [playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=39ac84782d121970598b91201b168f82)
///
/// you can easilly view the data layout with this crate https://github.com/hangj/layout-rs
#[repr(C)]
struct Cell<T: Future> {
header: Header,
future: T,
output: Option<T::Output>,
}
struct Vtable {
poll_task: unsafe fn(NonNull<Header>),
clone_task: unsafe fn(NonNull<Header>) -> NonNull<Header>,
drop_task: unsafe fn(NonNull<Header>),
}
Notable among them are:
RawTask
The one insideptr
actually points toNonNull<Cell<T: Future>>
Cell<T: Future>
Flagged#repr(C)
, reason stated in commentsvtable
The design refers to the vtable in Waker , which is equivalent to using a generic function to save type information, making it easier to restore the raw pointer to the original type later.
Have fun!