This commit is contained in:
Kenneth Allen 2026-10-24 20:55:59 +11:00
parent b628e20ff9
commit 65658d3797
7 changed files with 271 additions and 200 deletions

View File

@ -3,9 +3,7 @@
#![feature(context_ext)] #![feature(context_ext)]
use std::{ use std::{
ops::DerefMut as _,
pin::Pin, pin::Pin,
ptr,
rc::{Rc, Weak}, rc::{Rc, Weak},
task::{ContextBuilder, Poll, local_waker_fn, waker_fn}, task::{ContextBuilder, Poll, local_waker_fn, waker_fn},
time::Instant, time::Instant,
@ -23,10 +21,14 @@ use send_wrapper::SendWrapper;
use slotmap::{SlotMap, new_key_type}; use slotmap::{SlotMap, new_key_type};
use unchecked_refcell::UncheckedRefCell; use unchecked_refcell::UncheckedRefCell;
use crate::op::Op; use crate::ring_op::RingOpHandle;
pub mod op; pub mod ops;
mod timer; mod ring_op;
pub mod timer;
#[cfg(test)]
mod tests;
struct Task { struct Task {
ready: bool, ready: bool,
@ -60,8 +62,14 @@ impl Runtime {
.setup_no_sqarray() .setup_no_sqarray()
.build(128) .build(128)
.expect("failed to create ring"); .expect("failed to create ring");
assert!(ring.params().is_feature_nodrop(), "missing IORING_FEAT_NODROP (kernel 5.19)"); assert!(
assert!(ring.params().is_feature_ext_arg(), "missing IORING_FEAT_EXT_ARG (kernel 5.11)"); ring.params().is_feature_nodrop(),
"missing IORING_FEAT_NODROP (kernel 5.19)"
);
assert!(
ring.params().is_feature_ext_arg(),
"missing IORING_FEAT_EXT_ARG (kernel 5.11)"
);
let submitter = ring.submitter(); let submitter = ring.submitter();
submitter submitter
.register_restrictions(&mut [Restriction::sqe_op(Nop::CODE)]) .register_restrictions(&mut [Restriction::sqe_op(Nop::CODE)])
@ -109,6 +117,7 @@ impl Runtime {
trace!("Polling task {:?}", task_id); trace!("Polling task {:?}", task_id);
lock.tasks[task_id].ready = false; lock.tasks[task_id].ready = false;
// TODO better wakers
let waker = { let waker = {
let inner = SendWrapper::new(self.downgrade()); let inner = SendWrapper::new(self.downgrade());
waker_fn(move || { waker_fn(move || {
@ -165,15 +174,15 @@ impl Runtime {
trace!("Polling timers"); trace!("Polling timers");
let wait = loop { let wait = loop {
match lock.timer.poll(now) { match lock.timer.poll(now) {
timer::Status::Empty => { timer::TimerPoll::Empty => {
trace!("No timers expired"); trace!("No timers expired");
break None; break None;
} }
timer::Status::Pending(deadline) => { timer::TimerPoll::Pending(deadline) => {
trace!("Next timer expiring in {:?}", deadline - now); trace!("Next timer expiring in {:?}", deadline - now);
break Some(Timespec::from(deadline - now)); break Some(Timespec::from(deadline - now));
} }
timer::Status::Expired(waker) => { timer::TimerPoll::Expired(waker) => {
trace!("Timer expired, waking"); trace!("Timer expired, waking");
drop(lock); drop(lock);
waker.wake(); waker.wake();
@ -206,10 +215,11 @@ impl Runtime {
}; };
trace!("Processing CQE {:?}", &cqe); trace!("Processing CQE {:?}", &cqe);
debug_assert!(cqe.result() >= 0); debug_assert!(cqe.result() >= 0);
let user_data = ptr::with_exposed_provenance(cqe.user_data().try_into().unwrap());
let op: Rc<UncheckedRefCell<Op>> = unsafe { Rc::from_raw(user_data) };
drop(lock); drop(lock);
Op::handle(op); {
let handle = unsafe { RingOpHandle::from_user_data(cqe.user_data()) };
handle.complete(cqe.result(), cqe.flags());
}
lock = self.0.borrow_mut(); lock = self.0.borrow_mut();
} }
} }
@ -220,52 +230,3 @@ impl Runtime {
trace!("Exiting"); trace!("Exiting");
} }
} }
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::{op::no_op, timer::delay};
use super::*;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn it_works() {
init();
let mut runtime = Runtime::new();
runtime.spawn(Box::pin(async {
println!("1");
no_op().await;
println!("2");
delay(Duration::from_secs(1)).await;
println!("3");
no_op().await;
println!("4");
}));
runtime.run();
}
#[test]
fn concurrency() {
init();
let mut runtime = Runtime::new();
for x in 0..10 {
runtime.spawn(Box::pin(async move {
println!("Starting {}", x);
delay(Duration::from_millis(x * 100)).await;
for y in 0..10 {
println!("Checkin {}ms", y * 1000 + x * 100);
delay(Duration::from_secs(1)).await;
no_op().await;
}
println!("Finishing {}", x);
}));
}
runtime.run();
}
}

102
src/op.rs
View File

@ -1,102 +0,0 @@
use std::{
mem,
ops::DerefMut as _,
pin::Pin,
rc::Rc,
task::{Context, LocalWaker, Poll},
};
use io_uring::opcode::Nop;
use log::trace;
use unchecked_refcell::UncheckedRefCell;
use crate::{RT, Runtime, RuntimeInner, op::NoOp::Cancelled};
pub struct OpEntry(Rc<UncheckedRefCell<Op>>);
pub enum Op {
NoOp(NoOp),
}
enum NoOp {
New,
Live(LocalWaker),
Done,
Cancelled,
}
pub fn no_op() -> OpEntry {
OpEntry(Rc::new(UncheckedRefCell::new(Op::NoOp(NoOp::New))))
}
impl Future for OpEntry {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut lock = self.0.borrow_mut();
match &mut *lock {
Op::NoOp(NoOp::Cancelled) => unreachable!(),
Op::NoOp(NoOp::Done) => Poll::Ready(()),
Op::NoOp(op @ NoOp::New) => {
{
let rt: &Runtime = cx.ext().downcast_ref().unwrap();
let mut lock = rt.0.borrow_mut();
let mut sq = lock.ring.submission();
let entry = Nop::new().build().user_data(
Rc::into_raw(self.0.clone())
.expose_provenance()
.try_into()
.unwrap(),
);
trace!("Pushing SQE {:?}", &entry);
unsafe { sq.push(&entry) }.expect("failed to submit no-op");
}
*op = NoOp::Live(cx.local_waker().clone());
Poll::Pending
}
Op::NoOp(NoOp::Live(waker)) => {
waker.clone_from(cx.local_waker());
Poll::Pending
}
}
}
}
impl Op {
pub fn handle(this: Rc<UncheckedRefCell<Self>>) {
// TODO don't drop for multishot accepts
let mut lock = this.borrow_mut();
match lock.deref_mut() {
Op::NoOp(op) => match mem::replace(op, NoOp::Done) {
NoOp::New | NoOp::Done => unreachable!(),
NoOp::Cancelled => {}
NoOp::Live(waker) => waker.wake(),
},
}
}
}
/*
impl Drop for OpEntry {
fn drop(&mut self) {
let mut lock = self.0.borrow_mut();
match lock.deref_mut() {
Op::NoOp(op) => {
match mem::replace(op, NoOp::Cancelled) {
NoOp::New | NoOp::Done => {}
NoOp::Cancelled => unreachable!(),
NoOp::Live(_) => {
RT.with(|rt| {
let outer = rt.borrow();
let mut lock = outer.as_ref().expect("future cancelled outside runtime thread").0.borrow_mut();
let mut sq = lock.ring.submission();
let entry = AsyncCancel::new(self.0.as_ptr().addr().try_into().unwrap()).build();
todo!()
})
}
}
}
}
}
}
*/

1
src/ops/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod no_op;

34
src/ops/no_op.rs Normal file
View File

@ -0,0 +1,34 @@
use std::{
pin::Pin,
task::{Context, Poll},
};
use io_uring::opcode::Nop;
use crate::ring_op::RingOp;
pub fn no_op() -> NoOp {
Default::default()
}
#[derive(Default)]
pub struct NoOp {
ring_op: RingOp,
}
impl Future for NoOp {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self
.ring_op
.submit_and_poll(cx, |user_data| Nop::new().build().user_data(user_data))
{
Poll::Ready(complete) => {
debug_assert_eq!(complete.result, 0);
Poll::Ready(())
}
Poll::Pending => Poll::Pending,
}
}
}

117
src/ring_op.rs Normal file
View File

@ -0,0 +1,117 @@
use std::{
mem,
ops::DerefMut as _,
ptr,
rc::Rc,
task::{Context, LocalWaker, Poll},
};
use io_uring::{opcode::AsyncCancel, squeue::Entry};
use log::trace;
use unchecked_refcell::UncheckedRefCell;
use crate::{RT, Runtime};
pub(crate) struct RingOp(RingOpHandle);
pub(crate) struct RingOpHandle(Rc<UncheckedRefCell<Inner>>);
enum Inner {
New,
Live(LocalWaker),
Complete(Complete),
Cancelled,
}
#[derive(Clone, Copy)]
pub struct Complete {
pub result: i32,
pub flags: u32,
}
impl Default for RingOp {
fn default() -> Self {
Self(RingOpHandle(Rc::new(UncheckedRefCell::new(Inner::New))))
}
}
impl Drop for RingOp {
fn drop(&mut self) {
// TODO handle already completed before submitting cancel
let mut lock = self.0.0.borrow_mut();
match mem::replace(lock.deref_mut(), Inner::Cancelled) {
Inner::New | Inner::Complete(_) => {}
Inner::Cancelled => unreachable!(),
Inner::Live(_) => RT.with(|rt| {
let outer = rt.borrow();
let lock = outer
.as_ref()
.expect("future cancelled outside runtime thread")
.0
.borrow_mut();
let entry = AsyncCancel::new(self.0.0.as_ptr().addr().try_into().unwrap()).build();
trace!("Canceling sqe {:?}", &entry);
let mut sq = unsafe { lock.ring.submission_shared() };
unsafe { sq.push(&entry) }.expect("failed to submit cancel");
// TODO check for full
}),
}
}
}
impl RingOp {
pub fn submit_and_poll(
&mut self,
cx: &mut Context<'_>,
with_entry: impl FnOnce(u64) -> Entry,
) -> Poll<Complete> {
let mut lock = self.0.0.borrow_mut();
match &mut *lock {
Inner::Cancelled => unreachable!(),
Inner::Complete(complete) => Poll::Ready(*complete),
op @ Inner::New => {
{
let rt: &Runtime = cx.ext().downcast_ref().unwrap(); // TODO Get this way or thread local?
let mut lock = rt.0.borrow_mut();
let entry = with_entry(
Rc::into_raw(self.0.0.clone())
.expose_provenance()
.try_into()
.unwrap(),
);
trace!("Pushing SQE {:?}", &entry);
let mut sq = lock.ring.submission();
unsafe { sq.push(&entry) }.expect("failed to submit no-op");
// TODO check for full
}
*op = Inner::Live(cx.local_waker().clone());
Poll::Pending
}
Inner::Live(waker) => {
waker.clone_from(cx.local_waker());
Poll::Pending
}
}
}
}
impl RingOpHandle {
pub unsafe fn from_user_data(user_data: u64) -> Self {
let ptr = ptr::with_exposed_provenance(user_data.try_into().unwrap());
Self(unsafe { Rc::from_raw(ptr) })
}
pub fn complete(self, result: i32, flags: u32) {
// TODO don't drop for multishot accepts
let mut lock = self.0.borrow_mut();
match mem::replace(
lock.deref_mut(),
Inner::Complete(Complete { result, flags }),
) {
Inner::New | Inner::Complete { .. } => unreachable!(),
Inner::Cancelled => {}
Inner::Live(waker) => waker.wake(),
}
}
}

54
src/tests.rs Normal file
View File

@ -0,0 +1,54 @@
use std::{
io::{Write as _, stdout},
time::Duration,
};
use crate::{
ops::no_op::no_op,
timer::{delay, delay_until},
};
use super::*;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn it_works() {
init();
let mut runtime = Runtime::new();
runtime.spawn(Box::pin(async {
let mut out = stdout().lock();
writeln!(&mut out, "1").unwrap();
no_op().await;
writeln!(&mut out, "2").unwrap();
delay(Duration::from_secs(1)).await;
writeln!(&mut out, "3").unwrap();
no_op().await;
writeln!(&mut out, "4").unwrap();
}));
runtime.run();
}
#[test]
fn concurrency() {
init();
let mut runtime = Runtime::new();
let base = Instant::now();
for x in 0..10 {
runtime.spawn(Box::pin(async move {
println!("Starting {}", x);
let base = base + Duration::from_millis(x * 100);
delay_until(base).await;
for y in 0..5 {
println!("Checkin {}ms", y * 1000 + x * 100);
delay_until(base + Duration::from_secs(y + 1)).await;
no_op().await;
}
println!("Finishing {}", x);
}));
}
runtime.run();
}

View File

@ -1,17 +1,17 @@
use std::{ use std::{
borrow::BorrowMut as _,
cell::RefCell, cell::RefCell,
cmp::Reverse, cmp::Reverse,
collections::BinaryHeap, collections::BinaryHeap,
mem,
pin::Pin, pin::Pin,
rc::{Rc, Weak}, rc::Rc,
task::{Context, LocalWaker, Poll}, task::{Context, LocalWaker, Poll},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use slab::Slab; use slab::Slab;
use crate::RT; use crate::Runtime;
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Entry { struct Entry {
@ -20,73 +20,84 @@ struct Entry {
} }
enum Slot { enum Slot {
Live,
Armed(LocalWaker), Armed(LocalWaker),
Complete,
Cancelled, Cancelled,
// Potential future optimization: Delayed(Instant, T), // Potential future optimization: Delayed(Instant, T),
} }
#[derive(Default)] #[derive(Default)]
pub struct TimerHeap { pub(crate) struct TimerHeap {
slots: Slab<Rc<RefCell<Slot>>>, slots: Slab<Rc<RefCell<Slot>>>,
queue: BinaryHeap<Reverse<Entry>>, queue: BinaryHeap<Reverse<Entry>>,
} }
pub enum Status { pub(crate) enum TimerPoll {
Empty, Empty,
Pending(Instant), Pending(Instant),
Expired(LocalWaker), Expired(LocalWaker),
} }
impl TimerHeap { impl TimerHeap {
pub fn insert(&mut self, deadline: Instant) -> Weak<RefCell<Slot>> { fn insert(&mut self, deadline: Instant, waker: LocalWaker) -> Rc<RefCell<Slot>> {
let slot = Rc::new(RefCell::new(Slot::Live)); let slot = Rc::new(RefCell::new(Slot::Armed(waker)));
let weak = Rc::downgrade(&slot); let handle = Rc::clone(&slot);
let slot_id = self.slots.insert(slot); let slot_id = self.slots.insert(slot);
self.queue.push(Reverse(Entry { deadline, slot_id })); self.queue.push(Reverse(Entry { deadline, slot_id }));
weak handle
} }
pub fn poll(&mut self, now: Instant) -> Status { pub fn poll(&mut self, now: Instant) -> TimerPoll {
loop { loop {
let slot = match self.queue.peek() { let slot = match self.queue.peek() {
None => break Status::Empty, None => break TimerPoll::Empty,
Some(&Reverse(Entry { deadline, slot_id })) => { Some(&Reverse(Entry { deadline, slot_id })) => {
if deadline > now { if deadline > now {
// We should check if cancelled here // We could check if cancelled here
break Status::Pending(deadline); break TimerPoll::Pending(deadline);
} }
self.queue.pop().unwrap(); self.queue.pop().unwrap();
self.slots.remove(slot_id) self.slots.remove(slot_id)
} }
}; };
match Rc::into_inner(slot).unwrap().into_inner() { let mut lock = slot.borrow_mut();
Slot::Live => {} match mem::replace(&mut *lock, Slot::Complete) {
Slot::Armed(waker) => break Status::Expired(waker), Slot::Armed(waker) => break TimerPoll::Expired(waker),
Slot::Cancelled => {} Slot::Cancelled => {}
Slot::Complete => unreachable!(),
} }
} }
} }
} }
pub struct Timer { pub enum Timer {
slot: Weak<RefCell<Slot>>, Pending(Instant),
Queued(Queued),
} }
pub struct Queued(Rc<RefCell<Slot>>);
impl Future for Timer { impl Future for Timer {
type Output = (); type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.slot.upgrade() { match &mut *self {
None => Poll::Ready(()), Timer::Pending(deadline) => {
Some(slot) => { let waker = cx.local_waker().to_owned();
let rt: &Runtime = cx.ext().downcast_ref().unwrap(); // TODO Get this way or thread local?
let mut lock = rt.0.borrow_mut();
*self = Timer::Queued(Queued(lock.timer.insert(*deadline, waker)));
Poll::Pending
}
Timer::Queued(Queued(slot)) => {
let mut lock = slot.as_ref().borrow_mut(); let mut lock = slot.as_ref().borrow_mut();
match &**lock.borrow_mut() { match &mut *lock {
&Slot::Live | &Slot::Armed(_) => { Slot::Armed(old_waker) => {
*lock = Slot::Armed(cx.local_waker().clone()); old_waker.clone_from(cx.local_waker());
Poll::Pending Poll::Pending
} }
&Slot::Cancelled => Poll::Ready(()), Slot::Cancelled => unreachable!(),
Slot::Complete => Poll::Ready(()),
} }
} }
} }
@ -95,20 +106,15 @@ impl Future for Timer {
impl Drop for Timer { impl Drop for Timer {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(slot) = self.slot.upgrade() { match self {
*slot.as_ref().borrow_mut() = Slot::Cancelled; Timer::Pending(_) => {}
Timer::Queued(Queued(slot)) => *slot.as_ref().borrow_mut() = Slot::Cancelled,
} }
} }
} }
pub fn delay_until(deadline: Instant) -> Timer { pub fn delay_until(deadline: Instant) -> Timer {
Timer { Timer::Pending(deadline)
slot: RT.with(|rt| {
let rt = rt.borrow();
let mut lock = rt.as_ref().unwrap().0.as_ref().borrow_mut();
lock.timer.insert(deadline)
}),
}
} }
pub fn delay(duration: Duration) -> Timer { pub fn delay(duration: Duration) -> Timer {