#[cfg(test)]
use crate::{Runner, Spawner};
#[cfg(test)]
use futures::stream::{FuturesUnordered, StreamExt};
use futures::task::ArcWake;
use std::{
any::Any,
future::Future,
pin::Pin,
sync::{Arc, Condvar, Mutex},
task::{Context, Poll},
};
pub mod buffer;
pub mod signal;
mod handle;
pub use handle::Handle;
pub(crate) use handle::{Aborter, MetricHandle, Panicked, Panicker};
mod cell;
pub use cell::Cell as ContextCell;
pub(crate) mod supervision;
#[derive(Copy, Clone, Debug)]
pub enum Execution {
Dedicated,
Shared(bool),
}
impl Default for Execution {
fn default() -> Self {
Self::Shared(false)
}
}
pub async fn reschedule() {
struct Reschedule {
yielded: bool,
}
impl Future for Reschedule {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.yielded {
Poll::Ready(())
} else {
self.yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
Reschedule { yielded: false }.await
}
fn extract_panic_message(err: &(dyn Any + Send)) -> String {
err.downcast_ref::<&str>().map_or_else(
|| {
err.downcast_ref::<String>()
.map_or_else(|| format!("{err:?}"), |s| s.clone())
},
|s| s.to_string(),
)
}
pub struct RwLock<T>(async_lock::RwLock<T>);
pub type RwLockReadGuard<'a, T> = async_lock::RwLockReadGuard<'a, T>;
pub type RwLockWriteGuard<'a, T> = async_lock::RwLockWriteGuard<'a, T>;
impl<T> RwLock<T> {
#[inline]
pub const fn new(value: T) -> Self {
Self(async_lock::RwLock::new(value))
}
#[inline]
pub async fn read(&self) -> RwLockReadGuard<'_, T> {
self.0.read().await
}
#[inline]
pub async fn write(&self) -> RwLockWriteGuard<'_, T> {
self.0.write().await
}
#[inline]
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {
self.0.try_read()
}
#[inline]
pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>> {
self.0.try_write()
}
#[inline]
pub fn get_mut(&mut self) -> &mut T {
self.0.get_mut()
}
#[inline]
pub fn into_inner(self) -> T {
self.0.into_inner()
}
}
pub struct Blocker {
state: Mutex<bool>,
cv: Condvar,
}
impl Blocker {
pub fn new() -> Arc<Self> {
Arc::new(Self {
state: Mutex::new(false),
cv: Condvar::new(),
})
}
pub fn wait(&self) {
let mut signaled = self.state.lock().unwrap();
while !*signaled {
signaled = self.cv.wait(signaled).unwrap();
}
*signaled = false;
}
}
impl ArcWake for Blocker {
fn wake_by_ref(arc_self: &Arc<Self>) {
{
let mut signaled = arc_self.state.lock().unwrap();
*signaled = true;
}
arc_self.cv.notify_one();
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn count_running_tasks(metrics: &impl crate::Metrics, prefix: &str) -> usize {
let encoded = metrics.encode();
encoded
.lines()
.filter(|line| {
line.starts_with("runtime_tasks_running{")
&& line.contains("kind=\"Task\"")
&& line.trim_end().ends_with(" 1")
&& line
.split("name=\"")
.nth(1)
.is_some_and(|s| s.split('"').next().unwrap_or("").starts_with(prefix))
})
.count()
}
pub fn validate_label(label: &str) {
let mut chars = label.chars();
assert!(
chars.next().is_some_and(|c| c.is_ascii_alphabetic()),
"label must start with [a-zA-Z]: {label}"
);
assert!(
chars.all(|c| c.is_ascii_alphanumeric() || c == '_'),
"label must only contain [a-zA-Z0-9_]: {label}"
);
}
#[cfg(test)]
async fn task(i: usize) -> usize {
for _ in 0..5 {
reschedule().await;
}
i
}
#[cfg(test)]
pub fn run_tasks(tasks: usize, runner: crate::deterministic::Runner) -> (String, Vec<usize>) {
runner.start(|context| async move {
let mut handles = FuturesUnordered::new();
for i in 0..=tasks - 1 {
handles.push(context.clone().spawn(move |_| task(i)));
}
let mut outputs = Vec::new();
while let Some(result) = handles.next().await {
outputs.push(result.unwrap());
}
assert_eq!(outputs.len(), tasks);
(context.auditor().state(), outputs)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::deterministic;
use commonware_macros::test_traced;
use futures::task::waker;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[test_traced]
fn test_rwlock() {
let executor = deterministic::Runner::default();
executor.start(|_| async move {
let lock = RwLock::new(100);
let r1 = lock.read().await;
let r2 = lock.read().await;
assert_eq!(*r1 + *r2, 200);
drop((r1, r2)); let mut w = lock.write().await;
*w += 1;
assert_eq!(*w, 101);
});
}
#[test]
fn test_blocker_waits_until_wake() {
let blocker = Blocker::new();
let started = Arc::new(AtomicBool::new(false));
let completed = Arc::new(AtomicBool::new(false));
let thread_blocker = blocker.clone();
let thread_started = started.clone();
let thread_completed = completed.clone();
let handle = std::thread::spawn(move || {
thread_started.store(true, Ordering::SeqCst);
thread_blocker.wait();
thread_completed.store(true, Ordering::SeqCst);
});
while !started.load(Ordering::SeqCst) {
std::thread::yield_now();
}
assert!(!completed.load(Ordering::SeqCst));
waker(blocker).wake();
handle.join().unwrap();
assert!(completed.load(Ordering::SeqCst));
}
#[test]
fn test_blocker_handles_pre_wake() {
let blocker = Blocker::new();
waker(blocker.clone()).wake();
let completed = Arc::new(AtomicBool::new(false));
let thread_blocker = blocker;
let thread_completed = completed.clone();
std::thread::spawn(move || {
thread_blocker.wait();
thread_completed.store(true, Ordering::SeqCst);
})
.join()
.unwrap();
assert!(completed.load(Ordering::SeqCst));
}
#[test]
fn test_blocker_reusable_across_signals() {
let blocker = Blocker::new();
let completed = Arc::new(AtomicUsize::new(0));
let thread_blocker = blocker.clone();
let thread_completed = completed.clone();
let handle = std::thread::spawn(move || {
for _ in 0..2 {
thread_blocker.wait();
thread_completed.fetch_add(1, Ordering::SeqCst);
}
});
for expected in 1..=2 {
waker(blocker.clone()).wake();
while completed.load(Ordering::SeqCst) < expected {
std::thread::yield_now();
}
}
handle.join().unwrap();
assert_eq!(completed.load(Ordering::SeqCst), 2);
}
#[test_traced]
fn test_count_running_tasks() {
use crate::{Metrics, Runner, Spawner};
use futures::future;
let executor = deterministic::Runner::default();
executor.start(|context| async move {
assert_eq!(
count_running_tasks(&context, "worker"),
0,
"no worker tasks initially"
);
let worker_ctx = context.with_label("worker");
let handle1 = worker_ctx.clone().spawn(|_| async move {
future::pending::<()>().await;
});
let count = count_running_tasks(&context, "worker");
assert_eq!(count, 1, "worker task should be running");
assert_eq!(
count_running_tasks(&context, "other"),
0,
"no tasks with 'other' prefix"
);
let handle2 = worker_ctx.with_label("child").spawn(|_| async move {
future::pending::<()>().await;
});
let count = count_running_tasks(&context, "worker");
assert_eq!(count, 2, "both worker and worker_child should be counted");
handle1.abort();
let _ = handle1.await;
let count = count_running_tasks(&context, "worker");
assert_eq!(count, 1, "only worker_child should remain");
handle2.abort();
let _ = handle2.await;
assert_eq!(
count_running_tasks(&context, "worker"),
0,
"all worker tasks should be stopped"
);
});
}
}