This commit is contained in:
Kenneth Allen 2026-09-28 23:22:04 +10:00
parent 891788af2a
commit ae5df39323
7 changed files with 240 additions and 45 deletions

23
Cargo.lock generated
View File

@ -95,6 +95,8 @@ dependencies = [
"log", "log",
"send_wrapper", "send_wrapper",
"slab", "slab",
"slotmap",
"unchecked-refcell",
] ]
[[package]] [[package]]
@ -323,6 +325,15 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "slotmap"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.119" version = "2.0.119"
@ -365,6 +376,12 @@ dependencies = [
"syn 3.0.6", "syn 3.0.6",
] ]
[[package]]
name = "unchecked-refcell"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05442d1d94026b1d3e8ca10750c1461ce60731aee72c698526574e0e91c6e7a2"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.26" version = "1.0.26"
@ -377,6 +394,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"

View File

@ -4,9 +4,13 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
env_logger = "0.11.11"
io-uring = "0.7.15" io-uring = "0.7.15"
libc = "0.2.189" libc = "0.2.189"
log = "0.4.34" log = "0.4.34"
send_wrapper = "0.6.0" send_wrapper = "0.6.0"
slab = "0.4.12" slab = "0.4.12"
slotmap = "1.1.1"
unchecked-refcell = "0.2.2"
[dev-dependencies]
env_logger = "0.11.11"

View File

View File

