use std::{
cell::RefCell,
future::Future,
io,
num::NonZeroU32,
task::{Context, Waker},
time::Duration,
};
use std::rc::Rc;
use crate::{
builder::{RuntimeBuilder, RuntimeConfig},
driver::{Driver, Handle},
runtime::blocking::{BlockingPool, spawn_blocking_task},
runtime::local::scheduler::Scheduler,
task::join::JoinHandle,
time::Timer,
};
pub mod queue;
pub mod scheduler;
scoped_thread_local!(static CURRENT_SCHEDULER: Scheduler);
scoped_thread_local!(pub(crate) static CURRENT_DRIVER: Handle);
scoped_thread_local!(pub(crate) static CURRENT_TIMER: Rc<RefCell<Timer>>);
pub struct Runtime {
pub(crate) scheduler: Scheduler,
pub(crate) timer: Rc<RefCell<Timer>>,
_blocking: BlockingPool,
pub(crate) driver: Driver,
event_interval: NonZeroU32,
}
impl Drop for Runtime {
fn drop(&mut self) {
self.scheduler.shutdown();
self.timer.borrow_mut().clear();
self._blocking.shutdown_and_join();
self.driver.drain_blocking_completions();
self.driver.shutdown();
}
}
impl Runtime {
pub fn new() -> io::Result<Self> {
RuntimeBuilder::new().build()
}
pub fn builder() -> RuntimeBuilder {
RuntimeBuilder::new()
}
pub(crate) fn from_config(config: RuntimeConfig) -> io::Result<Self> {
config.validate()?;
let blocking = BlockingPool::new(config.blocking_threads, config.blocking_keep_alive);
let driver = Driver::new(
blocking.handle(),
crate::driver::DriverConfig {
capacity: config.driver_capacity,
buffer_pool_size: config.buffer_pool_size,
buffer_pool_buffer_len: config.buffer_pool_buffer_len,
multishot_accept_capacity: config.multishot_accept_capacity,
},
)?;
let mut scheduler = Scheduler::default();
scheduler.set_wakeup(driver.wakeup());
Ok(Self {
scheduler,
timer: Rc::new(RefCell::new(Timer::new())),
_blocking: blocking,
driver,
event_interval: config.event_interval,
})
}
pub fn block_on<F: Future>(&mut self, future: F) -> F::Output {
assert!(!CURRENT_SCHEDULER.is_set(), "Can not start a runtime inside a runtime");
let handle: Handle = (&self.driver).into();
let root_waker = Waker::from(std::sync::Arc::new(self.driver.wakeup()));
let mut root_context = Context::from_waker(&root_waker);
let mut root = std::pin::pin!(future);
CURRENT_TIMER.set(&self.timer, || {
CURRENT_SCHEDULER.set(&self.scheduler, || {
CURRENT_DRIVER.set(&handle, || {
loop {
loop {
self.scheduler.tick();
self.timer.borrow_mut().wake();
for _ in 0..self.event_interval.get() {
let Some(t) = self.scheduler.tasks.pop_front() else {
break;
};
self.scheduler.run_task(t);
}
if let std::task::Poll::Ready(output) = root.as_mut().poll(&mut root_context) {
return output;
}
if self.scheduler.tasks.is_empty() {
break;
}
self.driver
.turn(Some(Duration::ZERO))
.expect("Failed to poll I/O events");
}
let timeout = self.timer.borrow().min_timeout();
self.driver.turn(timeout).expect("Failed to wait for I/O events");
self.timer.borrow_mut().wake();
}
})
})
})
}
pub fn spawn<F: Future + 'static>(&self, future: F) -> JoinHandle<F::Output> {
self.scheduler.spawn(future)
}
pub fn spawn_blocking<F, R>(&self, f: F) -> JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
spawn_blocking_task(self.driver.blocking_pool(), self.driver.wakeup(), f)
}
pub fn shutdown_timeout(self, duration: Duration) {
self._blocking.shutdown_with_timeout(duration);
}
pub fn shutdown_background(self) {
self.shutdown_timeout(Duration::ZERO);
}
}
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
assert!(
CURRENT_DRIVER.is_set() && CURRENT_SCHEDULER.is_set(),
"spawn_blocking called outside of a runtime context"
);
CURRENT_DRIVER.with(|handle| {
let driver = handle.upgrade().expect("spawn_blocking: driver has been dropped");
spawn_blocking_task(driver.blocking_pool(), driver.wakeup(), f)
})
}
pub fn spawn_local<F, R>(future: F) -> JoinHandle<R>
where
F: Future<Output = R> + 'static,
R: 'static,
{
assert!(
CURRENT_DRIVER.is_set() && CURRENT_SCHEDULER.is_set(),
"spawn_local called outside of a runtime context"
);
CURRENT_SCHEDULER.with(|scheduler| scheduler.spawn(future))
}
#[cfg(test)]
mod tests {
use std::{
future::{Future, pending},
pin::Pin,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
mpsc,
},
task::{Context, Poll, Waker},
thread,
};
use super::{Runtime, spawn_blocking};
use crate::task::join::JoinHandle;
#[test]
fn join_handle_resolves_successful_task() {
let mut runtime = Runtime::new().expect("runtime should start");
let task = runtime.spawn(async { 7usize });
let output = runtime.block_on(task);
assert_eq!(output.expect("task should succeed"), 7);
}
#[test]
fn join_handle_reports_panics() {
let mut runtime = Runtime::new().expect("runtime should start");
let task = runtime.spawn(async {
panic!("boom");
#[allow(unreachable_code)]
7usize
});
let err = runtime.block_on(task).expect_err("task should report panic");
assert!(err.is_panic());
}
#[test]
fn abort_cancels_task_before_it_runs() {
let mut runtime = Runtime::new().expect("runtime should start");
let task = runtime.spawn(pending::<usize>());
task.abort();
let err = runtime.block_on(task).expect_err("task should be cancelled");
assert!(err.is_cancelled());
}
#[test]
fn dropping_runtime_with_queued_and_remote_tasks_does_not_panic() {
let mut runtime = Runtime::new().expect("runtime should start");
for _ in 0..8 {
drop(runtime.spawn(pending::<()>()));
}
let ready = Arc::new(AtomicBool::new(false));
let (waker_tx, waker_rx) = mpsc::channel();
let _detached = runtime.spawn({
let ready = Arc::clone(&ready);
async move {
struct Once {
ready: Arc<AtomicBool>,
tx: Option<mpsc::Sender<Waker>>,
}
impl Future for Once {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.ready.load(Ordering::Acquire) {
return Poll::Ready(());
}
if let Some(tx) = self.tx.take() {
let _ = tx.send(cx.waker().clone());
}
Poll::Pending
}
}
Once {
ready,
tx: Some(waker_tx),
}
.await
}
});
runtime.block_on(async {
crate::time::sleep(std::time::Duration::from_millis(10)).await;
});
let _ = thread::spawn(move || {
if let Ok(waker) = waker_rx.recv_timeout(std::time::Duration::from_millis(50)) {
ready.store(true, Ordering::Release);
waker.wake();
}
});
drop(runtime);
}
#[test]
fn dropping_runtime_joins_running_blocking_work() {
let mut runtime = Runtime::new().expect("runtime should start");
let (started_tx, started_rx) = mpsc::channel();
let (release_tx, release_rx) = mpsc::channel();
let (completed_tx, completed_rx) = mpsc::channel();
let (drop_started_tx, drop_started_rx) = mpsc::channel();
let task = runtime.spawn_blocking(move || {
started_tx.send(()).expect("report blocking work start");
release_rx.recv().expect("release blocking work");
completed_tx.send(()).expect("report blocking work completion");
});
runtime.block_on(async {
crate::time::sleep(std::time::Duration::from_millis(1)).await;
});
started_rx
.recv_timeout(std::time::Duration::from_secs(2))
.expect("blocking work should start before teardown");
drop(task);
let releaser = thread::spawn(move || {
drop_started_rx.recv().expect("wait for runtime teardown");
release_tx.send(()).expect("release blocking work");
});
drop_started_tx.send(()).expect("start runtime teardown");
drop(runtime);
releaser.join().expect("release thread should finish");
completed_rx
.try_recv()
.expect("runtime drop should join the running blocking job");
}
#[test]
fn shutdown_timeout_detaches_running_blocking_work() {
let mut runtime = Runtime::new().expect("runtime should start");
let (started_tx, started_rx) = mpsc::channel();
let (release_tx, release_rx) = mpsc::channel::<()>();
let _task = runtime.spawn_blocking(move || {
started_tx.send(()).expect("report blocking work start");
release_rx.recv().ok();
});
runtime.block_on(async {
crate::time::sleep(std::time::Duration::from_millis(1)).await;
});
started_rx
.recv_timeout(std::time::Duration::from_secs(2))
.expect("blocking work should start before teardown");
let start = std::time::Instant::now();
runtime.shutdown_timeout(std::time::Duration::from_millis(0));
assert!(start.elapsed() < std::time::Duration::from_secs(1));
drop(release_tx);
}
#[test]
fn shutdown_background_is_fast() {
let mut runtime = Runtime::new().expect("runtime should start");
let (started_tx, started_rx) = mpsc::channel();
let (release_tx, release_rx) = mpsc::channel::<()>();
let _task = runtime.spawn_blocking(move || {
started_tx.send(()).expect("report blocking work start");
release_rx.recv().ok();
});
runtime.block_on(async {
crate::time::sleep(std::time::Duration::from_millis(1)).await;
});
started_rx
.recv_timeout(std::time::Duration::from_secs(2))
.expect("blocking work should start before teardown");
let start = std::time::Instant::now();
runtime.shutdown_background();
assert!(start.elapsed() < std::time::Duration::from_secs(1));
drop(release_tx);
}
#[test]
fn task_can_be_woken_from_another_thread() {
struct RemoteWake {
ready: Arc<AtomicBool>,
sent_waker: bool,
waker_tx: mpsc::Sender<Waker>,
}
impl Future for RemoteWake {
type Output = usize;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.ready.load(Ordering::Acquire) {
return Poll::Ready(11);
}
if !self.sent_waker {
self.waker_tx.send(cx.waker().clone()).expect("send waker");
self.sent_waker = true;
}
Poll::Pending
}
}
let mut runtime = Runtime::new().expect("runtime should start");
let ready = Arc::new(AtomicBool::new(false));
let (waker_tx, waker_rx) = mpsc::channel();
let task = runtime.spawn(RemoteWake {
ready: Arc::clone(&ready),
sent_waker: false,
waker_tx,
});
let wake_thread = thread::spawn(move || {
let waker = waker_rx.recv().expect("receive waker");
ready.store(true, Ordering::Release);
waker.wake();
});
let output = runtime.block_on(task);
wake_thread.join().expect("wake thread should finish");
assert_eq!(output.expect("task should succeed"), 11);
}
#[test]
fn block_on_accepts_a_borrowed_future() {
let mut runtime = Runtime::new().expect("runtime should start");
let value = String::from("borrowed");
let length = runtime.block_on(async { value.len() });
assert_eq!(length, value.len());
}
#[test]
fn root_future_wake_unparks_runtime() {
struct RemoteWake {
ready: Arc<AtomicBool>,
waker_tx: Option<mpsc::Sender<Waker>>,
}
impl Future for RemoteWake {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.ready.load(Ordering::Acquire) {
return Poll::Ready(());
}
if let Some(waker_tx) = self.waker_tx.take() {
waker_tx.send(cx.waker().clone()).expect("send root waker");
}
Poll::Pending
}
}
let mut runtime = Runtime::new().expect("runtime should start");
let ready = Arc::new(AtomicBool::new(false));
let (waker_tx, waker_rx) = mpsc::channel::<Waker>();
let wake_ready = Arc::clone(&ready);
let wake_thread = thread::spawn(move || {
let waker = waker_rx.recv().expect("receive root waker");
wake_ready.store(true, Ordering::Release);
waker.wake();
});
let _watchdog = runtime.spawn(async {
crate::time::sleep(std::time::Duration::from_secs(2)).await;
});
let start = std::time::Instant::now();
runtime.block_on(RemoteWake {
ready,
waker_tx: Some(waker_tx),
});
wake_thread.join().expect("wake thread should finish");
assert!(start.elapsed() < std::time::Duration::from_secs(1));
}
#[test]
fn same_thread_wake_stays_with_its_runtime() {
struct CapturedWake {
ready: Arc<AtomicBool>,
waker_tx: Option<mpsc::Sender<Waker>>,
}
impl Future for CapturedWake {
type Output = usize;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
if self.ready.load(Ordering::Acquire) {
return Poll::Ready(17);
}
if let Some(waker_tx) = self.waker_tx.take() {
waker_tx.send(cx.waker().clone()).expect("capture task waker");
}
Poll::Pending
}
}
let mut first = Runtime::new().expect("first runtime should start");
let mut second = Runtime::new().expect("second runtime should start");
let ready = Arc::new(AtomicBool::new(false));
let (waker_tx, waker_rx) = mpsc::channel::<Waker>();
let task = first.spawn(CapturedWake {
ready: Arc::clone(&ready),
waker_tx: Some(waker_tx),
});
first.block_on(async {});
let waker = waker_rx.recv().expect("receive captured waker");
ready.store(true, Ordering::Release);
second.block_on(async move { waker.wake_by_ref() });
assert!(!task.is_finished());
let output = first.block_on(task);
assert_eq!(output.expect("task should finish on its own runtime"), 17);
}
#[test]
fn sleep_waits_for_duration() {
use std::time::Duration;
use crate::time::{Instant, sleep};
let mut runtime = Runtime::new().expect("runtime should start");
let start = Instant::now();
runtime.block_on(async {
sleep(Duration::from_millis(50)).await;
});
assert!(start.elapsed() >= Duration::from_millis(45));
}
#[test]
fn timeout_returns_err_when_deadline_elapses() {
use crate::time::{Duration, sleep, timeout};
let mut runtime = Runtime::new().expect("runtime should start");
let result = runtime.block_on(async {
timeout(Duration::from_millis(25), async {
sleep(Duration::from_millis(100)).await;
7usize
})
.await
});
assert!(result.is_err());
}
#[test]
fn timeout_returns_ok_when_future_completes_in_time() {
use crate::time::{Duration, sleep, timeout};
let mut runtime = Runtime::new().expect("runtime should start");
let result = runtime.block_on(async {
timeout(Duration::from_millis(100), async {
sleep(Duration::from_millis(10)).await;
7usize
})
.await
});
assert_eq!(result.expect("future should complete"), 7);
}
#[test]
fn interval_first_tick_is_immediate() {
use std::time::Instant as StdInstant;
use crate::time::{Duration, interval};
let mut runtime = Runtime::new().expect("runtime should start");
let start = StdInstant::now();
runtime.block_on(async {
let mut interval = interval(Duration::from_millis(100));
interval.tick().await;
});
assert!(start.elapsed() < Duration::from_millis(50));
}
#[test]
fn interval_ticks_on_schedule() {
use crate::time::{Duration, interval};
let mut runtime = Runtime::new().expect("runtime should start");
runtime.block_on(async {
let mut interval = interval(Duration::from_millis(20));
interval.tick().await;
interval.tick().await;
});
}
#[test]
fn timeout_at_past_deadline_polls_future_once() {
use std::time::Instant as StdInstant;
use crate::time::{Duration, timeout_at};
let mut runtime = Runtime::new().expect("runtime should start");
let past = StdInstant::now() - Duration::from_secs(1);
let result = runtime.block_on(async move { timeout_at(past, async { 7usize }).await });
assert_eq!(result, Ok(7));
let result = runtime.block_on(async move { timeout_at(past, std::future::pending::<usize>()).await });
assert!(result.is_err());
}
#[test]
fn spawn_allows_non_send_future_with_send_output() {
use std::rc::Rc;
fn assert_send<T: Send>() {}
let mut runtime = Runtime::new().expect("runtime should start");
let rc = Rc::new(41usize);
let task = runtime.spawn(async move {
*rc + 1
});
assert_send::<JoinHandle<usize>>();
let output = runtime.block_on(task);
assert_eq!(output.expect("task should succeed"), 42);
}
#[test]
fn shutdown_drops_non_send_future_on_owner_thread() {
use std::rc::Rc;
struct DropThread {
dropped_on: mpsc::Sender<thread::ThreadId>,
_local: Rc<()>,
}
impl Drop for DropThread {
fn drop(&mut self) {
self.dropped_on
.send(thread::current().id())
.expect("report future drop thread");
}
}
let owner = thread::current().id();
let (dropped_on_tx, dropped_on_rx) = mpsc::channel();
let runtime = Runtime::new().expect("runtime should start");
let drop_thread = DropThread {
dropped_on: dropped_on_tx,
_local: Rc::new(()),
};
let task = runtime.spawn(async move {
let _drop_thread = drop_thread;
pending::<usize>().await
});
drop(runtime);
assert!(task.is_finished());
assert_eq!(
dropped_on_rx
.recv_timeout(std::time::Duration::from_secs(1))
.expect("future should be dropped during runtime shutdown"),
owner
);
thread::spawn(move || drop(task))
.join()
.expect("remote handle drop should succeed");
}
#[test]
fn spawn_blocking_returns_value() {
let mut runtime = Runtime::new().expect("runtime should start");
let handle = runtime.spawn_blocking(|| 42usize);
let output = runtime.block_on(handle);
assert_eq!(output.expect("blocking task should succeed"), 42);
}
#[test]
fn spawn_blocking_free_function_works_inside_runtime() {
let mut runtime = Runtime::new().expect("runtime should start");
let output = runtime.block_on(async { spawn_blocking(|| 7usize).await.expect("blocking task should succeed") });
assert_eq!(output, 7);
}
#[test]
fn spawn_blocking_reports_panics() {
let mut runtime = Runtime::new().expect("runtime should start");
let handle = runtime.spawn_blocking(|| panic!("blocking boom"));
let err = runtime.block_on(handle).expect_err("blocking panic should surface");
assert!(err.is_panic());
}
#[test]
fn spawn_blocking_does_not_starve_runtime() {
use crate::time::{Duration, Instant, sleep};
let mut runtime = Runtime::new().expect("runtime should start");
let start = Instant::now();
runtime.block_on(async move {
let blocking = spawn_blocking(|| {
std::thread::sleep(std::time::Duration::from_millis(80));
1usize
});
sleep(Duration::from_millis(20)).await;
assert!(start.elapsed() < Duration::from_millis(60));
assert_eq!(blocking.await.expect("blocking ok"), 1);
});
}
#[cfg(feature = "net")]
#[test]
fn self_waking_task_does_not_starve_io() {
use crate::{
net::tcp::TcpListener,
time::{Duration, timeout},
};
let listener =
TcpListener::bind("127.0.0.1:0".parse().expect("valid loopback address")).expect("bind loopback listener");
let address = listener.local_addr().expect("read listener address");
let connector = thread::spawn(move || {
thread::sleep(std::time::Duration::from_millis(25));
std::net::TcpStream::connect(address).expect("connect to loopback listener")
});
let mut runtime = Runtime::new().expect("runtime should start");
let accepted = runtime.block_on(async move {
let spinner = super::spawn_local(std::future::poll_fn(|cx| {
cx.waker().wake_by_ref();
Poll::<()>::Pending
}));
let accepted = timeout(Duration::from_secs(1), listener.accept()).await;
spinner.abort();
accepted
});
let _client = connector.join().expect("connector thread should finish");
let (_stream, _peer) = accepted
.expect("I/O should complete despite the runnable task")
.expect("accept should succeed");
}
#[test]
fn spawn_blocking_wakes_idle_runtime() {
use crate::time::{Duration, Instant};
let mut runtime = Runtime::new().expect("runtime should start");
let start = Instant::now();
runtime.block_on(async {
let handle = spawn_blocking(|| {
std::thread::sleep(std::time::Duration::from_millis(30));
99usize
});
assert_eq!(handle.await.expect("blocking ok"), 99);
});
assert!(start.elapsed() >= Duration::from_millis(25));
assert!(start.elapsed() < Duration::from_secs(2));
}
#[test]
fn spawn_blocking_many_concurrent_jobs() {
let mut runtime = Runtime::new().expect("runtime should start");
runtime.block_on(async {
let mut handles = Vec::new();
for i in 0..8usize {
handles.push(spawn_blocking(move || {
std::thread::sleep(std::time::Duration::from_millis(20));
i
}));
}
let mut sum = 0usize;
for h in handles {
sum += h.await.expect("job ok");
}
assert_eq!(sum, (0..8).sum());
});
}
#[cfg(feature = "fs")]
#[test]
fn blocking_submission_create_and_remove_dir() {
use std::time::{SystemTime, UNIX_EPOCH};
use crate::fs::{create_dir, remove_dir};
let mut runtime = Runtime::new().expect("runtime should start");
let nanos = SystemTime::now().duration_since(UNIX_EPOCH).expect("time").as_nanos();
let path = std::env::temp_dir().join(format!("karmaio-blocking-dir-{nanos}"));
runtime.block_on(async move {
create_dir(&path).await.expect("create_dir");
assert!(path.is_dir());
remove_dir(&path).await.expect("remove_dir");
assert!(!path.exists());
});
}
}