use super::{
mailbox::{Mailbox, Message},
pool::Pool,
queue,
runtime::{Local, Panics},
tasks::{Links, Tasks},
};
use crossbeam_utils::CachePadded;
use std::{
future::Future,
marker::PhantomData,
mem::{self, ManuallyDrop},
num::NonZeroU64,
ops::Deref,
pin::Pin,
ptr::NonNull,
sync::{Arc, Weak},
task::{Context, Poll, RawWaker, RawWakerVTable, Wake, Waker},
thread,
};
cfg_if::cfg_if! {
if #[cfg(feature = "loom")] {
pub use loom::cell::UnsafeCell;
use loom::sync::atomic::{AtomicUsize, Ordering, fence};
} else {
use std::sync::atomic::{AtomicUsize, Ordering, fence};
#[derive(Default)]
#[repr(transparent)]
pub struct UnsafeCell<T>(std::cell::UnsafeCell<T>);
impl<T> UnsafeCell<T> {
pub const fn new(value: T) -> Self {
Self(std::cell::UnsafeCell::new(value))
}
pub fn with<R>(&self, f: impl FnOnce(*const T) -> R) -> R {
f(self.0.get())
}
pub fn with_mut<R>(&self, f: impl FnOnce(*mut T) -> R) -> R {
f(self.0.get())
}
}
}
}
const IDLE: usize = 0;
const QUEUED: usize = 1;
const RUNNING: usize = 2;
const NOTIFIED: usize = 3;
const COMPLETE: usize = 4;
const LIFECYCLE: usize = 0b111;
const CANCELLED: usize = 0b1000;
const REF_ONE: usize = 1 << 8;
const REFS: usize = !(REF_ONE - 1);
enum AfterPending {
Done,
Requeue,
Complete,
}
enum AfterWake {
Schedule,
Done,
Dealloc,
}
struct State(
AtomicUsize,
);
impl State {
#[allow(clippy::missing_const_for_fn)]
fn new() -> Self {
Self(AtomicUsize::new(QUEUED | (2 * REF_ONE)))
}
#[inline]
const fn with_lifecycle(state: usize, lifecycle: usize) -> usize {
(state & !LIFECYCLE) | lifecycle
}
fn retain(&self) {
if self.0.fetch_add(REF_ONE, Ordering::Relaxed) > isize::MAX as usize {
std::process::abort();
}
}
fn release(&self) -> bool {
self.0.fetch_sub(REF_ONE, Ordering::Release) & REFS == REF_ONE
}
#[inline]
fn notify_by_ref(&self) -> bool {
let mut state = self.0.load(Ordering::Acquire);
loop {
let (next, publish) = match state & LIFECYCLE {
IDLE => {
if state > isize::MAX as usize {
std::process::abort();
}
(Self::with_lifecycle(state, QUEUED) + REF_ONE, true)
}
QUEUED => (state, false),
RUNNING | NOTIFIED => (Self::with_lifecycle(state, NOTIFIED), false),
COMPLETE => return false,
other => unreachable!("invalid task state {other}"),
};
match self
.0
.compare_exchange_weak(state, next, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => return publish,
Err(actual) => state = actual,
}
}
}
#[inline]
fn notify_by_value(&self) -> AfterWake {
let mut state = self.0.load(Ordering::Acquire);
loop {
let (next, publish) = match state & LIFECYCLE {
IDLE => (Self::with_lifecycle(state, QUEUED), true),
QUEUED | COMPLETE => (state - REF_ONE, false),
RUNNING | NOTIFIED => (Self::with_lifecycle(state, NOTIFIED) - REF_ONE, false),
other => unreachable!("invalid task state {other}"),
};
match self
.0
.compare_exchange_weak(state, next, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) if publish => return AfterWake::Schedule,
Ok(_) if next & REFS == 0 => return AfterWake::Dealloc,
Ok(_) => return AfterWake::Done,
Err(actual) => state = actual,
}
}
}
#[inline]
fn start_poll(&self) -> bool {
let mut state = self.0.load(Ordering::Acquire);
loop {
if state & LIFECYCLE != QUEUED {
return false;
}
match self.0.compare_exchange_weak(
state,
Self::with_lifecycle(state, RUNNING),
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return true,
Err(actual) => state = actual,
}
}
}
#[inline]
fn finish_pending(&self) -> AfterPending {
let mut state = self.0.load(Ordering::Acquire);
loop {
if state & CANCELLED != 0 {
return AfterPending::Complete;
}
let (next, result) = match state & LIFECYCLE {
RUNNING => {
assert!(
state & REFS > REF_ONE,
"idle task would release its last reference"
);
(
Self::with_lifecycle(state, IDLE) - REF_ONE,
AfterPending::Done,
)
}
NOTIFIED => (Self::with_lifecycle(state, QUEUED), AfterPending::Requeue),
other => unreachable!("task left poll in invalid state {other}"),
};
match self
.0
.compare_exchange_weak(state, next, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => return result,
Err(actual) => state = actual,
}
}
}
#[inline]
fn complete(&self) {
let mut state = self.0.load(Ordering::Acquire);
loop {
assert!(
matches!(state & LIFECYCLE, RUNNING | NOTIFIED),
"completed task was not running"
);
match self.0.compare_exchange_weak(
state,
(state & REFS) | COMPLETE,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return,
Err(actual) => state = actual,
}
}
}
fn clear(&self) -> bool {
let mut state = self.0.load(Ordering::Acquire);
loop {
let (next, drop_now) = match state & LIFECYCLE {
IDLE | QUEUED => ((state & REFS) | COMPLETE, true),
RUNNING | NOTIFIED => (state | CANCELLED, false),
COMPLETE => return false,
other => unreachable!("invalid task state {other}"),
};
match self
.0
.compare_exchange_weak(state, next, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => return drop_now,
Err(actual) => state = actual,
}
}
}
}
#[must_use = "an ignored result wedges a requeued task or leaves a retired one in its set"]
pub enum AfterPoll {
Done,
Requeue(Runnable),
Retire(Task),
}
struct Vtable {
poll: unsafe fn(NonNull<Header>, &mut Context<'_>) -> Poll<()>,
drop_future: unsafe fn(NonNull<Header>),
dealloc: unsafe fn(NonNull<Header>),
trailer: usize,
}
#[repr(C)]
pub struct Header {
state: State,
vtable: &'static Vtable,
pool: Weak<Pool>,
owner: NonZeroU64,
}
impl Header {
pub const fn owner(&self) -> NonZeroU64 {
self.owner
}
pub const unsafe fn links(ptr: NonNull<Self>) -> NonNull<Links> {
unsafe {
let offset = ptr.as_ref().vtable.trailer;
ptr.cast::<u8>().add(offset).cast()
}
}
}
#[repr(C)]
struct Cell<F> {
header: Header,
future: UnsafeCell<Option<F>>,
links: Links,
_align: [CachePadded<()>; 0],
}
impl<F: Future<Output = ()> + Send + 'static> Cell<F> {
fn vtable() -> &'static Vtable {
&Vtable {
poll: Self::poll,
drop_future: Self::drop_future,
dealloc: Self::dealloc,
trailer: mem::offset_of!(Self, links),
}
}
unsafe fn poll(header: NonNull<Header>, cx: &mut Context<'_>) -> Poll<()> {
let cell = unsafe { header.cast::<Self>().as_ref() };
cell.future.with_mut(|future| {
let future = unsafe { &mut *future };
let future = future.as_mut().expect("queued task retains its future");
unsafe { Pin::new_unchecked(future) }.poll(cx)
})
}
unsafe fn drop_future(header: NonNull<Header>) {
unsafe {
header
.cast::<Self>()
.as_ref()
.future
.with_mut(|future| *future = None)
};
}
unsafe fn dealloc(header: NonNull<Header>) {
let cell = header.cast::<Self>();
let present = unsafe { cell.as_ref().future.with(|future| (*future).is_some()) };
assert!(!present, "task freed with its future still present");
drop(unsafe { Box::from_raw(cell.as_ptr()) });
}
}
pub struct Task(
NonNull<Header>,
);
unsafe impl Send for Task {}
unsafe impl Sync for Task {}
impl Deref for Task {
type Target = Header;
fn deref(&self) -> &Header {
unsafe { self.0.as_ref() }
}
}
impl Clone for Task {
fn clone(&self) -> Self {
self.state.retain();
Self(self.0)
}
}
impl Drop for Task {
fn drop(&mut self) {
if self.state.release() {
fence(Ordering::Acquire);
unsafe { (self.vtable.dealloc)(self.0) };
}
}
}
impl Task {
pub fn new<F>(future: F, tasks: &Tasks, pool: Weak<Pool>) -> (Self, Runnable)
where
F: Future<Output = ()> + Send + 'static,
{
let cell = Box::new(Cell {
header: Header {
state: State::new(),
vtable: Cell::<F>::vtable(),
pool,
owner: tasks.id(),
},
future: UnsafeCell::new(Some(future)),
links: Links::default(),
_align: [],
});
let ptr = NonNull::from(Box::leak(cell)).cast();
(Self(ptr), Runnable(Self(ptr)))
}
pub const fn as_ptr(&self) -> NonNull<Header> {
self.0
}
pub fn into_raw(self) -> NonNull<Header> {
ManuallyDrop::new(self).0
}
pub const unsafe fn from_raw(ptr: NonNull<Header>) -> Self {
Self(ptr)
}
fn waker(&self) -> WakerRef<'_> {
let waker =
unsafe { Waker::from_raw(RawWaker::new(self.0.as_ptr().cast(), &WAKER_VTABLE)) };
WakerRef {
waker: ManuallyDrop::new(waker),
_task: PhantomData,
}
}
fn wake(self) {
let this = ManuallyDrop::new(self);
match this.state.notify_by_value() {
AfterWake::Schedule => Runnable(ManuallyDrop::into_inner(this)).schedule(),
AfterWake::Done => {}
AfterWake::Dealloc => {
unsafe { (this.vtable.dealloc)(this.0) };
}
}
}
fn wake_by_ref(&self) {
if self.state.notify_by_ref() {
Runnable(unsafe { Self::from_raw(self.0) }).schedule();
}
}
pub fn clear(&self) {
if !self.state.clear() {
return;
}
unsafe { (self.vtable.drop_future)(self.0) };
}
}
#[must_use = "a runnable must be spawned, scheduled, polled, or discarded"]
pub struct Runnable(
Task,
);
impl Runnable {
pub fn schedule(self) {
let owner = self.0.owner;
let refused = Local::with_current(|local| match local {
Some(local) if local.serves(owner) => {
local.enqueue(self);
None
}
_ => Some(self),
});
if let Some(runnable) = refused {
runnable.push_global();
}
}
pub fn spawn(self) {
let owner = self.0.owner;
let refused = Local::with_current(|local| match local {
Some(local)
if local.serves(owner) && (local.ready().is_empty() || local.workers() == 1) =>
{
local.enqueue(self);
None
}
_ => Some(self),
});
if let Some(runnable) = refused {
runnable.push_global();
}
}
fn push_global(self) {
let Some(pool) = self.0.pool.upgrade() else {
self.discard();
return;
};
if let Err(runnable) = pool.push(self) {
runnable.discard();
}
}
pub fn poll(self, owner: NonZeroU64) -> AfterPoll {
assert_eq!(
self.0.owner, owner,
"runnable must be polled by its own runtime"
);
if !self.0.state.start_poll() {
self.discard();
return AfterPoll::Done;
}
let task = &self.0;
let polled = Panics::contain(|| {
let waker = task.waker();
let mut cx = Context::from_waker(&waker);
unsafe { (task.vtable.poll)(task.0, &mut cx) }
});
if matches!(polled, Some(Poll::Pending)) {
match task.state.finish_pending() {
AfterPending::Done => {
mem::forget(self);
return AfterPoll::Done;
}
AfterPending::Requeue => return AfterPoll::Requeue(self),
AfterPending::Complete => {}
}
}
task.state.complete();
Panics::contain(|| unsafe { (task.vtable.drop_future)(task.0) });
AfterPoll::Retire(self.into_task())
}
pub fn discard(self) {
drop(self.into_task());
}
fn into_task(self) -> Task {
Task(ManuallyDrop::new(self).0.as_ptr())
}
}
impl Drop for Runnable {
fn drop(&mut self) {
if thread::panicking() {
return;
}
panic!("runnable dropped without being scheduled, polled, or discarded");
}
}
struct WakerRef<'a> {
waker: ManuallyDrop<Waker>,
_task: PhantomData<&'a Task>,
}
impl Deref for WakerRef<'_> {
type Target = Waker;
fn deref(&self) -> &Waker {
&self.waker
}
}
static WAKER_VTABLE: RawWakerVTable =
RawWakerVTable::new(waker_clone, waker_wake, waker_wake_by_ref, waker_drop);
const unsafe fn header<'a>(ptr: *const ()) -> &'a Header {
unsafe { &*ptr.cast::<Header>() }
}
const unsafe fn task(ptr: *const ()) -> Task {
unsafe { Task::from_raw(NonNull::new_unchecked(ptr.cast_mut().cast())) }
}
unsafe fn waker_clone(ptr: *const ()) -> RawWaker {
unsafe { header(ptr) }.state.retain();
RawWaker::new(ptr, &WAKER_VTABLE)
}
unsafe fn waker_wake(ptr: *const ()) {
unsafe { task(ptr) }.wake();
}
unsafe fn waker_wake_by_ref(ptr: *const ()) {
ManuallyDrop::new(unsafe { task(ptr) }).wake_by_ref();
}
unsafe fn waker_drop(ptr: *const ()) {
drop(unsafe { task(ptr) });
}
pub struct RootWaker {
mailbox: Weak<Mailbox>,
}
impl RootWaker {
pub fn new(mailbox: Weak<Mailbox>) -> Arc<Self> {
Arc::new(Self { mailbox })
}
}
impl Wake for RootWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
if let Some(local) = Local::owner(&self.mailbox) {
let mut local = local.borrow_mut();
if !local.closing {
local.root_ready = true;
}
return;
}
if let Some(mailbox) = self.mailbox.upgrade() {
let _ = mailbox.send(Message::WakeRoot);
}
}
}
pub type Ready = queue::Owner<Runnable>;
impl Ready {
pub fn discard(&mut self) {
while let Some(runnable) = self.pop() {
runnable.discard();
}
}
}
#[cfg(test)]
#[commonware_macros::test_group("miri")]
pub mod tests {
use super::*;
use crate::{
iouring::{pool::LocalQueue, queue::Push},
utils::extract_panic_message,
};
use commonware_utils::sync::Mutex;
use std::{
future::{pending, poll_fn},
marker::PhantomPinned,
panic::{AssertUnwindSafe, catch_unwind},
ptr,
sync::{
Barrier,
atomic::{AtomicUsize, Ordering},
},
thread,
};
struct DropCount(Arc<AtomicUsize>);
impl Drop for DropCount {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
struct PanicsOnDrop {
complete: bool,
drops: Arc<AtomicUsize>,
}
impl Future for PanicsOnDrop {
type Output = ();
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> {
if self.complete {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
impl Drop for PanicsOnDrop {
fn drop(&mut self) {
self.drops.fetch_add(1, Ordering::Relaxed);
panic!("future drop panic");
}
}
struct WakesOnDrop {
waker: Option<Waker>,
complete: bool,
_drops: DropCount,
}
impl Future for WakesOnDrop {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
self.waker = Some(cx.waker().clone());
if self.complete {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
impl Drop for WakesOnDrop {
fn drop(&mut self) {
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
}
struct SelfWaker {
wakes: usize,
polls: usize,
_drops: DropCount,
}
impl Future for SelfWaker {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
for _ in 0..self.wakes {
cx.waker().wake_by_ref();
}
self.polls -= 1;
if self.polls == 0 {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
#[repr(align(512))]
struct OverAligned {
remaining: AtomicUsize,
addresses: Arc<Mutex<Vec<usize>>>,
_pinned: PhantomPinned,
}
impl Future for OverAligned {
type Output = ();
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> {
self.addresses.lock().push(ptr::from_ref(&*self).addr());
if self.remaining.fetch_sub(1, Ordering::Relaxed) == 1 {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
impl Drop for OverAligned {
fn drop(&mut self) {
self.addresses.lock().push(ptr::from_ref(self).addr());
}
}
pub fn pool() -> Arc<Pool> {
Arc::new(Pool::new(vec![Arc::new(Mailbox::new().unwrap())]))
}
impl Ready {
pub fn private() -> Self {
LocalQueue::shared().owner()
}
}
pub fn queue(ready: &mut Ready, runnable: Runnable) {
match ready.push(runnable) {
Push::Pushed(_) => {}
Push::Overflow(..) | Push::Claimed(_) => panic!("the run queue must have room"),
}
}
fn insert(
set: &Tasks,
ready: &mut Ready,
pool: &Arc<Pool>,
future: impl Future<Output = ()> + Send + 'static,
) -> Task {
let (task, runnable) = Task::new(future, set, Arc::downgrade(pool));
assert!(set.insert(task.clone()).is_ok());
queue(ready, runnable);
task
}
fn retire(set: &Tasks, task: Task) {
assert!(set.remove(&task).is_some(), "completed task not retained");
}
pub fn refs(task: &Task) -> usize {
(task.state.0.load(Ordering::Acquire) & REFS) / REF_ONE
}
pub const fn task_of(runnable: &Runnable) -> &Task {
&runnable.0
}
pub fn cancelled(task: &Task) -> bool {
task.state.0.load(Ordering::Acquire) & CANCELLED != 0
}
fn scheduled(pool: &Pool) -> Vec<Runnable> {
std::iter::from_fn(|| pool.pop()).collect()
}
#[test]
fn test_cell_is_aligned_as_a_whole() {
type Small = Cell<std::future::Pending<()>>;
assert!(std::mem::size_of::<Header>() <= 64);
let unit = std::mem::align_of::<CachePadded<()>>();
assert_eq!(std::mem::align_of::<Small>(), unit);
if unit >= 64 {
assert_eq!(std::mem::size_of::<Small>(), unit);
}
}
#[test]
fn test_links_follow_the_future() {
fn check<F: Future<Output = ()> + Send + 'static>(future: F) {
let set = Tasks::new(1);
let (task, runnable) = Task::new(future, &set, Weak::new());
let cell = task.as_ptr().cast::<Cell<F>>();
let expected = unsafe { ptr::addr_of!((*cell.as_ptr()).links) };
let links = unsafe { Header::links(task.as_ptr()) };
assert_eq!(links.as_ptr().cast_const(), expected);
task.clear();
runnable.discard();
}
check(pending::<()>());
let large = async {
let large = [0_u8; 300];
pending::<()>().await;
std::hint::black_box(&large);
};
assert!(std::mem::size_of_val(&large) >= 300);
check(large);
check(OverAligned {
remaining: AtomicUsize::new(usize::MAX),
addresses: Arc::new(Mutex::new(Vec::new())),
_pinned: PhantomPinned,
});
}
#[test]
fn test_header_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Header>();
}
#[test]
fn test_runnable_of_another_runtime_is_not_polled() {
let set = Tasks::new(1);
let other = Tasks::new(1);
let polled = Arc::new(AtomicUsize::new(0));
let future = {
let polled = polled.clone();
poll_fn(move |_| {
polled.fetch_add(1, Ordering::Relaxed);
Poll::<()>::Pending
})
};
let (task, runnable) = Task::new(future, &set, Weak::new());
let panic = catch_unwind(AssertUnwindSafe(|| drop(runnable.poll(other.id())))).unwrap_err();
assert!(
extract_panic_message(&*panic).contains("runnable must be polled by its own runtime")
);
assert_eq!(polled.load(Ordering::Relaxed), 0);
assert_eq!(refs(&task), 1);
task.clear();
}
#[test]
fn test_dropped_runnable_panics_unless_unwinding() {
let set = Tasks::new(1);
let (task, runnable) = Task::new(pending::<()>(), &set, Weak::new());
let panic = catch_unwind(AssertUnwindSafe(|| drop(runnable))).unwrap_err();
assert_eq!(
extract_panic_message(&*panic),
"runnable dropped without being scheduled, polled, or discarded"
);
assert_eq!(refs(&task), 1);
let (other, runnable) = Task::new(pending::<()>(), &set, Weak::new());
let panic = catch_unwind(AssertUnwindSafe(|| {
let _runnable = runnable;
panic!("original panic");
}))
.unwrap_err();
assert_eq!(extract_panic_message(&*panic), "original panic");
assert_eq!(refs(&other), 1);
task.clear();
other.clear();
}
#[test]
fn test_wakers_hold_references_until_the_cell_is_freed() {
let pool = pool();
let set = Tasks::new(1);
let (task, runnable) = Task::new(pending::<()>(), &set, Arc::downgrade(&pool));
assert_eq!(refs(&task), 2);
assert_eq!(Arc::weak_count(&pool), 1);
assert!(matches!(runnable.poll(set.id()), AfterPoll::Done));
assert_eq!(refs(&task), 1);
let waker = Waker::clone(&task.waker());
let other = waker.clone();
assert_eq!(refs(&task), 3);
drop(other);
assert_eq!(refs(&task), 2);
waker.wake();
assert_eq!(refs(&task), 2);
let mut runnables = scheduled(&pool);
assert_eq!(runnables.len(), 1);
let runnable = runnables.pop().unwrap();
assert_eq!(task_of(&runnable).as_ptr(), task.as_ptr());
runnable.discard();
assert_eq!(refs(&task), 1);
task.clear();
drop(task);
assert_eq!(Arc::weak_count(&pool), 0);
}
#[test]
fn test_foreign_wakes_coalesce_into_one_runnable() {
let pool = pool();
let set = Tasks::new(1);
let mut ready = Ready::private();
let task = insert(&set, &mut ready, &pool, pending());
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
let waker = Waker::clone(&task.waker());
thread::spawn(move || {
waker.wake_by_ref();
waker.wake_by_ref();
waker.wake();
})
.join()
.unwrap();
assert_eq!(refs(&task), 3);
let mut runnables = scheduled(&pool);
assert_eq!(runnables.len(), 1);
queue(&mut ready, runnables.pop().unwrap());
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
assert!(scheduled(&pool).is_empty());
task.clear();
drop(set.teardown());
}
#[test]
fn test_running_poll_cannot_be_claimed_again() {
let state = State::new();
assert!(state.start_poll());
assert!(!state.start_poll());
assert!(!state.notify_by_ref());
assert!(!state.start_poll());
}
#[test]
fn test_wakes_during_poll_coalesce_into_one_requeue() {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let set = Tasks::new(1);
let mut ready = Ready::private();
let task = insert(
&set,
&mut ready,
&pool,
SelfWaker {
wakes: 3,
polls: 2,
_drops: DropCount(drops.clone()),
},
);
let AfterPoll::Requeue(runnable) = ready.pop().unwrap().poll(set.id()) else {
panic!("self-woken pending poll must requeue");
};
assert_eq!(task_of(&runnable).as_ptr(), task.as_ptr());
assert!(scheduled(&pool).is_empty());
let AfterPoll::Retire(retired) = runnable.poll(set.id()) else {
panic!("final poll must complete");
};
assert_eq!(drops.load(Ordering::Relaxed), 1);
retire(&set, retired);
assert!(scheduled(&pool).is_empty());
assert_eq!(refs(&task), 1);
}
#[test]
fn test_poll_panic_completes_the_task_and_leaves_the_set() {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let set = Tasks::new(1);
let mut ready = Ready::private();
let guard = DropCount(drops.clone());
let first = insert(&set, &mut ready, &pool, async move {
let _guard = guard;
panic!("poll panic");
});
let waker = Waker::clone(&first.waker());
let AfterPoll::Retire(retired) = ready.pop().unwrap().poll(set.id()) else {
panic!("panicking poll must complete the task");
};
assert_eq!(drops.load(Ordering::Relaxed), 1);
retire(&set, retired);
assert_eq!(set.live(), 0);
let second = insert(&set, &mut ready, &pool, pending());
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
waker.wake_by_ref();
assert!(scheduled(&pool).is_empty());
drop(waker);
assert_eq!(refs(&first), 1);
assert_eq!(set.live(), 1);
second.clear();
drop(set.teardown());
}
#[test]
fn test_foreign_registration_retains_the_task_and_queues_its_runnable_globally() {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let set = Tasks::new(1);
assert!(set.register(pending(), Arc::downgrade(&pool)).is_ok());
assert_eq!(set.live(), 1);
let mut runnables = scheduled(&pool);
assert_eq!(runnables.len(), 1);
let runnable = runnables.pop().unwrap();
assert_eq!(refs(task_of(&runnable)), 2);
runnable.discard();
pool.close();
assert!(set.register(pending(), Arc::downgrade(&pool)).is_ok());
let gone = Arc::downgrade(&pool);
drop(pool);
assert!(set.register(pending(), gone).is_ok());
assert_eq!(set.live(), 3);
let retained = set.teardown();
assert_eq!(retained.len(), 3);
assert!(retained.iter().all(|task| refs(task) == 1));
let guard = DropCount(drops.clone());
let registered = set.register(
async move {
let _guard = guard;
pending::<()>().await;
},
Weak::new(),
);
let Err(rejected) = registered else {
panic!("a closed set must reject the task");
};
assert_eq!(drops.load(Ordering::Relaxed), 0);
assert_eq!(refs(&rejected), 1);
rejected.clear();
assert_eq!(drops.load(Ordering::Relaxed), 1);
for task in retained {
task.clear();
}
}
#[test]
fn test_wake_to_a_closed_or_dropped_pool_discards_its_runnable() {
let set = Tasks::new(1);
let mut ready = Ready::private();
let closed = pool();
let dropped = pool();
let first = insert(&set, &mut ready, &closed, pending());
let second = insert(&set, &mut ready, &dropped, pending());
for _ in 0..2 {
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
}
closed.close();
first.wake_by_ref();
assert_eq!(refs(&first), 2);
drop(dropped);
second.wake_by_ref();
assert_eq!(refs(&second), 2);
for task in set.teardown() {
task.clear();
}
}
#[test]
fn test_destructor_waking_its_own_task_publishes_nothing() {
for complete in [false, true] {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let set = Tasks::new(1);
let mut ready = Ready::private();
let task = insert(
&set,
&mut ready,
&pool,
WakesOnDrop {
waker: None,
complete,
_drops: DropCount(drops.clone()),
},
);
match ready.pop().unwrap().poll(set.id()) {
AfterPoll::Retire(retired) => retire(&set, retired),
AfterPoll::Done => task.clear(),
_ => panic!("the poll must complete or leave the task idle"),
}
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert!(scheduled(&pool).is_empty());
assert_eq!(refs(&task), if complete { 1 } else { 2 });
drop(set.teardown());
}
}
#[test]
fn test_clear_detaches_tasks_in_each_state() {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let set = Tasks::new(1);
let mut ready = Ready::private();
let handles = [(); 3].map(|_| {
let guard = DropCount(drops.clone());
insert(&set, &mut ready, &pool, async move {
let _guard = guard;
pending::<()>().await;
})
});
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
handles[1].wake_by_ref();
let mut runnables = scheduled(&pool);
assert_eq!(runnables.len(), 1);
ready.discard();
assert!(ready.is_empty());
let retired = set.teardown();
assert_eq!(retired.len(), 3);
assert!(set.drain(0).next().is_none());
assert_eq!(drops.load(Ordering::Relaxed), 0);
for task in &retired {
task.clear();
task.clear();
}
assert_eq!(drops.load(Ordering::Relaxed), 3);
for task in &handles {
task.wake_by_ref();
}
assert!(scheduled(&pool).is_empty());
assert!(matches!(
runnables.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
assert_eq!(refs(&handles[1]), 2);
assert!(set.remove(&handles[0]).is_none());
drop(retired);
for task in &handles {
assert_eq!(refs(task), 1);
}
}
#[test]
fn test_clear_during_poll_is_finished_by_the_poller() {
for wake_first in [false, true] {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let set = Tasks::new(1);
let mut ready = Ready::private();
let cell = Arc::new(Mutex::new(None::<Task>));
let task = insert(&set, &mut ready, &pool, {
let guard = DropCount(drops.clone());
let cell = Arc::clone(&cell);
poll_fn(move |cx| {
let _ = &guard;
if wake_first {
cx.waker().wake_by_ref();
}
cell.lock().as_ref().unwrap().clear();
if !wake_first {
cx.waker().wake_by_ref();
}
Poll::<()>::Pending
})
});
*cell.lock() = Some(task.clone());
let AfterPoll::Retire(retired) = ready.pop().unwrap().poll(set.id()) else {
panic!("a task cleared during its poll must complete");
};
assert_eq!(drops.load(Ordering::Relaxed), 1);
retire(&set, retired);
task.wake_by_ref();
assert!(scheduled(&pool).is_empty());
cell.lock().take();
}
}
#[test]
fn test_concurrent_final_releases_free_the_cell_once() {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let baseline = Arc::weak_count(&pool);
let set = Tasks::new(1);
let mut ready = Ready::private();
let guard = DropCount(drops.clone());
let task = insert(&set, &mut ready, &pool, async move {
let _guard = guard;
pending::<()>().await;
});
let waker = Waker::clone(&task.waker());
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
task.clear();
retire(&set, task.clone());
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(refs(&task), 2);
let barrier = Arc::new(Barrier::new(2));
let releaser = thread::spawn({
let barrier = barrier.clone();
move || {
barrier.wait();
drop(waker);
}
});
barrier.wait();
drop(task);
releaser.join().unwrap();
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(Arc::weak_count(&pool), baseline);
}
#[test]
fn test_wake_by_value_frees_a_completed_task() {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let baseline = Arc::weak_count(&pool);
let set = Tasks::new(1);
let mut ready = Ready::private();
let guard = DropCount(drops.clone());
let task = insert(&set, &mut ready, &pool, async move {
let _guard = guard;
});
let waker = Waker::clone(&task.waker());
let AfterPoll::Retire(retired) = ready.pop().unwrap().poll(set.id()) else {
panic!("ready future must complete");
};
retire(&set, retired);
drop(task);
assert_eq!(drops.load(Ordering::Relaxed), 1);
waker.wake();
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(Arc::weak_count(&pool), baseline);
}
#[test]
fn test_panicking_destructor_releases_the_cell() {
for complete in [false, true] {
let drops = Arc::new(AtomicUsize::new(0));
let pool = pool();
let baseline = Arc::weak_count(&pool);
let set = Tasks::new(1);
let mut ready = Ready::private();
let task = insert(
&set,
&mut ready,
&pool,
PanicsOnDrop {
complete,
drops: drops.clone(),
},
);
let waker = Waker::clone(&task.waker());
let outcome = ready.pop().unwrap().poll(set.id());
if complete {
let AfterPoll::Retire(retired) = outcome else {
panic!("ready future must complete despite its destructor panic");
};
retire(&set, retired);
} else {
assert!(matches!(outcome, AfterPoll::Done));
for retired in set.teardown() {
assert!(Panics::contain(|| retired.clear()).is_none());
}
}
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(refs(&task), 2);
drop(task);
waker.wake();
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(Arc::weak_count(&pool), baseline);
}
}
#[test]
fn test_over_aligned_future_is_polled_and_dropped_in_place() {
let align = std::mem::align_of::<OverAligned>();
assert!(align > std::mem::align_of::<CachePadded<()>>());
for complete in [true, false] {
let pool = pool();
let set = Tasks::new(1);
let mut ready = Ready::private();
let addresses = Arc::new(Mutex::new(Vec::new()));
let task = insert(
&set,
&mut ready,
&pool,
OverAligned {
remaining: AtomicUsize::new(if complete { 3 } else { usize::MAX }),
addresses: addresses.clone(),
_pinned: PhantomPinned,
},
);
for _ in 0..2 {
assert!(matches!(
ready.pop().unwrap().poll(set.id()),
AfterPoll::Done
));
task.wake_by_ref();
queue(&mut ready, scheduled(&pool).pop().unwrap());
}
if complete {
let AfterPoll::Retire(retired) = ready.pop().unwrap().poll(set.id()) else {
panic!("the third poll must complete");
};
retire(&set, retired);
} else {
ready.discard();
for retired in set.teardown() {
retired.clear();
}
}
let addresses = addresses.lock();
assert_eq!(addresses.len(), if complete { 4 } else { 3 });
assert_eq!(addresses[0] % align, 0, "future is not aligned");
assert!(
addresses.iter().all(|address| *address == addresses[0]),
"future moved: {addresses:?}"
);
}
}
}
#[cfg(all(test, feature = "loom"))]
mod loom_tests {
use super::{
AfterPending, AfterPoll, Mailbox, Panics, Pool, REF_ONE, REFS, Ready, Runnable, State,
Task, Tasks,
};
use crate::iouring::{
idle::{Park, Searcher},
queue::{CAPACITY, STEALABLE},
};
use loom::{
sync::{
Arc, Condvar, Mutex,
atomic::{AtomicBool, AtomicUsize, Ordering},
},
thread,
};
use std::{
future::{Future, poll_fn},
task::{Poll, Waker},
};
fn refs(state: &State) -> usize {
(state.0.load(Ordering::Acquire) & REFS) / REF_ONE
}
fn pool(workers: usize) -> std::sync::Arc<Pool> {
let mailboxes = (0..workers)
.map(|_| std::sync::Arc::new(Mailbox::new().unwrap()))
.collect();
std::sync::Arc::new(Pool::new(mailboxes))
}
struct DropCount(Arc<AtomicUsize>);
impl Drop for DropCount {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
fn signaled(
signal: Arc<AtomicBool>,
drops: &Arc<AtomicUsize>,
) -> impl Future<Output = ()> + Send + 'static {
let guard = DropCount(drops.clone());
poll_fn(move |_| {
let _ = &guard;
if signal.load(Ordering::Acquire) {
Poll::Ready(())
} else {
Poll::Pending
}
})
}
fn pending(drops: &Arc<AtomicUsize>) -> impl Future<Output = ()> + Send + 'static {
let guard = DropCount(drops.clone());
poll_fn(move |_| {
let _ = &guard;
Poll::Pending
})
}
fn run(set: &Tasks, runnable: Runnable) -> bool {
let mut next = Some(runnable);
while let Some(runnable) = next.take() {
match runnable.poll(set.id()) {
AfterPoll::Done => {}
AfterPoll::Requeue(runnable) => next = Some(runnable),
AfterPoll::Retire(task) => {
drop(set.remove(&task));
return true;
}
}
}
false
}
fn teardown(set: &Tasks, pool: &Pool) {
set.close();
pool.close();
for task in set.drain(0) {
Panics::contain(|| task.clear());
}
}
fn complete(
pool: &Pool,
set: &Tasks,
runnable: Runnable,
completed: &AtomicUsize,
tasks: usize,
) {
if run(set, runnable) && completed.fetch_add(1, Ordering::AcqRel) + 1 == tasks {
for worker in 0..pool.workers() as u32 {
pool.mailbox(worker).waker.wake();
}
}
}
fn work(
pool: &Pool,
set: &Tasks,
ready: &mut Ready,
index: u32,
completed: &AtomicUsize,
tasks: usize,
spin: bool,
) {
let mut searcher = Searcher::new(pool, index);
loop {
if completed.load(Ordering::Acquire) == tasks {
ready.discard();
return;
}
let next = ready.pop().or_else(|| pool.take(ready));
if let Some(runnable) = next {
searcher.leave_search();
complete(pool, set, runnable, completed, tasks);
continue;
}
if searcher.search(ready) || searcher.publish_and_look(ready) {
searcher.leave_search();
continue;
}
if completed.load(Ordering::Acquire) == tasks {
searcher.finish_park();
continue;
}
let waker = &pool.mailbox(index).waker;
if spin && waker.signalled() {
waker.consume_signal();
} else {
waker.park_idle(0, None);
}
searcher.finish_park();
}
}
fn foreign_pushes(workers: usize, tasks: usize, spin: bool, bound: Option<usize>) {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = bound;
builder.check(move || {
let pool = pool(workers);
let set = Arc::new(Tasks::new(workers));
let signal = Arc::new(AtomicBool::new(false));
let drops = Arc::new(AtomicUsize::new(0));
let completed = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
let mut wakers = Vec::new();
for _ in 0..tasks {
let (task, runnable) = Task::new(
signaled(signal.clone(), &drops),
&set,
std::sync::Arc::downgrade(&pool),
);
assert!(set.insert(task.clone()).is_ok());
assert!(!run(&set, runnable));
wakers.push(Waker::clone(&task.waker()));
handles.push(task);
}
let pushing = thread::spawn(move || {
signal.store(true, Ordering::Release);
for waker in wakers {
waker.wake();
}
});
let others: Vec<_> = (1..workers as u32)
.map(|index| {
let pool = pool.clone();
let set = set.clone();
let completed = completed.clone();
thread::spawn(move || {
let mut ready = pool.queue(index).owner();
work(&pool, &set, &mut ready, index, &completed, tasks, spin);
})
})
.collect();
let mut ready = pool.queue(0).owner();
work(&pool, &set, &mut ready, 0, &completed, tasks, spin);
pushing.join().unwrap();
for other in others {
other.join().unwrap();
}
assert_eq!(drops.load(Ordering::Relaxed), tasks);
for task in handles {
assert_eq!(refs(&task.state), 1);
drop(task);
}
assert_eq!(std::sync::Arc::weak_count(&pool), 0);
});
}
type Ran = Arc<(Mutex<bool>, Condvar)>;
fn raising(ran: &Ran) -> impl Future<Output = ()> + Send + 'static {
let ran = ran.clone();
poll_fn(move |_| {
*ran.0.lock().unwrap() = true;
ran.1.notify_all();
Poll::Ready(())
})
}
fn wait_ran(ran: &Ran) {
let mut raised = ran.0.lock().unwrap();
while !*raised {
raised = ran.1.wait(raised).unwrap();
}
}
fn enqueue_raising(
pool: &std::sync::Arc<Pool>,
set: &Tasks,
ready: &mut Ready,
ran: &Ran,
tasks: usize,
) -> Vec<Task> {
(0..tasks)
.map(|_| {
let (task, runnable) =
Task::new(raising(ran), set, std::sync::Arc::downgrade(pool));
assert!(set.insert(task.clone()).is_ok());
pool.enqueue(ready, runnable);
task
})
.collect()
}
type Gate = Arc<(Mutex<u8>, Condvar)>;
fn reach(gate: &Gate, stage: u8) {
*gate.0.lock().unwrap() = stage;
gate.1.notify_all();
}
fn await_stage(gate: &Gate, stage: u8) {
let mut current = gate.0.lock().unwrap();
while *current < stage {
current = gate.1.wait(current).unwrap();
}
}
fn wait_ran_count(ran: &Arc<(Mutex<usize>, Condvar)>, count: usize) {
let mut raised = ran.0.lock().unwrap();
while *raised < count {
raised = ran.1.wait(raised).unwrap();
}
}
#[test]
fn test_foreign_push_racing_a_park_is_never_lost() {
for spin in [false, true] {
foreign_pushes(1, 1, spin, None);
}
}
#[test]
fn test_foreign_push_racing_two_parks_is_never_lost() {
for spin in [false, true] {
foreign_pushes(2, 1, spin, Some(3));
}
}
#[test]
fn test_successive_foreign_pushes_racing_a_take_are_never_lost() {
foreign_pushes(1, 2, false, Some(3));
}
#[test]
fn test_backlog_behind_a_busy_worker_is_stolen() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(|| {
let pool = pool(2);
let set = Arc::new(Tasks::new(2));
let completed = Arc::new(AtomicUsize::new(0));
let ran: Ran = Arc::new((Mutex::new(false), Condvar::new()));
let thief = thread::spawn({
let pool = pool.clone();
let set = set.clone();
let completed = completed.clone();
move || {
let mut ready = pool.queue(1).owner();
work(&pool, &set, &mut ready, 1, &completed, STEALABLE, false);
}
});
let mut ready = pool.queue(0).owner();
let handles = enqueue_raising(&pool, &set, &mut ready, &ran, STEALABLE);
wait_ran(&ran);
work(&pool, &set, &mut ready, 0, &completed, STEALABLE, false);
thief.join().unwrap();
for task in handles {
assert_eq!(refs(&task.state), 1);
}
});
}
#[test]
fn test_kick_left_to_a_searcher_is_handed_on() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(2);
builder.check(|| {
let pool = pool(3);
let set = Arc::new(Tasks::new(3));
let completed = Arc::new(AtomicUsize::new(0));
let ran: Ran = Arc::new((Mutex::new(false), Condvar::new()));
let searching = thread::spawn({
let pool = pool.clone();
let set = set.clone();
let completed = completed.clone();
let ran = ran.clone();
move || {
let mut ready = pool.queue(1).owner();
let mut searcher = Searcher::new(&pool, 1);
let _ = searcher.search(&mut ready);
searcher.leave_search();
while let Some(runnable) = ready.pop() {
complete(&pool, &set, runnable, &completed, STEALABLE);
}
wait_ran(&ran);
}
});
let parked = thread::spawn({
let pool = pool.clone();
let set = set.clone();
let completed = completed.clone();
move || {
let mut ready = pool.queue(2).owner();
work(&pool, &set, &mut ready, 2, &completed, STEALABLE, false);
}
});
let mut ready = pool.queue(0).owner();
let handles = enqueue_raising(&pool, &set, &mut ready, &ran, STEALABLE);
wait_ran(&ran);
searching.join().unwrap();
work(&pool, &set, &mut ready, 0, &completed, STEALABLE, false);
parked.join().unwrap();
for task in handles {
assert_eq!(refs(&task.state), 1);
}
});
}
#[test]
fn test_run_after_the_last_count_wakes_a_parked_peer() {
const TASKS: usize = CAPACITY + 1;
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(2);
builder.check(|| {
let pool = pool(4);
let set = Arc::new(Tasks::new(4));
let completed = Arc::new(AtomicUsize::new(0));
let ran: Ran = Arc::new((Mutex::new(false), Condvar::new()));
let gate: Gate = Arc::new((Mutex::new(0), Condvar::new()));
let peer = thread::spawn({
let pool = pool.clone();
let set = set.clone();
let completed = completed.clone();
let gate = gate.clone();
move || {
let mut ready = pool.queue(1).owner();
{
let mut searcher = Searcher::new(&pool, 1);
assert_eq!(searcher.prepare_park(&ready), Park::Wait);
reach(&gate, 1);
pool.mailbox(1).waker.park_idle(0, None);
await_stage(&gate, 2);
searcher.finish_park();
searcher.leave_search();
}
work(&pool, &set, &mut ready, 1, &completed, TASKS, false);
}
});
await_stage(&gate, 1);
let mut r0 = pool.queue(0).owner();
let mut r2 = pool.queue(2).owner();
let mut r3 = pool.queue(3).owner();
let mut zero = Searcher::new(&pool, 0);
let mut two = Searcher::new(&pool, 2);
assert_eq!(zero.prepare_park(&r0), Park::Wait);
assert!(!two.search(&mut r2));
let handles = enqueue_raising(&pool, &set, &mut r3, &ran, TASKS);
pool.mailbox(0).waker.consume_signal();
zero.finish_park();
let first_zero = pool.take(&mut r0).unwrap();
zero.leave_search();
assert!(r0.is_empty());
assert_eq!(two.prepare_park(&r2), Park::Run);
two.leave_search();
let first_two = pool.take(&mut r2).unwrap();
assert!(r2.is_empty());
drop((zero, two));
reach(&gate, 2);
wait_ran(&ran);
complete(&pool, &set, first_zero, &completed, TASKS);
complete(&pool, &set, first_two, &completed, TASKS);
work(&pool, &set, &mut r3, 3, &completed, TASKS, false);
peer.join().unwrap();
assert_eq!(completed.load(Ordering::Acquire), TASKS);
for task in handles {
assert_eq!(refs(&task.state), 1);
}
});
}
#[test]
fn test_exit_racing_a_claim_restores_the_idle_state() {
loom::model(|| {
let pool = pool(2);
let set = Tasks::new(2);
let drops = Arc::new(AtomicUsize::new(0));
let (task, runnable) =
Task::new(pending(&drops), &set, std::sync::Arc::downgrade(&pool));
assert!(set.insert(task).is_ok());
let pushing = thread::spawn({
let pool = pool.clone();
move || assert!(pool.push(runnable).is_ok())
});
{
let ready = pool.queue(1).owner();
let mut searcher = Searcher::new(&pool, 1);
let _ = searcher.prepare_park(&ready);
}
pushing.join().unwrap();
teardown(&set, &pool);
assert_eq!(drops.load(Ordering::Relaxed), 1);
});
}
#[test]
fn test_search_return_publishes_the_steal_to_the_last_return() {
const TASKS: usize = 2 * STEALABLE;
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(2);
builder.check(|| {
let pool = pool(3);
let set = Arc::new(Tasks::new(3));
let completed = Arc::new(AtomicUsize::new(0));
let ran: Arc<(Mutex<usize>, Condvar)> = Arc::new((Mutex::new(0), Condvar::new()));
let gate: Gate = Arc::new((Mutex::new(0), Condvar::new()));
let thief = thread::spawn({
let pool = pool.clone();
let set = set.clone();
let completed = completed.clone();
let gate = gate.clone();
move || {
let mut ready = pool.queue(1).owner();
let mut searcher = Searcher::new(&pool, 1);
assert!(!searcher.search(&mut ready));
reach(&gate, 1);
await_stage(&gate, 2);
let _ = searcher.search(&mut ready);
searcher.leave_search();
while let Some(runnable) = ready.pop() {
complete(&pool, &set, runnable, &completed, TASKS);
}
}
});
let parked = thread::spawn({
let pool = pool.clone();
let set = set.clone();
let completed = completed.clone();
move || {
let mut ready = pool.queue(2).owner();
work(&pool, &set, &mut ready, 2, &completed, TASKS, false);
}
});
await_stage(&gate, 1);
let mut ready = pool.queue(0).owner();
let handles: Vec<Task> = (0..TASKS)
.map(|_| {
let ran = ran.clone();
let future = poll_fn(move |_| {
*ran.0.lock().unwrap() += 1;
ran.1.notify_all();
Poll::Ready(())
});
let (task, runnable) =
Task::new(future, &set, std::sync::Arc::downgrade(&pool));
assert!(set.insert(task.clone()).is_ok());
pool.enqueue(&mut ready, runnable);
task
})
.collect();
reach(&gate, 2);
wait_ran_count(&ran, TASKS - 1);
thief.join().unwrap();
work(&pool, &set, &mut ready, 0, &completed, TASKS, false);
parked.join().unwrap();
for task in handles {
assert_eq!(refs(&task.state), 1);
}
});
}
#[test]
fn test_foreign_wake_racing_the_poll_path_is_never_lost() {
for by_value in [false, true] {
loom::model(move || {
let pool = pool(1);
let set = Tasks::new(1);
let signal = Arc::new(AtomicBool::new(false));
let drops = Arc::new(AtomicUsize::new(0));
let (task, runnable) = Task::new(
signaled(signal.clone(), &drops),
&set,
std::sync::Arc::downgrade(&pool),
);
assert!(set.insert(task.clone()).is_ok());
let waker = Waker::clone(&task.waker());
let waking = thread::spawn(move || {
waker.wake_by_ref();
signal.store(true, Ordering::Release);
if by_value {
waker.wake();
} else {
waker.wake_by_ref();
}
});
let mut completed = run(&set, runnable);
waking.join().unwrap();
while let Some(runnable) = pool.pop() {
assert!(!completed, "a completed task received a runnable");
completed = run(&set, runnable);
}
assert!(completed, "wake lost");
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(refs(&task.state), 1);
drop(task);
assert_eq!(std::sync::Arc::weak_count(&pool), 0);
});
}
}
#[test]
fn test_teardown_racing_a_foreign_wake_disposes_of_the_task_once() {
loom::model(|| {
let pool = pool(1);
let set = Tasks::new(1);
let drops = Arc::new(AtomicUsize::new(0));
let (task, runnable) =
Task::new(pending(&drops), &set, std::sync::Arc::downgrade(&pool));
let waker = Waker::clone(&task.waker());
assert!(set.insert(task).is_ok());
assert!(!run(&set, runnable));
let waking = thread::spawn(move || waker.wake());
teardown(&set, &pool);
waking.join().unwrap();
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(std::sync::Arc::weak_count(&pool), 0);
});
}
#[test]
fn test_registration_racing_teardown_disposes_of_the_task_once() {
loom::model(|| {
let pool = pool(1);
let set = Arc::new(Tasks::new(1));
let drops = Arc::new(AtomicUsize::new(0));
let registering = thread::spawn({
let set = set.clone();
let pool = std::sync::Arc::downgrade(&pool);
let future = pending(&drops);
move || {
if let Err(task) = set.register(future, pool) {
task.clear();
}
}
});
teardown(&set, &pool);
registering.join().unwrap();
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(std::sync::Arc::weak_count(&pool), 0);
});
}
#[test]
fn test_teardown_racing_the_poll_path_drops_the_future_once() {
loom::model(|| {
let pool = pool(1);
let set = Arc::new(Tasks::new(1));
let drops = Arc::new(AtomicUsize::new(0));
let guard = DropCount(drops.clone());
let mut woken = false;
let future = poll_fn(move |cx| {
let _ = &guard;
if !woken {
woken = true;
cx.waker().wake_by_ref();
}
Poll::<()>::Pending
});
let (task, runnable) = Task::new(future, &set, std::sync::Arc::downgrade(&pool));
assert!(set.insert(task).is_ok());
let tearing_down = thread::spawn({
let set = set.clone();
let pool = pool.clone();
move || teardown(&set, &pool)
});
run(&set, runnable);
tearing_down.join().unwrap();
assert_eq!(drops.load(Ordering::Relaxed), 1);
assert_eq!(std::sync::Arc::weak_count(&pool), 0);
});
}
#[test]
fn test_wake_racing_clear_of_an_idle_task_leaves_nothing_to_poll() {
loom::model(|| {
let state = Arc::new(State::new());
assert!(state.start_poll());
assert!(matches!(state.finish_pending(), AfterPending::Done));
let wake = thread::spawn({
let state = Arc::clone(&state);
move || state.notify_by_ref()
});
assert!(state.clear());
let _ = wake.join().unwrap();
assert!(!state.start_poll());
assert!(!state.notify_by_ref());
});
}
}