@ -1,19 +1,31 @@
#![feature(local_waker)] #![feature(local_waker)]
#![feature(waker_fn)] #![feature(waker_fn)]
#![feature(context_ext)]
use std::{ use std::{
cell::RefCell, pin::Pin, rc::{Rc, Weak}, task::{ContextBuilder, Poll, local_waker_fn, waker_fn}, thread::sleep, time::Instant, ops::DerefMut as _,
pin::Pin,
ptr,
rc::{Rc, Weak},
task::{ContextBuilder, Poll, local_waker_fn, waker_fn},
time::Instant,
}; };
use io_uring::{ use io_uring::{
IoUring, IoUring,
opcode::Nop,
register::Restriction,
types::{SubmitArgs, Timespec}, types::{SubmitArgs, Timespec},
}; };
use libc::{ETIME}; use libc::ETIME;
use log::trace; use log::trace;
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use slab::Slab; use slotmap::{SlotMap, new_key_type};
use unchecked_refcell::UncheckedRefCell;
use crate::op::Op;
pub mod op;
mod timer; mod timer;
struct Task { struct Task {
@ -21,41 +33,41 @@ struct Task {
fut: Option<Pin<Box<dyn Future<Output = ()>>>>, fut: Option<Pin<Box<dyn Future<Output = ()>>>>,
} }
new_key_type! { struct TaskId; }
pub struct RuntimeInner { pub struct RuntimeInner {
timer: timer::TimerHeap, timer: timer::TimerHeap,
ring: IoUring, ring: IoUring,
tasks: Slab<Task>, tasks: SlotMap<TaskId, Task>,
runqueue: Vec<usize>, runqueue: Vec<TaskId>,
} }
pub struct Runtime(Rc<RefCell<RuntimeInner>>); pub struct Runtime(Rc<UncheckedRefCell<RuntimeInner>>);
struct RuntimeWeak(Weak<RefCell<RuntimeInner>>); struct RuntimeWeak(Weak<UncheckedRefCell<RuntimeInner>>);
thread_local! { thread_local! {
static RT: RefCell<Option<Runtime>> = RefCell::new(None); static RT: UncheckedRefCell<Option<Runtime>> = const { UncheckedRefCell::new(None) };
} }
impl Runtime { impl Runtime {
pub fn new() -> Self { pub fn new() -> Self {
let ring = IoUring::builder() let ring = IoUring::builder()
.setup_cqsize(256) .setup_cqsize(256)
.setup_r_disabled() //.setup_r_disabled()
.setup_defer_taskrun() .setup_defer_taskrun()
.setup_single_issuer() .setup_single_issuer()
.setup_no_sqarray() .setup_no_sqarray()
.build(128) .build(128)
.expect("failed to create ring"); .expect("failed to create ring");
let submitter = ring.submitter(); //let submitter = ring.submitter();
submitter //submitter
.register_restrictions(&mut [ //.register_restrictions(&mut [Restriction::register_op(Nop::CODE)])
// TODO //.expect("failed to register restrictions on ring");
]) //submitter
.expect("failed to register restrictions on ring"); //.register_enable_rings()
submitter //.expect("failed to enable ring");
.register_enable_rings() Self(Rc::new(UncheckedRefCell::new(RuntimeInner {
.expect("failed to enable rings");
Self(Rc::new(RefCell::new(RuntimeInner {
timer: Default::default(), timer: Default::default(),
ring, ring,
@ -78,28 +90,32 @@ impl Runtime {
} }
pub fn run(&mut self) { pub fn run(&mut self) {
RT.set(Some(Runtime(Rc::clone(&self.0)))); RT.with(|cell| {
let mut lock = cell.borrow_mut();
if lock.is_some() {
panic!("runtime already initialized on this thread");
}
*lock = Some(Runtime(Rc::clone(&self.0)))
});
let mut lock = self.0.borrow_mut(); let mut lock = self.0.borrow_mut();
while !lock.tasks.is_empty() { while !lock.tasks.is_empty() {
// Run until no ready tasks, no timers, no future CQEs // Run until no ready tasks, no timers, no future CQEs
trace!("Polling tasks"); trace!("Polling {} tasks", lock.tasks.len());
loop { while let Some(task_id) = lock.runqueue.pop() {
let Some(task_id) = lock.runqueue.pop() else { trace!("Polling task {:?}", task_id);
break;
};
trace!("Polling task {}", task_id);
lock.tasks[task_id].ready = false; lock.tasks[task_id].ready = false;
let waker = { let waker = {
let inner = SendWrapper::new(self.downgrade()); let inner = SendWrapper::new(self.downgrade());
waker_fn(move || { waker_fn(move || {
if let Some(inner) = inner.0.upgrade() { if let Some(inner) = inner.0.upgrade() {
trace!("Waking {}", task_id); trace!("Waking {:?}", task_id);
let mut lock = inner.borrow_mut(); let mut lock = inner.borrow_mut();
let ready = &mut lock.tasks[task_id].ready; let ready = &mut lock.tasks[task_id].ready;
if !*ready { if !*ready {
trace!("Enqueueing {}", task_id); trace!("Enqueueing {:?}", task_id);
*ready = true; *ready = true;
lock.runqueue.push(task_id); lock.runqueue.push(task_id);
} }
@ -110,24 +126,29 @@ impl Runtime {
let inner = SendWrapper::new(self.downgrade()); let inner = SendWrapper::new(self.downgrade());
local_waker_fn(move || { local_waker_fn(move || {
if let Some(inner) = inner.0.upgrade() { if let Some(inner) = inner.0.upgrade() {
trace!("Local waking {}", task_id); trace!("Local waking {:?}", task_id);
let mut lock = inner.borrow_mut(); let mut lock = inner.borrow_mut();
let ready = &mut lock.tasks[task_id].ready; let ready = &mut lock.tasks[task_id].ready;
if !*ready { if !*ready {
trace!("Local enqueueing {}", task_id); trace!("Local enqueueing {:?}", task_id);
*ready = true; *ready = true;
lock.runqueue.push(task_id); lock.runqueue.push(task_id);
} }
} }
}) })
}; };
let mut cx = ContextBuilder::from_waker(&waker)
.local_waker(&local_waker) // TODO we shouldn't need to take task out
.build();
let mut task = lock.tasks[task_id].fut.take().unwrap(); let mut task = lock.tasks[task_id].fut.take().unwrap();
drop(lock); drop(lock);
let mut cx = ContextBuilder::from_waker(&waker)
.local_waker(&local_waker)
.ext(self)
.build();
// Unlock runtime while running user code
let outcome = task.as_mut().poll(&mut cx); let outcome = task.as_mut().poll(&mut cx);
lock = self.0.borrow_mut(); lock = self.0.borrow_mut();
match outcome { match outcome {
Poll::Ready(()) => { Poll::Ready(()) => {
lock.tasks.remove(task_id); lock.tasks.remove(task_id);
@ -169,14 +190,26 @@ impl Runtime {
None => 0, None => 0,
}; };
match lock.ring.submitter().submit_with_args(want, &args) { match lock.ring.submitter().submit_with_args(want, &args) {
Ok(n) => { Ok(_) => {}
debug_assert_eq!(n, 0);
}
Err(err) if err.raw_os_error() == Some(ETIME) && wait.is_some() => {} Err(err) if err.raw_os_error() == Some(ETIME) && wait.is_some() => {}
e => { e => {
e.expect("failed to enter ring"); e.expect("failed to enter ring");
} }
} }
loop {
let entry = lock.ring.completion().next();
let Some(cqe) = entry else {
break;
};
trace!("Processing CQE {:?}", &cqe);
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);
Op::handle(op);
lock = self.0.borrow_mut();
}
} }
trace!("Exiting"); trace!("Exiting");
} }
@ -186,19 +219,47 @@ impl Runtime {
mod tests { mod tests {
use std::time::Duration; use std::time::Duration;
use crate::timer::delay; use crate::{op::no_op, timer::delay};
use super::*; use super::*;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test] #[test]
fn it_works() { fn it_works() {
env_logger::init(); init();
let mut runtime = Runtime::new(); let mut runtime = Runtime::new();
runtime.spawn(Box::pin(async { runtime.spawn(Box::pin(async {
println!("1"); println!("1");
delay(Duration::from_secs(1)).await; no_op().await;
println!("2"); println!("2");
delay(Duration::from_secs(1)).await;
println!("3");
no_op().await;
println!("4");
})); }));
runtime.run(); 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 Normal file
View File

@ -0,0 +1,102 @@
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!()
})
}
}
}
}
}
}
*/

View File

@ -103,11 +103,9 @@ impl Drop for Timer {
pub fn delay_until(deadline: Instant) -> Timer { pub fn delay_until(deadline: Instant) -> Timer {
Timer { Timer {
slot: RT.with_borrow(|rt| { slot: RT.with(|rt| {
let rt = rt let rt = rt.borrow();
.as_ref() let mut lock = rt.as_ref().unwrap().0.as_ref().borrow_mut();
.expect("called from outside curio runtime thread");
let mut lock = rt.0.as_ref().borrow_mut();
lock.timer.insert(deadline) lock.timer.insert(deadline)
}), }),
} }

7
todo.txt Normal file
View File

@ -0,0 +1,7 @@
Minimal wakers
Nop reactor
Sleep reactor
File reactor
Network reactor
Task soundness
Unchecked SQ/CQ without debug_assertions