#![warn(missing_docs, missing_debug_implementations)]
mod latch;
mod multitask;
mod placement;
use latch::{Latch, LatchState};
pub use placement::{CpuSet, Placement};
use tracing::trace;
use std::{
cell::RefCell,
collections::{hash_map::Entry, BinaryHeap},
future::Future,
io,
marker::PhantomData,
pin::Pin,
rc::Rc,
sync::Arc,
task::{Context, Poll},
thread::{Builder, JoinHandle},
time::{Duration, Instant},
};
use futures_lite::pin;
use scoped_tls::scoped_thread_local;
use crate::{
error::BuilderErrorKind,
parking,
sys,
task::{self, waker_fn::dummy_waker},
GlommioError,
IoRequirements,
IoStats,
Latency,
Reactor,
Shares,
};
use ahash::AHashMap;
type Result<T> = crate::Result<T, ()>;
scoped_thread_local!(static LOCAL_EX: LocalExecutor);
pub(crate) fn executor_id() -> Option<usize> {
if LOCAL_EX.is_set() {
Some(LOCAL_EX.with(|ex| ex.id))
} else {
None
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct TaskQueueHandle {
index: usize,
}
impl Default for TaskQueueHandle {
fn default() -> Self {
TaskQueueHandle { index: 0 }
}
}
impl TaskQueueHandle {
pub fn index(&self) -> usize {
self.index
}
}
#[derive(Debug)]
pub(crate) struct TaskQueue {
pub(crate) ex: Rc<multitask::LocalExecutor>,
active: bool,
shares: Shares,
vruntime: u64,
io_requirements: IoRequirements,
name: String,
last_adjustment: Instant,
yielded: bool,
stats: TaskQueueStats,
}
impl Ord for TaskQueue {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.vruntime.cmp(&self.vruntime)
}
}
impl PartialOrd for TaskQueue {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(other.vruntime.cmp(&self.vruntime))
}
}
impl PartialEq for TaskQueue {
fn eq(&self, other: &Self) -> bool {
self.vruntime == other.vruntime
}
}
impl Eq for TaskQueue {}
impl TaskQueue {
fn new<S>(
index: TaskQueueHandle,
name: S,
shares: Shares,
ioreq: IoRequirements,
) -> Rc<RefCell<Self>>
where
S: Into<String>,
{
Rc::new(RefCell::new(TaskQueue {
ex: Rc::new(multitask::LocalExecutor::new()),
active: false,
stats: TaskQueueStats::new(index, shares.reciprocal_shares()),
shares,
vruntime: 0,
io_requirements: ioreq,
name: name.into(),
last_adjustment: Instant::now(),
yielded: false,
}))
}
fn is_active(&self) -> bool {
self.active
}
fn get_task(&mut self) -> Option<multitask::Runnable> {
self.ex.get_task()
}
fn yielded(&self) -> bool {
self.yielded
}
fn prepare_to_run(&mut self, now: Instant) {
self.yielded = false;
if let Shares::Dynamic(bm) = &self.shares {
if now.saturating_duration_since(self.last_adjustment) > bm.adjustment_period() {
self.last_adjustment = now;
self.stats.reciprocal_shares = self.shares.reciprocal_shares();
}
}
}
fn account_vruntime(&mut self, delta: Duration) -> Option<u64> {
let delta_scaled = (self.stats.reciprocal_shares * (delta.as_nanos() as u64)) >> 12;
self.stats.runtime += delta;
self.stats.queue_selected += 1;
self.active = self.ex.is_active();
let vruntime = self.vruntime.checked_add(delta_scaled);
if let Some(x) = vruntime {
self.vruntime = x;
}
vruntime
}
}
fn bind_to_cpu_set(cpus: impl IntoIterator<Item = usize>) -> Result<()> {
let mut cpuset = nix::sched::CpuSet::new();
for cpu in cpus {
cpuset.set(cpu).map_err(|e| to_io_error!(e))?;
}
let pid = nix::unistd::Pid::from_raw(0);
nix::sched::sched_setaffinity(pid, &cpuset).map_err(|e| Into::into(to_io_error!(e)))
}
#[derive(Debug, Copy, Clone)]
pub struct ExecutorStats {
executor_runtime: Duration,
total_runtime: Duration,
scheduler_runs: u64,
tasks_executed: u64,
}
impl ExecutorStats {
fn new() -> Self {
Self {
executor_runtime: Duration::from_nanos(0),
total_runtime: Duration::from_nanos(0),
scheduler_runs: 0,
tasks_executed: 0,
}
}
pub fn executor_runtime(&self) -> Duration {
self.executor_runtime
}
pub fn total_runtime(&self) -> Duration {
self.total_runtime
}
pub fn scheduler_runs(&self) -> u64 {
self.scheduler_runs
}
pub fn tasks_executed(&self) -> u64 {
self.tasks_executed
}
}
#[derive(Debug, Copy, Clone)]
pub struct TaskQueueStats {
index: TaskQueueHandle,
reciprocal_shares: u64,
queue_selected: u64,
runtime: Duration,
}
impl TaskQueueStats {
fn new(index: TaskQueueHandle, reciprocal_shares: u64) -> Self {
Self {
index,
reciprocal_shares,
runtime: Duration::from_nanos(0),
queue_selected: 0,
}
}
pub fn index(&self) -> TaskQueueHandle {
self.index
}
pub fn current_shares(&self) -> usize {
((1u64 << 22) / self.reciprocal_shares) as usize
}
pub fn runtime(&self) -> Duration {
self.runtime
}
pub fn queue_selected(&self) -> u64 {
self.queue_selected
}
}
#[derive(Debug)]
struct ExecutorQueues {
active_executors: BinaryHeap<Rc<RefCell<TaskQueue>>>,
available_executors: AHashMap<usize, Rc<RefCell<TaskQueue>>>,
active_executing: Option<Rc<RefCell<TaskQueue>>>,
executor_index: usize,
last_vruntime: u64,
preempt_timer_duration: Duration,
default_preempt_timer_duration: Duration,
spin_before_park: Option<Duration>,
stats: ExecutorStats,
}
impl ExecutorQueues {
fn new(preempt_timer_duration: Duration, spin_before_park: Option<Duration>) -> Self {
ExecutorQueues {
active_executors: BinaryHeap::new(),
available_executors: AHashMap::new(),
active_executing: None,
executor_index: 1, last_vruntime: 0,
preempt_timer_duration,
default_preempt_timer_duration: preempt_timer_duration,
spin_before_park,
stats: ExecutorStats::new(),
}
}
fn reevaluate_preempt_timer(&mut self) {
self.preempt_timer_duration = self
.active_executors
.iter()
.map(|tq| match tq.borrow().io_requirements.latency_req {
Latency::NotImportant => self.default_preempt_timer_duration,
Latency::Matters(d) => d,
})
.min()
.unwrap_or(self.default_preempt_timer_duration)
}
fn maybe_activate(&mut self, queue: Rc<RefCell<TaskQueue>>) {
let mut state = queue.borrow_mut();
if !state.is_active() {
state.vruntime = self.last_vruntime;
state.active = true;
drop(state);
self.active_executors.push(queue);
self.reevaluate_preempt_timer();
}
}
}
#[derive(Debug)]
pub struct LocalExecutorBuilder {
binding: Option<usize>,
spin_before_park: Option<Duration>,
name: String,
io_memory: usize,
preempt_timer_duration: Duration,
}
impl LocalExecutorBuilder {
pub fn new() -> LocalExecutorBuilder {
LocalExecutorBuilder {
binding: None,
spin_before_park: None,
name: String::from("unnamed"),
io_memory: 10 << 20,
preempt_timer_duration: Duration::from_millis(100),
}
}
pub fn pin_to_cpu(mut self, cpu: usize) -> LocalExecutorBuilder {
self.binding = Some(cpu);
self
}
pub fn spin_before_park(mut self, spin: Duration) -> LocalExecutorBuilder {
self.spin_before_park = Some(spin);
self
}
pub fn name(mut self, name: &str) -> LocalExecutorBuilder {
self.name = String::from(name);
self
}
pub fn io_memory(mut self, io_memory: usize) -> LocalExecutorBuilder {
self.io_memory = io_memory;
self
}
pub fn preempt_timer(mut self, dur: Duration) -> LocalExecutorBuilder {
self.preempt_timer_duration = dur;
self
}
pub fn make(self) -> Result<LocalExecutor> {
let notifier = sys::new_sleep_notifier()?;
let mut le = LocalExecutor::new(
notifier,
self.io_memory,
self.preempt_timer_duration,
self.binding.map(Some),
self.spin_before_park,
)?;
le.init();
Ok(le)
}
#[must_use = "This spawns an executor on a thread, so you may need to call \
`JoinHandle::join()` to keep the main thread alive"]
pub fn spawn<G, F, T>(self, fut_gen: G) -> Result<JoinHandle<()>>
where
G: FnOnce() -> F + Send + 'static,
F: Future<Output = T> + 'static,
{
let notifier = sys::new_sleep_notifier()?;
let name = format!("{}-{}", self.name, notifier.id());
Builder::new()
.name(name)
.spawn(move || {
let mut le = LocalExecutor::new(
notifier,
self.io_memory,
self.preempt_timer_duration,
self.binding.map(Some),
self.spin_before_park,
)
.unwrap();
le.init();
le.run(async move {
fut_gen().await;
})
})
.map_err(Into::into)
}
}
impl Default for LocalExecutorBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct LocalExecutorPoolBuilder {
nr_shards: usize,
spin_before_park: Option<Duration>,
name: String,
io_memory: usize,
preempt_timer_duration: Duration,
placement: Placement,
}
impl LocalExecutorPoolBuilder {
pub fn new(nr_shards: usize) -> Self {
Self {
nr_shards,
spin_before_park: None,
name: String::from("unnamed"),
io_memory: 10 << 20,
preempt_timer_duration: Duration::from_millis(100),
placement: Placement::Unbound,
}
}
pub fn spin_before_park(mut self, spin: Duration) -> Self {
self.spin_before_park = Some(spin);
self
}
pub fn name(mut self, name: &str) -> Self {
self.name = String::from(name);
self
}
pub fn io_memory(mut self, io_memory: usize) -> Self {
self.io_memory = io_memory;
self
}
pub fn preempt_timer(mut self, dur: Duration) -> Self {
self.preempt_timer_duration = dur;
self
}
pub fn placement(mut self, p: Placement) -> Self {
self.placement = p;
self
}
#[must_use = "This spawns executors on multiple threads; threads may fail to spawn or you may \
need to call `PoolThreadHandles::join_all()` to keep the main thread alive"]
pub fn on_all_shards<G, F, T>(mut self, fut_gen: G) -> Result<PoolThreadHandles<T>>
where
G: FnOnce() -> F + Clone + Send + 'static,
F: Future<Output = T> + 'static,
T: Send + 'static,
{
let mut handles = PoolThreadHandles::new();
let placement = std::mem::take(&mut self.placement);
let mut cpu_set_gen = placement::CpuSetGenerator::new(placement, self.nr_shards)?;
let latch = Latch::new(self.nr_shards);
for _ in 0..self.nr_shards {
match self.spawn_thread(&mut cpu_set_gen, &latch, fut_gen.clone()) {
Ok(handle) => handles.push(handle),
Err(err) => {
handles.join_all();
return Err(err);
}
}
}
Ok(handles)
}
fn spawn_thread<G, F, T>(
&self,
cpu_set_gen: &mut placement::CpuSetGenerator,
latch: &Latch,
fut_gen: G,
) -> Result<JoinHandle<Result<T>>>
where
G: FnOnce() -> F + Clone + Send + 'static,
F: Future<Output = T> + 'static,
T: Send + 'static,
{
let cpu_binding = cpu_set_gen.next().cpu_binding();
let notifier = sys::new_sleep_notifier()?;
let name = format!("{}-{}", &self.name, notifier.id());
let handle = Builder::new().name(name).spawn({
let io_memory = self.io_memory;
let preempt_timer_duration = self.preempt_timer_duration;
let spin_before_park = self.spin_before_park;
let latch = Latch::clone(latch);
move || {
if latch.arrive_and_wait() == LatchState::Ready {
let mut le = LocalExecutor::new(
notifier,
io_memory,
preempt_timer_duration,
cpu_binding,
spin_before_park,
)
.unwrap();
le.init();
le.run(async move { Ok(fut_gen().await) })
} else {
Err(io::Error::new(io::ErrorKind::Other, "spawn failed").into())
}
}
});
match handle {
Ok(h) => Ok(h),
Err(e) => {
latch.cancel().expect("unreachable: latch was ready");
Err(e.into())
}
}
}
}
#[derive(Debug)]
pub struct PoolThreadHandles<T> {
handles: Vec<JoinHandle<Result<T>>>,
}
impl<T> PoolThreadHandles<T> {
fn new() -> Self {
Self {
handles: Vec::new(),
}
}
fn push(&mut self, handle: JoinHandle<Result<T>>) {
self.handles.push(handle)
}
pub fn handles(&self) -> &Vec<JoinHandle<Result<T>>> {
&self.handles
}
pub fn join_all(self) -> Vec<Result<T>> {
self.handles
.into_iter()
.map(|h| {
match h.join() {
Ok(ok @ Ok(_)) => ok,
Ok(err @ Err(_)) => err,
Err(e) => Err(GlommioError::BuilderError(BuilderErrorKind::ThreadPanic(e))),
}
})
.collect::<Vec<_>>()
}
}
pub(crate) fn maybe_activate(tq: Rc<RefCell<TaskQueue>>) {
LOCAL_EX.with(|local_ex| {
let mut queues = local_ex.queues.borrow_mut();
queues.maybe_activate(tq)
})
}
#[derive(Debug)]
pub struct LocalExecutor {
queues: Rc<RefCell<ExecutorQueues>>,
parker: parking::Parker,
id: usize,
reactor: Rc<parking::Reactor>,
}
impl LocalExecutor {
fn get_reactor(&self) -> Rc<Reactor> {
self.reactor.clone()
}
fn init(&mut self) {
let io_requirements = IoRequirements::new(Latency::NotImportant, 0);
self.queues.borrow_mut().available_executors.insert(
0,
TaskQueue::new(
Default::default(),
"default",
Shares::Static(1000),
io_requirements,
),
);
}
fn new(
notifier: Arc<sys::SleepNotifier>,
io_memory: usize,
preempt_timer: Duration,
cpu_binding: Option<impl IntoIterator<Item = usize>>,
mut spin_before_park: Option<Duration>,
) -> Result<LocalExecutor> {
match cpu_binding {
Some(cpu_set) => bind_to_cpu_set(cpu_set)?,
None => spin_before_park = None,
}
let p = parking::Parker::new();
let queues = ExecutorQueues::new(preempt_timer, spin_before_park);
trace!(id = notifier.id(), "Creating executor");
Ok(LocalExecutor {
queues: Rc::new(RefCell::new(queues)),
parker: p,
id: notifier.id(),
reactor: Rc::new(parking::Reactor::new(notifier, io_memory)),
})
}
pub fn id(&self) -> usize {
self.id
}
fn create_task_queue<S>(&self, shares: Shares, latency: Latency, name: S) -> TaskQueueHandle
where
S: Into<String>,
{
let index = {
let mut ex = self.queues.borrow_mut();
let index = ex.executor_index;
ex.executor_index += 1;
index
};
let io_requirements = IoRequirements::new(latency, index);
let tq = TaskQueue::new(TaskQueueHandle { index }, name, shares, io_requirements);
self.queues
.borrow_mut()
.available_executors
.insert(index, tq);
TaskQueueHandle { index }
}
pub fn remove_task_queue(&self, handle: TaskQueueHandle) -> Result<()> {
let mut queues = self.queues.borrow_mut();
let queue_entry = queues.available_executors.entry(handle.index);
if let Entry::Occupied(entry) = queue_entry {
let tq = entry.get();
if tq.borrow().is_active() {
return Err(GlommioError::queue_still_active(handle.index));
}
entry.remove();
return Ok(());
}
Err(GlommioError::queue_not_found(handle.index))
}
fn get_queue(&self, handle: &TaskQueueHandle) -> Option<Rc<RefCell<TaskQueue>>> {
self.queues
.borrow()
.available_executors
.get(&handle.index)
.cloned()
}
fn current_task_queue(&self) -> TaskQueueHandle {
self.queues
.borrow()
.active_executing
.as_ref()
.unwrap()
.borrow()
.stats
.index
}
fn mark_me_for_yield(&self) {
let queues = self.queues.borrow();
let mut me = queues.active_executing.as_ref().unwrap().borrow_mut();
me.yielded = true;
}
fn spawn<T>(&self, future: impl Future<Output = T>) -> multitask::Task<T> {
let tq = self
.queues
.borrow()
.active_executing
.clone() .or_else(|| self.get_queue(&TaskQueueHandle { index: 0 }))
.unwrap();
let id = self.id;
let ex = tq.borrow().ex.clone();
ex.spawn_and_run(id, tq, future)
}
fn spawn_into<T, F>(&self, future: F, handle: TaskQueueHandle) -> Result<multitask::Task<T>>
where
F: Future<Output = T>,
{
let tq = self
.get_queue(&handle)
.ok_or_else(|| GlommioError::queue_not_found(handle.index))?;
let ex = tq.borrow().ex.clone();
let id = self.id;
Ok(ex.spawn_and_schedule(id, tq, future))
}
fn preempt_timer_duration(&self) -> Duration {
self.queues.borrow().preempt_timer_duration
}
fn spin_before_park(&self) -> Option<Duration> {
self.queues.borrow().spin_before_park
}
#[inline(always)]
pub(crate) fn need_preempt(&self) -> bool {
self.reactor.need_preempt()
}
fn run_task_queues(&self) -> bool {
let mut ran = false;
while !self.need_preempt() {
if !self.run_one_task_queue() {
return false;
} else {
ran = true;
}
}
ran
}
fn run_one_task_queue(&self) -> bool {
let mut tq = self.queues.borrow_mut();
let candidate = tq.active_executors.pop();
tq.stats.scheduler_runs += 1;
match candidate {
Some(queue) => {
tq.active_executing = Some(queue.clone());
drop(tq);
let time = {
let now = Instant::now();
let mut queue_ref = queue.borrow_mut();
queue_ref.prepare_to_run(now);
self.reactor
.inform_io_requirements(queue_ref.io_requirements);
now
};
let mut tasks_executed_this_loop = 0;
loop {
let mut queue_ref = queue.borrow_mut();
if self.need_preempt() || queue_ref.yielded() {
break;
}
if let Some(r) = queue_ref.get_task() {
drop(queue_ref);
r.run();
tasks_executed_this_loop += 1;
} else {
break;
}
}
let runtime = time.elapsed();
let (need_repush, last_vruntime) = {
let mut state = queue.borrow_mut();
let last_vruntime = state.account_vruntime(runtime);
(state.is_active(), last_vruntime)
};
let mut tq = self.queues.borrow_mut();
tq.active_executing = None;
tq.stats.executor_runtime += runtime;
tq.stats.tasks_executed += tasks_executed_this_loop;
tq.last_vruntime = match last_vruntime {
Some(x) => x,
None => {
for queue in tq.available_executors.values() {
let mut q = queue.borrow_mut();
q.vruntime = 0;
}
0
}
};
if need_repush {
tq.active_executors.push(queue);
} else {
tq.reevaluate_preempt_timer();
}
true
}
None => false,
}
}
pub fn run<T>(&self, future: impl Future<Output = T>) -> T {
let waker = dummy_waker();
let cx = &mut Context::from_waker(&waker);
let spin_before_park = self.spin_before_park().unwrap_or_default();
if LOCAL_EX.is_set() {
panic!("There is already an Executor running in this thread");
}
LOCAL_EX.set(self, || {
let future = self
.spawn_into(async move { future.await }, TaskQueueHandle::default())
.unwrap()
.detach();
pin!(future);
let mut pre_time = Instant::now();
loop {
if let Poll::Ready(t) = future.as_mut().poll(cx) {
let cur_time = Instant::now();
self.queues.borrow_mut().stats.total_runtime += cur_time - pre_time;
break t.unwrap();
}
let duration = self.preempt_timer_duration();
self.parker.poll_io(duration);
let run = self.run_task_queues();
let cur_time = Instant::now();
self.queues.borrow_mut().stats.total_runtime += cur_time - pre_time;
pre_time = cur_time;
if !run {
if let Poll::Ready(t) = future.as_mut().poll(cx) {
break t.unwrap();
} else {
while !self.reactor.spin_poll_io().unwrap() {
if pre_time.elapsed() > spin_before_park {
self.parker.park();
break;
}
}
pre_time = Instant::now();
}
}
}
})
}
}
impl Default for LocalExecutor {
fn default() -> Self {
LocalExecutorBuilder::new().make().unwrap()
}
}
#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"]
#[derive(Debug)]
pub struct Task<T>(multitask::Task<T>);
impl<T> Task<T> {
pub fn local(future: impl Future<Output = T> + 'static) -> Task<T>
where
T: 'static,
{
LOCAL_EX.with(|local_ex| Self(local_ex.spawn(future)))
}
pub async fn later() {
Self::cond_yield(|_| true).await
}
async fn cond_yield<F>(cond: F)
where
F: FnOnce(&LocalExecutor) -> bool,
{
let need_yield = LOCAL_EX.with(|local_ex| {
if cond(local_ex) {
local_ex.mark_me_for_yield();
true
} else {
false
}
});
if need_yield {
futures_lite::future::yield_now().await;
}
}
#[inline(always)]
pub fn need_preempt() -> bool {
LOCAL_EX.with(|local_ex| local_ex.need_preempt())
}
#[inline]
pub async fn yield_if_needed() {
Self::cond_yield(|local_ex| local_ex.need_preempt()).await;
}
#[inline]
pub(crate) fn get_reactor() -> Rc<parking::Reactor> {
LOCAL_EX.with(|local_ex| local_ex.get_reactor())
}
pub fn local_into(
future: impl Future<Output = T> + 'static,
handle: TaskQueueHandle,
) -> Result<Task<T>>
where
T: 'static,
{
LOCAL_EX.with(|local_ex| local_ex.spawn_into(future, handle).map(Self))
}
pub fn id() -> usize
where
T: 'static,
{
LOCAL_EX.with(|local_ex| local_ex.id())
}
pub fn detach(self) -> task::JoinHandle<T> {
self.0.detach()
}
pub fn create_task_queue(shares: Shares, latency: Latency, name: &str) -> TaskQueueHandle {
LOCAL_EX.with(|local_ex| local_ex.create_task_queue(shares, latency, name))
}
pub fn current_task_queue() -> TaskQueueHandle {
LOCAL_EX.with(|local_ex| local_ex.current_task_queue())
}
pub fn task_queue_stats(handle: TaskQueueHandle) -> Result<TaskQueueStats> {
LOCAL_EX.with(|local_ex| match local_ex.get_queue(&handle) {
Some(x) => Ok(x.borrow().stats),
None => Err(GlommioError::queue_not_found(handle.index)),
})
}
pub fn all_task_queue_stats<V>(mut output: V) -> V
where
V: Extend<TaskQueueStats>,
{
LOCAL_EX.with(|local_ex| {
let tq = local_ex.queues.borrow();
output.extend(tq.available_executors.values().map(|x| x.borrow().stats));
output
})
}
pub fn executor_stats() -> ExecutorStats {
LOCAL_EX.with(|local_ex| local_ex.queues.borrow().stats)
}
pub fn io_stats() -> IoStats {
LOCAL_EX.with(|local_ex| local_ex.get_reactor().io_stats())
}
pub fn task_queue_io_stats(handle: TaskQueueHandle) -> Result<IoStats> {
LOCAL_EX.with(
|local_ex| match local_ex.get_reactor().task_queue_io_stats(&handle) {
Some(x) => Ok(x),
None => Err(GlommioError::queue_not_found(handle.index)),
},
)
}
pub async fn cancel(self) -> Option<T> {
self.0.cancel().await
}
}
impl<T> Future for Task<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
#[must_use = "scoped tasks get canceled when dropped, use a standard Task and `.detach()` to run \
them in the background"]
#[derive(Debug)]
pub struct ScopedTask<'a, T>(multitask::Task<T>, PhantomData<&'a T>);
impl<'a, T> ScopedTask<'a, T> {
pub unsafe fn local(future: impl Future<Output = T> + 'a) -> Self {
LOCAL_EX.with(|local_ex| Self(local_ex.spawn(future), PhantomData))
}
pub unsafe fn local_into(
future: impl Future<Output = T> + 'a,
handle: TaskQueueHandle,
) -> Result<Self> {
LOCAL_EX.with(|local_ex| {
local_ex
.spawn_into(future, handle)
.map(|x| Self(x, PhantomData))
})
}
pub async fn cancel(self) -> Option<T> {
self.0.cancel().await
}
}
impl<'a, T> Future for ScopedTask<'a, T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
enclose,
timer::{self, sleep, Timer},
Local,
SharesManager,
};
use core::mem::MaybeUninit;
use futures::{
future::{join_all, poll_fn},
join,
};
use std::{
cell::Cell,
collections::HashMap,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
Mutex,
},
task::Waker,
};
#[test]
fn create_and_destroy_executor() {
let mut var = Rc::new(RefCell::new(0));
let local_ex = LocalExecutor::default();
let varclone = var.clone();
local_ex.run(async move {
let mut m = varclone.borrow_mut();
*m += 10;
});
let v = Rc::get_mut(&mut var).unwrap();
let v = v.replace(0);
assert_eq!(v, 10);
}
#[test]
fn create_fail_to_bind() {
if LocalExecutorBuilder::new()
.pin_to_cpu(usize::MAX)
.make()
.is_ok()
{
unreachable!("Should have failed");
}
}
#[test]
fn bind_to_cpu_set_range() {
assert!(bind_to_cpu_set(vec![0, 1, 2, 3]).is_ok());
assert!(bind_to_cpu_set(0..1024).is_ok());
assert!(bind_to_cpu_set(0..1025).is_err());
}
#[test]
fn create_and_bind() {
if let Err(x) = LocalExecutorBuilder::new().pin_to_cpu(0).make() {
panic!("got error {:?}", x);
}
}
#[test]
#[should_panic]
fn spawn_without_executor() {
let _ = LocalExecutor::default();
let _ = Task::local(async move {});
}
#[test]
fn invalid_task_queue() {
let local_ex = LocalExecutor::default();
local_ex.run(async {
let task = Task::local_into(
async move {
unreachable!("Should not have executed this");
},
TaskQueueHandle { index: 1 },
);
if task.is_ok() {
unreachable!("Should have failed");
}
});
}
#[test]
fn ten_yielding_queues() {
let local_ex = LocalExecutor::default();
let executed_last = Rc::new(RefCell::new(0));
local_ex.run(async {
let mut joins = Vec::with_capacity(10);
for id in 1..11 {
let exec = executed_last.clone();
joins.push(Task::local(async move {
for _ in 0..10_000 {
let mut last = exec.borrow_mut();
assert_ne!(id, *last);
*last = id;
drop(last);
Local::later().await;
}
}));
}
futures::future::join_all(joins).await;
});
}
#[test]
fn task_with_latency_requirements() {
let local_ex = LocalExecutor::default();
local_ex.run(async {
let not_latency =
Local::create_task_queue(Shares::default(), Latency::NotImportant, "test");
let latency = Local::create_task_queue(
Shares::default(),
Latency::Matters(Duration::from_millis(2)),
"testlat",
);
let nolat_started = Rc::new(RefCell::new(false));
let lat_status = Rc::new(RefCell::new(false));
let nolat = local_ex
.spawn_into(
crate::enclose! { (nolat_started, lat_status)
async move {
*(nolat_started.borrow_mut()) = true;
let start = Instant::now();
loop {
if *(lat_status.borrow()) {
break; }
if start.elapsed().as_secs() > 1 {
panic!("Never received preempt signal");
}
Local::yield_if_needed().await;
}
}
},
not_latency,
)
.unwrap();
let lat = local_ex
.spawn_into(
crate::enclose! { (nolat_started, lat_status)
async move {
loop {
if !(*(nolat_started.borrow())) {
Local::later().await;
} else {
break;
}
}
*(lat_status.borrow_mut()) = true;
}
},
latency,
)
.unwrap();
futures::join!(nolat, lat);
});
}
#[test]
fn current_task_queue_matches() {
let local_ex = LocalExecutor::default();
local_ex.run(async {
let tq1 = Local::create_task_queue(Shares::default(), Latency::NotImportant, "test1");
let tq2 = Local::create_task_queue(Shares::default(), Latency::NotImportant, "test2");
let id1 = tq1.index;
let id2 = tq2.index;
let j0 = Local::local(async {
assert_eq!(Local::current_task_queue().index, 0);
});
let j1 = Local::local_into(
async move {
assert_eq!(Local::current_task_queue().index, id1);
},
tq1,
)
.unwrap();
let j2 = Local::local_into(
async move {
assert_eq!(Local::current_task_queue().index, id2);
},
tq2,
)
.unwrap();
futures::join!(j0, j1, j2);
})
}
#[test]
fn task_optimized_for_throughput() {
let local_ex = LocalExecutor::default();
local_ex.run(async {
let tq1 = Local::create_task_queue(Shares::default(), Latency::NotImportant, "test");
let tq2 = Local::create_task_queue(Shares::default(), Latency::NotImportant, "testlat");
let first_started = Rc::new(RefCell::new(false));
let second_status = Rc::new(RefCell::new(false));
let first = local_ex
.spawn_into(
crate::enclose! { (first_started, second_status)
async move {
*(first_started.borrow_mut()) = true;
let start = Instant::now();
loop {
if start.elapsed().as_millis() >= 99 {
break;
}
if *(second_status.borrow()) {
panic!("I was preempted but should not have been");
}
Local::yield_if_needed().await;
}
}
},
tq1,
)
.unwrap();
let second = local_ex
.spawn_into(
crate::enclose! { (first_started, second_status)
async move {
loop {
if !(*(first_started.borrow())) {
Local::later().await;
} else {
break;
}
}
*(second_status.borrow_mut()) = true;
}
},
tq2,
)
.unwrap();
futures::join!(first, second);
});
}
#[test]
fn test_detach() {
let ex = LocalExecutor::default();
ex.run(async {
Local::local(async {
loop {
Local::later().await;
}
})
.detach();
Timer::new(Duration::from_micros(100)).await;
});
}
fn from_timeval(v: libc::timeval) -> Duration {
Duration::from_secs(v.tv_sec as u64) + Duration::from_micros(v.tv_usec as u64)
}
fn getrusage() -> libc::rusage {
let mut s0 = MaybeUninit::<libc::rusage>::uninit();
let err = unsafe { libc::getrusage(libc::RUSAGE_THREAD, s0.as_mut_ptr()) };
if err != 0 {
panic!("getrusage error = {}", err);
}
unsafe { s0.assume_init() }
}
fn getrusage_utime() -> Duration {
from_timeval(getrusage().ru_utime)
}
#[test]
fn test_no_spin() {
let ex = LocalExecutor::default();
let task_queue = ex.create_task_queue(
Shares::default(),
Latency::Matters(Duration::from_millis(10)),
"my_tq",
);
let start = getrusage_utime();
ex.run(async {
Local::local_into(
async { timer::sleep(Duration::from_secs(1)).await },
task_queue,
)
.expect("failed to spawn task")
.await;
});
assert!(
getrusage_utime() - start < Duration::from_millis(2),
"expected user time on LE is less than 2 millisecond"
);
}
#[test]
fn test_spin() {
let dur = Duration::from_secs(1);
let ex0 = LocalExecutorBuilder::new().make().unwrap();
let ex0_ru_start = getrusage_utime();
ex0.run(async { timer::sleep(dur).await });
let ex0_ru_finish = getrusage_utime();
let ex = LocalExecutorBuilder::new()
.pin_to_cpu(0)
.spin_before_park(Duration::from_millis(100))
.make()
.unwrap();
let ex_ru_start = getrusage_utime();
ex.run(async {
Local::local(async move { timer::sleep(dur).await }).await;
});
let ex_ru_finish = getrusage_utime();
assert!(
ex0_ru_finish - ex0_ru_start < Duration::from_millis(10),
"expected user time on LE0 is less than 10 millisecond"
);
assert!(
ex_ru_finish - ex_ru_start >= Duration::from_millis(50),
"expected user time on LE is much greater than 50 millisecond"
);
}
#[test]
fn test_runtime_stats() {
let dur = Duration::from_secs(2);
let ex0 = LocalExecutorBuilder::new().make().unwrap();
ex0.run(async {
assert!(
Local::executor_stats().total_runtime() < Duration::from_nanos(10),
"expected runtime on LE {:#?} is less than 10 ns",
Local::executor_stats().total_runtime()
);
let now = Instant::now();
while now.elapsed().as_millis() < 200 {}
Local::later().await;
assert!(
Local::executor_stats().total_runtime() >= Duration::from_millis(200),
"expected runtime on LE0 {:#?} is greater than 200 ms",
Local::executor_stats().total_runtime()
);
timer::sleep(dur).await;
assert!(
Local::executor_stats().total_runtime() < Duration::from_millis(400),
"expected runtime on LE0 {:#?} is not greater than 400 ms",
Local::executor_stats().total_runtime()
);
});
let ex = LocalExecutorBuilder::new()
.pin_to_cpu(0)
.spin_before_park(Duration::from_secs(5))
.make()
.unwrap();
ex.run(async {
Local::local(async move {
assert!(
Local::executor_stats().total_runtime() < Duration::from_nanos(10),
"expected runtime on LE {:#?} is less than 10 ns",
Local::executor_stats().total_runtime()
);
let now = Instant::now();
while now.elapsed().as_millis() < 200 {}
Local::later().await;
assert!(
Local::executor_stats().total_runtime() >= Duration::from_millis(200),
"expected runtime on LE {:#?} is greater than 200 ms",
Local::executor_stats().total_runtime()
);
timer::sleep(dur).await;
assert!(
Local::executor_stats().total_runtime() < Duration::from_millis(400),
"expected runtime on LE {:#?} is not greater than 400 ms",
Local::executor_stats().total_runtime()
);
})
.await;
});
}
async fn work_quanta() {
let now = Instant::now();
while now.elapsed().as_millis() < 2 {}
Local::later().await;
}
macro_rules! test_static_shares {
( $s1:expr, $s2:expr, $work:block ) => {
let local_ex = LocalExecutor::default();
local_ex.run(async {
let tq1 = Local::create_task_queue(
Shares::Static($s1),
Latency::Matters(Duration::from_millis(1)),
"test_1",
);
let tq2 = Local::create_task_queue(
Shares::Static($s2),
Latency::Matters(Duration::from_millis(1)),
"test_2",
);
let tq1_count = Rc::new(Cell::new(0));
let tq2_count = Rc::new(Cell::new(0));
let now = Instant::now();
let t1 = Local::local_into(
enclose! { (tq1_count, now) async move {
while now.elapsed().as_secs() < 5 {
$work;
tq1_count.replace(tq1_count.get() + 1);
}
}},
tq1,
)
.unwrap();
let t2 = Local::local_into(
enclose! { (tq2_count, now ) async move {
while now.elapsed().as_secs() < 5 {
$work;
tq2_count.replace(tq2_count.get() + 1);
}
}},
tq2,
)
.unwrap();
join!(t1, t2);
let expected_ratio = $s2 as f64 / (($s2 + $s1) as f64);
let actual_ratio =
tq2_count.get() as f64 / ((tq1_count.get() + tq2_count.get()) as f64);
assert!((expected_ratio - actual_ratio).abs() < 0.1);
});
};
}
#[test]
fn test_shares_high_disparity_fat_task() {
test_static_shares!(1000, 10, { work_quanta().await });
}
#[test]
fn test_shares_low_disparity_fat_task() {
test_static_shares!(1000, 1000, { work_quanta().await });
}
struct DynamicSharesTest {
shares: Cell<usize>,
}
impl DynamicSharesTest {
fn new() -> Rc<Self> {
Rc::new(Self {
shares: Cell::new(0),
})
}
fn tick(&self, millis: u64) {
if millis < 1000 {
self.shares.replace(1);
} else {
self.shares.replace(1000);
}
}
}
impl SharesManager for DynamicSharesTest {
fn shares(&self) -> usize {
self.shares.get()
}
fn adjustment_period(&self) -> Duration {
Duration::from_millis(1)
}
}
#[test]
fn test_dynamic_shares() {
let local_ex = LocalExecutor::default();
local_ex.run(async {
let bm = DynamicSharesTest::new();
let tq1 = Local::create_task_queue(
Shares::Static(1000),
Latency::Matters(Duration::from_millis(1)),
"test_1",
);
let tq2 = Local::create_task_queue(
Shares::Dynamic(bm.clone()),
Latency::Matters(Duration::from_millis(1)),
"test_2",
);
let tq1_count = Rc::new(RefCell::new(vec![0, 0]));
let tq2_count = Rc::new(RefCell::new(vec![0, 0]));
let now = Instant::now();
let t1 = Local::local_into(
enclose! { (tq1_count, now) async move {
loop {
let secs = now.elapsed().as_secs();
if secs >= 2 {
break;
}
(*tq1_count.borrow_mut())[secs as usize] += 1;
Local::later().await;
}
}},
tq1,
)
.unwrap();
let t2 = Local::local_into(
enclose! { (tq2_count, now, bm) async move {
loop {
let elapsed = now.elapsed();
let secs = elapsed.as_secs();
if secs >= 2 {
break;
}
bm.tick(elapsed.as_millis() as u64);
(*tq2_count.borrow_mut())[secs as usize] += 1;
Local::later().await;
}
}},
tq2,
)
.unwrap();
join!(t1, t2);
let ratios: Vec<f64> = tq1_count
.borrow()
.iter()
.zip(tq2_count.borrow().iter())
.map(|(x, y)| *y as f64 / *x as f64)
.collect();
assert!(ratios[1] > ratios[0]);
assert!(ratios[0] < 0.25);
assert!(ratios[1] > 0.50);
});
}
#[test]
fn multiple_spawn() {
LocalExecutor::default().run(async {
Local::local(async {}).detach().await;
Local::local(async {}).detach().await;
});
}
#[test]
#[should_panic(expected = "Message!")]
fn panic_is_not_list() {
LocalExecutor::default().run(async { panic!("Message!") });
}
struct TestFuture {
w: Arc<Mutex<Option<Waker>>>,
}
impl Future for TestFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut w = self.w.lock().unwrap();
match w.take() {
Some(_) => Poll::Ready(()),
None => {
*w = Some(cx.waker().clone());
Poll::Pending
}
}
}
}
#[test]
fn cross_executor_wake_by_ref() {
let w = Arc::new(Mutex::new(None));
let t = w.clone();
let fut = TestFuture { w };
let ex1 = LocalExecutorBuilder::new()
.spawn(|| async move {
fut.await;
})
.unwrap();
let ex2 = LocalExecutorBuilder::new()
.spawn(|| async move {
loop {
sleep(Duration::from_secs(1)).await;
let w = t.lock().unwrap();
if let Some(ref x) = *w {
x.wake_by_ref();
return;
}
}
})
.unwrap();
ex1.join().unwrap();
ex2.join().unwrap();
}
#[test]
fn cross_executor_wake_by_value() {
let w = Arc::new(Mutex::new(None));
let t = w.clone();
let fut = TestFuture { w };
let ex1 = LocalExecutorBuilder::new()
.spawn(|| async move {
fut.await;
})
.unwrap();
let ex2 = LocalExecutorBuilder::new()
.spawn(|| async move {
loop {
sleep(Duration::from_secs(1)).await;
let w = t.lock().unwrap();
if let Some(x) = w.clone() {
x.wake();
return;
}
}
})
.unwrap();
ex1.join().unwrap();
ex2.join().unwrap();
}
#[test]
fn cross_executor_wake_with_join_handle() {
let w = Arc::new(Mutex::new(None));
let t = w.clone();
let fut = TestFuture { w };
let ex1 = LocalExecutorBuilder::new()
.spawn(|| async move {
let x = Local::local(fut).detach();
x.await;
})
.unwrap();
let ex2 = LocalExecutorBuilder::new()
.spawn(|| async move {
loop {
sleep(Duration::from_secs(1)).await;
let w = t.lock().unwrap();
if let Some(x) = w.clone() {
x.wake();
return;
}
}
})
.unwrap();
ex1.join().unwrap();
ex2.join().unwrap();
}
#[test]
fn cross_executor_wake_early_drop() {
let w = Arc::new(Mutex::new(None));
let t = w.clone();
let fut = TestFuture { w };
let ex1 = LocalExecutorBuilder::new()
.spawn(|| async move {
let _drop = futures_lite::future::poll_once(fut).await;
})
.unwrap();
let ex2 = LocalExecutorBuilder::new()
.spawn(|| async move {
loop {
sleep(Duration::from_secs(1)).await;
let w = t.lock().unwrap();
if let Some(ref x) = *w {
x.wake_by_ref();
return;
}
}
})
.unwrap();
ex1.join().unwrap();
ex2.join().unwrap();
}
#[test]
fn cross_executor_wake_hold_waker() {
let w = Arc::new(Mutex::new(None));
let t = w.clone();
let fut = TestFuture { w };
let ex1 = LocalExecutorBuilder::new()
.spawn(|| async move {
let _drop = futures_lite::future::poll_once(fut).await;
})
.unwrap();
ex1.join().unwrap();
let ex2 = LocalExecutorBuilder::new()
.spawn(|| async move {
let w = t.lock().unwrap().clone().unwrap();
w.wake_by_ref();
})
.unwrap();
ex2.join().unwrap();
}
#[test]
fn executor_pool_builder() {
let nr_cpus = 4;
let count = Arc::new(AtomicUsize::new(0));
let handles = LocalExecutorPoolBuilder::new(nr_cpus)
.on_all_shards({
let count = Arc::clone(&count);
|| async move { count.fetch_add(1, Ordering::Relaxed) }
})
.unwrap();
let _: std::thread::ThreadId = handles.handles[0].thread().id();
assert_eq!(nr_cpus, handles.handles().iter().count());
let mut fut_output = handles
.join_all()
.into_iter()
.map(Result::unwrap)
.collect::<Vec<_>>();
fut_output.sort_unstable();
assert_eq!(fut_output, (0..nr_cpus).into_iter().collect::<Vec<_>>());
assert_eq!(nr_cpus, count.load(Ordering::Relaxed));
}
#[test]
fn executor_pool_builder_placements() {
let cpu_set = CpuSet::online().unwrap();
assert!(!cpu_set.is_empty());
for nn in 0..2 {
let nr_execs = nn * cpu_set.len();
let placements = [
Placement::Unbound,
Placement::Fenced(cpu_set.clone()),
Placement::MaxSpread(None),
Placement::MaxSpread(Some(cpu_set.clone())),
Placement::MaxPack(None),
Placement::MaxPack(Some(cpu_set.clone())),
];
for pp in std::array::IntoIter::new(placements) {
let ids = Arc::new(Mutex::new(HashMap::new()));
let cpus = Arc::new(Mutex::new(HashMap::new()));
let cpu_hard_bind = !matches!(pp, Placement::Unbound | Placement::Fenced(_));
let handles = LocalExecutorPoolBuilder::new(nr_execs)
.placement(pp)
.on_all_shards({
let ids = Arc::clone(&ids);
let cpus = Arc::clone(&cpus);
|| async move {
ids.lock()
.unwrap()
.entry(Local::id())
.and_modify(|e| *e += 1)
.or_insert(1);
let pid = nix::unistd::Pid::from_raw(0);
let cpu = nix::sched::sched_getaffinity(pid).unwrap();
cpus.lock()
.unwrap()
.entry(cpu)
.and_modify(|e| *e += 1)
.or_insert(1);
}
})
.unwrap();
assert_eq!(nr_execs, handles.handles().len());
handles
.join_all()
.into_iter()
.for_each(|r| assert!(r.is_ok()));
assert_eq!(nr_execs, ids.lock().unwrap().len());
ids.lock().unwrap().values().for_each(|v| assert_eq!(*v, 1));
if cpu_hard_bind {
assert_eq!(nr_execs, cpus.lock().unwrap().len());
cpus.lock()
.unwrap()
.values()
.for_each(|v| assert_eq!(*v, nn));
}
}
}
}
#[test]
fn executor_pool_builder_shards_limit() {
let cpu_set = CpuSet::online().unwrap();
assert!(!cpu_set.is_empty());
{
let placements = [
(false, Placement::Unbound),
(false, Placement::Fenced(cpu_set.clone())),
(true, Placement::MaxSpread(None)),
(true, Placement::MaxSpread(Some(cpu_set.clone()))),
(true, Placement::MaxPack(None)),
(true, Placement::MaxPack(Some(cpu_set.clone()))),
];
for (_shard_limited, p) in std::array::IntoIter::new(placements) {
LocalExecutorPoolBuilder::new(cpu_set.len())
.placement(p)
.on_all_shards(|| async move {})
.unwrap()
.join_all();
}
}
{
let placements = [
(false, Placement::Unbound),
(false, Placement::Fenced(cpu_set.clone())),
(true, Placement::MaxSpread(None)),
(true, Placement::MaxSpread(Some(cpu_set.clone()))),
(true, Placement::MaxPack(None)),
(true, Placement::MaxPack(Some(cpu_set.clone()))),
];
for (shard_limited, p) in std::array::IntoIter::new(placements) {
match LocalExecutorPoolBuilder::new(1 + cpu_set.len())
.placement(p)
.on_all_shards(|| async move {})
{
Ok(handles) => {
handles.join_all();
assert!(!shard_limited);
}
Err(_) => assert!(shard_limited),
}
}
}
}
#[test]
fn scoped_task() {
LocalExecutor::default().run(async {
let mut a = 1;
unsafe {
ScopedTask::local(async {
a = 2;
})
.await;
}
Local::later().await;
assert_eq!(a, 2);
let mut a = 1;
let do_later = unsafe {
ScopedTask::local(async {
a = 2;
})
};
Local::later().await;
do_later.await;
assert_eq!(a, 2);
});
}
#[test]
fn executor_pool_builder_thread_panic() {
let nr_execs = 8;
let res = LocalExecutorPoolBuilder::new(nr_execs)
.on_all_shards(|| async move { panic!("join handle will be Err") })
.unwrap()
.join_all();
assert_eq!(nr_execs, res.len());
assert!(res.into_iter().all(|r| r.is_err()));
}
#[test]
fn executor_pool_builder_return_values() {
let nr_execs = 8;
let x = Arc::new(AtomicUsize::new(0));
let mut values = LocalExecutorPoolBuilder::new(nr_execs)
.on_all_shards(|| async move { x.fetch_add(1, Ordering::Relaxed) })
.unwrap()
.join_all()
.into_iter()
.map(Result::unwrap)
.collect::<Vec<_>>();
values.sort_unstable();
assert_eq!(values, (0..nr_execs).into_iter().collect::<Vec<_>>());
}
#[test]
fn executor_pool_builder_spawn_cancel() {
let nr_shards = 8;
let mut builder = LocalExecutorPoolBuilder::new(nr_shards);
let nr_exectuted = Arc::new(AtomicUsize::new(0));
let fut_gen = {
let nr_exectuted = Arc::clone(&nr_exectuted);
|| async move {
nr_exectuted.fetch_add(1, Ordering::Relaxed);
unreachable!("should not execute")
}
};
let mut handles = PoolThreadHandles::new();
let placement = std::mem::take(&mut builder.placement);
let mut cpu_set_gen =
placement::CpuSetGenerator::new(placement, builder.nr_shards).unwrap();
let latch = Latch::new(builder.nr_shards);
let ii_cxl = 2;
for ii in 0..builder.nr_shards {
if ii == nr_shards - ii_cxl {
std::thread::sleep(std::time::Duration::from_millis(100));
assert!(ii_cxl <= latch.cancel().unwrap());
}
match builder.spawn_thread(&mut cpu_set_gen, &latch, fut_gen.clone()) {
Ok(handle) => handles.push(handle),
Err(_) => break,
}
}
assert_eq!(0, nr_exectuted.load(Ordering::Relaxed));
assert_eq!(nr_shards, handles.handles.len());
handles.join_all().into_iter().for_each(|s| {
assert!(format!("{}", s.unwrap_err()).contains("spawn failed"));
});
}
#[should_panic]
#[test]
fn executor_inception() {
LocalExecutor::default().run(async {
LocalExecutor::default().run(async {});
});
}
enum TaskState {
Pending(Option<Waker>),
Ready,
}
#[test]
fn wake_by_ref_refcount_underflow_with_join_handle() {
LocalExecutor::default().run(async {
let slot: Rc<RefCell<TaskState>> = Rc::new(RefCell::new(TaskState::Pending(None)));
let cloned_slot = slot.clone();
let jh = Local::local(async move {
poll_fn::<(), _>(|cx| {
let current = &mut *cloned_slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => match maybe_waker {
Some(_) => unreachable!(),
None => {
*current = TaskState::Pending(Some(cx.waker().clone()));
Poll::Pending
}
},
TaskState::Ready => Poll::Ready(()),
}
})
.await;
})
.detach();
let jh2 = Local::local(async move {
let current = &mut *slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => {
let waker = maybe_waker.take().unwrap();
waker.wake_by_ref();
*current = TaskState::Ready; }
TaskState::Ready => unreachable!(), }
})
.detach();
join_all(vec![jh, jh2]).await;
});
}
#[test]
fn wake_by_ref_refcount_underflow_with_sleep() {
LocalExecutor::default().run(async {
let slot: Rc<RefCell<TaskState>> = Rc::new(RefCell::new(TaskState::Pending(None)));
let cloned_slot = slot.clone();
Local::local(async move {
poll_fn::<(), _>(|cx| {
let current = &mut *cloned_slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => match maybe_waker {
Some(_) => unreachable!(),
None => {
*current = TaskState::Pending(Some(cx.waker().clone()));
Poll::Pending
}
},
TaskState::Ready => Poll::Ready(()),
}
})
.await;
})
.detach();
Local::local(async move {
let current = &mut *slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => {
let waker = maybe_waker.take().unwrap();
waker.wake_by_ref();
*current = TaskState::Ready;
}
TaskState::Ready => unreachable!(),
}
})
.detach();
timer::sleep(Duration::from_millis(1)).await;
});
}
#[test]
fn wake_refcount_underflow_with_join_handle() {
LocalExecutor::default().run(async {
let slot: Rc<RefCell<TaskState>> = Rc::new(RefCell::new(TaskState::Pending(None)));
let cloned_slot = slot.clone();
let jh = Local::local(async move {
poll_fn::<(), _>(|cx| {
let current = &mut *cloned_slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => match maybe_waker {
Some(_) => unreachable!(),
None => {
*current = TaskState::Pending(Some(cx.waker().clone()));
Poll::Pending
}
},
TaskState::Ready => Poll::Ready(()),
}
})
.await;
})
.detach();
let jh2 = Local::local(async move {
let current = &mut *slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => {
let waker = maybe_waker.take().unwrap();
waker.wake();
*current = TaskState::Ready;
}
TaskState::Ready => unreachable!(),
}
})
.detach();
join_all(vec![jh, jh2]).await;
});
}
#[test]
fn wake_refcount_underflow_with_sleep() {
LocalExecutor::default().run(async {
let slot: Rc<RefCell<TaskState>> = Rc::new(RefCell::new(TaskState::Pending(None)));
let cloned_slot = slot.clone();
Local::local(async move {
poll_fn::<(), _>(|cx| {
let current = &mut *cloned_slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => match maybe_waker {
Some(_) => unreachable!(),
None => {
*current = TaskState::Pending(Some(cx.waker().clone()));
Poll::Pending
}
},
TaskState::Ready => Poll::Ready(()),
}
})
.await;
})
.detach();
Local::local(async move {
let current = &mut *slot.borrow_mut();
match current {
TaskState::Pending(maybe_waker) => {
let waker = maybe_waker.take().unwrap();
waker.wake();
*current = TaskState::Ready;
}
TaskState::Ready => unreachable!(),
}
})
.detach();
timer::sleep(Duration::from_millis(1)).await;
});
}
}