use std::{
sync::Arc,
thread::{self, ThreadId},
};
use async_task::Runnable;
use crossbeam::deque::{Injector, Stealer, Worker};
use dashmap::DashMap;
pub mod futures;
mod join_handle;
pub use join_handle::*;
thread_local! {
static WORK_QUEUE: Worker<Runnable> = Worker::new_fifo();
}
pub struct Runtime {
injector: Arc<Injector<Runnable>>,
stealers: Arc<DashMap<ThreadId, Stealer<Runnable>>>,
}
#[derive(Clone)]
pub struct Handle {
injector: Arc<Injector<Runnable>>,
stealers: Arc<DashMap<ThreadId, Stealer<Runnable>>>,
}
impl Runtime {
pub fn new(background_threads: usize) -> Self {
let injector: Arc<Injector<Runnable>> = Arc::new(Injector::new());
for idx in 0..background_threads {
thread::Builder::new()
.name(format!("cuckoo-background-thread-{idx}"))
.spawn({
let injector = Arc::clone(&injector);
move || {
loop {
if let Some(task) = injector.steal().success() {
task.run();
}
}
}
})
.expect("Failed to spawn background thread");
}
Self {
injector,
stealers: Arc::new(DashMap::new()),
}
}
pub fn handle(&self) -> Handle {
Handle {
injector: Arc::clone(&self.injector),
stealers: Arc::clone(&self.stealers),
}
}
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + Sync,
{
let injector = Arc::clone(&self.injector);
let schedule = move |runnable| injector.push(runnable);
let (runnable, task) = async_task::spawn(future, schedule);
runnable.schedule();
JoinHandle { task: Some(task) }
}
pub fn block_on<F>(&self, future: F) -> F::Output
where
F: Future + Send + 'static,
F::Output: Send + Sync,
{
self.handle().block_on(future)
}
}
impl Handle {
pub fn block_on<F>(&self, future: F) -> F::Output
where
F: Future + Send + 'static,
F::Output: Send + Sync,
{
let schedule = {
let stealers = Arc::clone(&self.stealers);
move |runnable| {
WORK_QUEUE.with(|q| {
let current_thread_id = thread::current().id();
if !stealers.contains_key(¤t_thread_id) {
stealers.entry(current_thread_id).or_insert(q.stealer());
}
q.push(runnable);
})
}
};
let (runnable, task) = async_task::spawn(future, schedule);
runnable.run();
while !task.is_finished() {
if let Some(stolen_task) = self.find_task() {
stolen_task.run();
}
}
::futures::executor::block_on(task)
}
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + Sync,
{
let injector = Arc::clone(&self.injector);
let schedule = move |runnable| injector.push(runnable);
let (runnable, task) = async_task::spawn(future, schedule);
runnable.schedule();
JoinHandle { task: Some(task) }
}
fn find_task(&self) -> Option<Runnable> {
WORK_QUEUE.with(|local| {
local.pop().or_else(|| {
std::iter::repeat_with(|| {
self.injector
.steal_batch_with_limit_and_pop(local, 1)
.or_else(|| self.stealers.iter().map(|s| s.steal()).collect())
})
.find(|s| !s.is_retry())
.and_then(|s| s.success())
})
})
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(0)]
#[case(1)]
fn test_basic(#[case] thread_count: usize) {
let rt = Runtime::new(thread_count);
let jh: JoinHandle<()> = rt.spawn(async move { println!("spawned task!") });
let handle = rt.handle();
let h2 = handle.clone();
handle.block_on(async move {
_ = h2.spawn(async move {
if thread_count == 0 {
panic!("This should never run");
}
});
println!("blocking here!");
println!("Awaited the second future");
});
println!("Finished block_on block");
rt.block_on(jh);
}
}