use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::sync::Notify as BackendNotify;
mod broadcast;
pub use broadcast::{
broadcast_channel, BroadcastReceiver, BroadcastRecvError, BroadcastSender,
BroadcastTryRecvError,
};
#[cfg(feature = "event-stream")]
pub use broadcast::{BroadcastLagged, BroadcastStream};
pub const BACKEND_NAME: &str = "tokio";
pub const BACKEND_VERSION: &str = "1.53.1";
#[derive(Debug)]
#[must_use = "dropping a Task cancels it; call `.detach()` to run it in the \
background, or await/store the handle to join it"]
pub struct Task<T> {
inner: tokio::task::JoinHandle<T>,
detached: bool,
}
impl<T> Task<T> {
fn new(inner: tokio::task::JoinHandle<T>) -> Self {
Self {
inner,
detached: false,
}
}
pub fn cancel(&self) {
self.inner.abort();
}
pub fn is_finished(&self) -> bool {
self.inner.is_finished()
}
pub fn detach(mut self) {
self.detached = true;
}
}
impl<T> Drop for Task<T> {
fn drop(&mut self) {
if !self.detached {
self.inner.abort();
}
}
}
impl<T> Future for Task<T> {
type Output = Result<T, TaskError>;
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(context)
.map(|result| result.map_err(TaskError::from_backend))
}
}
#[derive(Debug)]
pub struct TaskError {
inner: tokio::task::JoinError,
}
impl TaskError {
fn from_backend(inner: tokio::task::JoinError) -> Self {
Self { inner }
}
pub fn is_cancelled(&self) -> bool {
self.inner.is_cancelled()
}
pub fn is_panic(&self) -> bool {
self.inner.is_panic()
}
}
impl std::fmt::Display for TaskError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(formatter)
}
}
impl std::error::Error for TaskError {}
pub struct Runtime {
inner: tokio::runtime::Runtime,
drivers_enabled: bool,
}
impl Runtime {
pub fn interrupt_signal(&self) -> std::io::Result<InterruptSignal<'_>> {
if !self.drivers_enabled {
return Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"interrupt notifications require enabled runtime drivers",
));
}
let _entered = self.inner.enter();
Ok(InterruptSignal {
inner: crate::platform_imp::interrupt::InterruptReceiver::new()?,
_runtime: self,
})
}
pub async fn wait_for_interrupt(&self) -> std::io::Result<()> {
self.interrupt_signal()?.wait().await
}
pub fn run<F: Future>(&self, future: F) -> F::Output {
self.inner.block_on(future)
}
pub fn handle(&self) -> RuntimeHandle {
RuntimeHandle {
inner: self.inner.handle().clone(),
}
}
}
pub struct RuntimeBuilder {
inner: tokio::runtime::Builder,
drivers_enabled: bool,
}
impl RuntimeBuilder {
pub fn current_thread() -> Self {
Self {
inner: tokio::runtime::Builder::new_current_thread(),
drivers_enabled: false,
}
}
pub fn multi_thread() -> Self {
Self {
inner: tokio::runtime::Builder::new_multi_thread(),
drivers_enabled: false,
}
}
pub fn enable_all(mut self) -> Self {
self.inner.enable_all();
self.drivers_enabled = true;
self
}
pub fn worker_threads(mut self, count: usize) -> Self {
self.inner.worker_threads(count);
self
}
pub fn thread_name(mut self, name: impl Into<String>) -> Self {
self.inner.thread_name(name.into());
self
}
pub fn build(mut self) -> std::io::Result<Runtime> {
self.inner.build().map(|inner| Runtime {
inner,
drivers_enabled: self.drivers_enabled,
})
}
}
pub struct InterruptSignal<'runtime> {
inner: crate::platform_imp::interrupt::InterruptReceiver,
_runtime: &'runtime Runtime,
}
impl InterruptSignal<'_> {
pub async fn wait(&mut self) -> std::io::Result<()> {
self.inner.recv().await.ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::BrokenPipe, "interrupt receiver closed")
})
}
}
#[derive(Clone, Debug)]
pub struct RuntimeHandle {
inner: tokio::runtime::Handle,
}
impl PartialEq for RuntimeHandle {
fn eq(&self, other: &Self) -> bool {
self.inner.id() == other.inner.id()
}
}
impl Eq for RuntimeHandle {}
impl RuntimeHandle {
pub fn current() -> Result<Self, NoRuntime> {
tokio::runtime::Handle::try_current()
.map(|inner| Self { inner })
.map_err(|_| NoRuntime)
}
pub fn launch<F>(&self, future: F) -> Task<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
Task::new(self.inner.spawn(future))
}
pub fn launch_blocking<F, R>(&self, operation: F) -> Task<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
Task::new(self.inner.spawn_blocking(operation))
}
#[cfg(feature = "wasm-sketch-host")]
pub(crate) fn block_on_wasm<F>(&self, future: F) -> F::Output
where
F: Future,
{
self.inner.block_on(future)
}
#[cfg(feature = "wasm-sketch-host")]
pub(crate) fn same_runtime_for_wasm(&self, other: &Self) -> bool {
self.inner.id() == other.inner.id()
}
}
#[derive(Clone, Copy, Debug, thiserror::Error)]
#[error("no kernal-api async runtime is active on this thread")]
pub struct NoRuntime;
pub fn launch<F>(future: F) -> Task<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
Task::new(tokio::spawn(future))
}
pub fn launch_blocking<F, R>(operation: F) -> Task<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
Task::new(tokio::task::spawn_blocking(operation))
}
pub async fn join<A, B>(first: A, second: B) -> (A::Output, B::Output)
where
A: Future,
B: Future,
{
tokio::join!(first, second)
}
#[derive(Debug)]
pub struct TaskGroup<T> {
inner: tokio::task::JoinSet<T>,
}
impl<T: 'static> TaskGroup<T> {
pub fn new() -> Self {
Self {
inner: tokio::task::JoinSet::new(),
}
}
pub fn spawn<F>(&mut self, future: F)
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
self.inner.spawn(future);
}
pub fn spawn_blocking<F>(&mut self, operation: F)
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
self.inner.spawn_blocking(operation);
}
pub async fn join_next(&mut self) -> Option<Result<T, TaskError>> {
self.inner
.join_next()
.await
.map(|result| result.map_err(TaskError::from_backend))
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl<T: 'static> Default for TaskGroup<T> {
fn default() -> Self {
Self::new()
}
}
pub async fn yield_now() {
tokio::task::yield_now().await;
}
pub async fn sleep(duration: Duration) {
tokio::time::sleep(duration).await;
}
pub async fn sleep_until(deadline: Deadline) {
tokio::time::sleep_until(deadline.at).await;
}
#[derive(Debug)]
pub struct PeriodicTimer {
inner: tokio::time::Interval,
}
impl PeriodicTimer {
pub fn new(period: Duration) -> std::io::Result<Self> {
if period.is_zero() || period > Duration::from_secs(365 * 24 * 60 * 60) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"periodic timer period must be positive and at most 365 days",
));
}
RuntimeHandle::current().map_err(std::io::Error::other)?;
let mut inner = tokio::time::interval(period);
inner.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Burst);
Ok(Self { inner })
}
pub async fn tick(&mut self) {
self.inner.tick().await;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Deadline {
at: tokio::time::Instant,
}
impl Deadline {
pub fn after(duration: Duration) -> Self {
Self {
at: tokio::time::Instant::now() + duration,
}
}
pub fn remaining(self) -> Duration {
self.at
.checked_duration_since(tokio::time::Instant::now())
.unwrap_or(Duration::ZERO)
}
pub fn is_elapsed(self) -> bool {
self.remaining().is_zero()
}
}
pub async fn timeout<F: Future>(
duration: Duration,
future: F,
) -> Result<F::Output, DeadlineElapsed> {
timeout_at(Deadline::after(duration), future).await
}
pub async fn timeout_at<F: Future>(
deadline: Deadline,
future: F,
) -> Result<F::Output, DeadlineElapsed> {
tokio::time::timeout_at(deadline.at, future)
.await
.map_err(|_| DeadlineElapsed)
}
#[derive(Clone, Copy, Debug, thiserror::Error)]
#[error("the kernal-api async operation exceeded its deadline")]
pub struct DeadlineElapsed;
#[derive(Clone, Debug)]
pub struct CancellationSource {
state: Arc<CancellationState>,
}
#[derive(Debug)]
struct CancellationState {
cancelled: AtomicBool,
notify: BackendNotify,
#[cfg(test)]
cancel_after_waiter_check: AtomicBool,
}
#[derive(Clone, Debug)]
pub struct CancellationToken {
state: Arc<CancellationState>,
}
impl CancellationSource {
pub fn new() -> Self {
Self {
state: Arc::new(CancellationState {
cancelled: AtomicBool::new(false),
notify: BackendNotify::new(),
#[cfg(test)]
cancel_after_waiter_check: AtomicBool::new(false),
}),
}
}
pub fn token(&self) -> CancellationToken {
CancellationToken {
state: Arc::clone(&self.state),
}
}
pub fn cancel(&self) {
if !self.state.cancelled.swap(true, Ordering::AcqRel) {
self.state.notify.notify_waiters();
}
}
pub fn is_cancelled(&self) -> bool {
self.state.cancelled.load(Ordering::Acquire)
}
}
impl Default for CancellationSource {
fn default() -> Self {
Self::new()
}
}
impl CancellationToken {
pub fn is_cancelled(&self) -> bool {
self.state.cancelled.load(Ordering::Acquire)
}
pub async fn cancelled(&self) {
loop {
if self.is_cancelled() {
return;
}
let notified = self.state.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.is_cancelled() {
return;
}
#[cfg(test)]
if self
.state
.cancel_after_waiter_check
.swap(false, Ordering::AcqRel)
{
self.state.cancelled.store(true, Ordering::Release);
self.state.notify.notify_waiters();
}
notified.await;
}
}
}
pub async fn cancellable<F>(token: &CancellationToken, future: F) -> Result<F::Output, Cancelled>
where
F: Future,
{
tokio::select! {
biased;
() = token.cancelled() => Err(Cancelled),
output = future => Ok(output),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[error("the kernal-api async operation was cancelled")]
pub struct Cancelled;
#[derive(Debug)]
pub struct Notify {
inner: BackendNotify,
}
impl Notify {
pub fn new() -> Self {
Self {
inner: BackendNotify::new(),
}
}
pub fn notify_one(&self) {
self.inner.notify_one();
}
pub fn notify_waiters(&self) {
self.inner.notify_waiters();
}
pub async fn notified(&self) {
self.inner.notified().await;
}
}
impl Default for Notify {
fn default() -> Self {
Self::new()
}
}
pub async fn connection_timeout<F>(
duration: Duration,
future: F,
) -> Result<F::Output, ConnectionDeadlineElapsed>
where
F: Future,
{
connection_until(Deadline::after(duration), future).await
}
pub async fn connection_until<F>(
deadline: Deadline,
future: F,
) -> Result<F::Output, ConnectionDeadlineElapsed>
where
F: Future,
{
tokio::time::timeout_at(deadline.at, future)
.await
.map_err(|_| ConnectionDeadlineElapsed)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[error("the kernal-api connection deadline elapsed before the operation completed")]
pub struct ConnectionDeadlineElapsed;
#[derive(Clone, Debug)]
pub struct ProgressReporter {
state: Arc<ProgressState>,
}
#[derive(Debug)]
struct ProgressState {
last_progress: Mutex<tokio::time::Instant>,
notify: BackendNotify,
}
#[derive(Clone, Debug)]
pub struct ProgressWatch {
state: Arc<ProgressState>,
}
impl ProgressReporter {
pub fn new() -> Self {
Self {
state: Arc::new(ProgressState {
last_progress: Mutex::new(tokio::time::Instant::now()),
notify: BackendNotify::new(),
}),
}
}
pub fn watch(&self) -> ProgressWatch {
ProgressWatch {
state: Arc::clone(&self.state),
}
}
pub fn restart_idle_window(&self) {
*self
.state
.last_progress
.lock()
.expect("progress state mutex must not be poisoned") = tokio::time::Instant::now();
self.state.notify.notify_waiters();
}
pub fn report_progress(&self) {
self.restart_idle_window();
}
}
impl Default for ProgressReporter {
fn default() -> Self {
Self::new()
}
}
impl ProgressWatch {
async fn wait_until_stalled(&self, idle: Duration) -> ProgressIdleElapsed {
loop {
let notified = self.state.notify.notified();
let deadline = *self
.state
.last_progress
.lock()
.expect("progress state mutex must not be poisoned")
+ idle;
let sleep = tokio::time::sleep_until(deadline);
tokio::pin!(sleep);
tokio::select! {
() = notified => continue,
() = &mut sleep => {
let last_progress = *self
.state
.last_progress
.lock()
.expect("progress state mutex must not be poisoned");
if last_progress + idle <= tokio::time::Instant::now() {
return ProgressIdleElapsed;
}
}
}
}
}
}
pub async fn progress_timeout<F>(
idle: Duration,
watch: &ProgressWatch,
future: F,
) -> Result<F::Output, ProgressIdleElapsed>
where
F: Future,
{
tokio::select! {
output = future => Ok(output),
elapsed = watch.wait_until_stalled(idle) => Err(elapsed),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[error("the kernal-api async operation made no progress before its idle deadline")]
pub struct ProgressIdleElapsed;
#[derive(Clone, Debug)]
pub struct Semaphore {
inner: Arc<tokio::sync::Semaphore>,
}
impl Semaphore {
pub const MAX_PERMITS: usize = tokio::sync::Semaphore::MAX_PERMITS;
pub fn new(permits: usize) -> Self {
Self {
inner: Arc::new(tokio::sync::Semaphore::new(permits)),
}
}
pub fn available_permits(&self) -> usize {
self.inner.available_permits()
}
pub fn try_acquire(&self) -> Option<SemaphorePermit> {
Arc::clone(&self.inner)
.try_acquire_owned()
.ok()
.map(|permit| SemaphorePermit { _permit: permit })
}
pub async fn acquire(&self) -> SemaphorePermit {
SemaphorePermit {
_permit: Arc::clone(&self.inner)
.acquire_owned()
.await
.expect("kernal-api semaphore is never closed"),
}
}
pub async fn acquire_many(&self, permits: u32) -> SemaphorePermit {
SemaphorePermit {
_permit: Arc::clone(&self.inner)
.acquire_many_owned(permits)
.await
.expect("kernal-api semaphore is never closed"),
}
}
}
#[derive(Debug)]
pub struct SemaphorePermit {
_permit: tokio::sync::OwnedSemaphorePermit,
}
#[derive(Debug)]
pub struct Sender<T> {
inner: tokio::sync::mpsc::Sender<T>,
}
impl<T> Clone for Sender<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T> Sender<T> {
pub async fn send(&self, value: T) -> Result<(), SendError<T>> {
self.inner
.send(value)
.await
.map_err(|error| SendError(error.0))
}
pub fn blocking_send(&self, value: T) -> Result<(), BlockingSendError<T>> {
if RuntimeHandle::current().is_ok() {
return Err(BlockingSendError::AsyncContext(value));
}
self.inner
.blocking_send(value)
.map_err(|error| BlockingSendError::Closed(error.0))
}
pub fn try_send(&self, value: T) -> Result<(), TrySendError<T>> {
self.inner.try_send(value).map_err(|error| match error {
tokio::sync::mpsc::error::TrySendError::Full(value) => TrySendError::Full(value),
tokio::sync::mpsc::error::TrySendError::Closed(value) => TrySendError::Closed(value),
})
}
pub fn is_closed(&self) -> bool {
self.inner.is_closed()
}
}
#[derive(Debug)]
pub struct Receiver<T> {
inner: tokio::sync::mpsc::Receiver<T>,
}
impl<T> Receiver<T> {
pub async fn recv(&mut self) -> Option<T> {
self.inner.recv().await
}
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
self.inner.try_recv().map_err(|error| match error {
tokio::sync::mpsc::error::TryRecvError::Empty => TryRecvError::Empty,
tokio::sync::mpsc::error::TryRecvError::Disconnected => TryRecvError::Disconnected,
})
}
pub fn close(&mut self) {
self.inner.close();
}
}
pub fn channel<T>(capacity: usize) -> (Sender<T>, Receiver<T>) {
let (sender, receiver) = tokio::sync::mpsc::channel(capacity);
(Sender { inner: sender }, Receiver { inner: receiver })
}
#[derive(Debug)]
pub struct UnboundedSender<T> {
inner: tokio::sync::mpsc::UnboundedSender<T>,
}
impl<T> Clone for UnboundedSender<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T> UnboundedSender<T> {
pub fn send(&self, value: T) -> Result<(), SendError<T>> {
self.inner.send(value).map_err(|error| SendError(error.0))
}
pub fn is_closed(&self) -> bool {
self.inner.is_closed()
}
}
#[derive(Debug)]
pub struct UnboundedReceiver<T> {
inner: tokio::sync::mpsc::UnboundedReceiver<T>,
}
impl<T> UnboundedReceiver<T> {
pub async fn recv(&mut self) -> Option<T> {
self.inner.recv().await
}
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
self.inner.try_recv().map_err(|error| match error {
tokio::sync::mpsc::error::TryRecvError::Empty => TryRecvError::Empty,
tokio::sync::mpsc::error::TryRecvError::Disconnected => TryRecvError::Disconnected,
})
}
pub fn close(&mut self) {
self.inner.close();
}
}
pub fn unbounded_channel<T>() -> (UnboundedSender<T>, UnboundedReceiver<T>) {
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
(
UnboundedSender { inner: sender },
UnboundedReceiver { inner: receiver },
)
}
#[derive(Clone, Copy, Debug, thiserror::Error)]
#[error("the kernal-api channel receiver has been dropped")]
pub struct SendError<T>(pub T);
#[derive(Clone, Copy, Debug, thiserror::Error)]
pub enum BlockingSendError<T> {
#[error("the kernal-api channel receiver is closed")]
Closed(T),
#[error("blocking channel send cannot run inside a runtime")]
AsyncContext(T),
}
#[derive(Clone, Copy, Debug, thiserror::Error)]
pub enum TrySendError<T> {
#[error("the kernal-api channel is at capacity")]
Full(T),
#[error("the kernal-api channel receiver has been dropped")]
Closed(T),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum TryRecvError {
#[error("the kernal-api channel has no value available right now")]
Empty,
#[error("the kernal-api channel sender has been dropped")]
Disconnected,
}
#[derive(Debug)]
pub struct OneshotSender<T> {
inner: tokio::sync::oneshot::Sender<T>,
}
impl<T> OneshotSender<T> {
pub fn send(self, value: T) -> Result<(), T> {
self.inner.send(value)
}
pub fn is_closed(&self) -> bool {
self.inner.is_closed()
}
}
#[derive(Debug)]
pub struct OneshotReceiver<T> {
inner: tokio::sync::oneshot::Receiver<T>,
}
impl<T> OneshotReceiver<T> {
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
self.inner.try_recv().map_err(|error| match error {
tokio::sync::oneshot::error::TryRecvError::Empty => TryRecvError::Empty,
tokio::sync::oneshot::error::TryRecvError::Closed => TryRecvError::Disconnected,
})
}
}
impl<T> Future for OneshotReceiver<T> {
type Output = Result<T, OneshotReceiverClosed>;
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(context)
.map(|result| result.map_err(|_| OneshotReceiverClosed))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[error("the kernal-api channel sender was dropped without sending a value")]
pub struct OneshotReceiverClosed;
pub fn oneshot_channel<T>() -> (OneshotSender<T>, OneshotReceiver<T>) {
let (sender, receiver) = tokio::sync::oneshot::channel();
(
OneshotSender { inner: sender },
OneshotReceiver { inner: receiver },
)
}
#[cfg(feature = "tokio-console")]
pub use crate::runtime::{DiagnosticsConfig, DiagnosticsInstallError};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn owned_runtime_launches_owned_tasks() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
let answer = runtime.run(async { launch(async { 42_u8 }).await.unwrap() });
assert_eq!(answer, 42);
}
#[test]
fn deadline_error_is_facade_owned() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
let result = runtime.run(timeout(Duration::ZERO, std::future::pending::<()>()));
assert!(result.is_err());
}
#[test]
fn cancellation_source_unblocks_a_pending_operation() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let source = CancellationSource::new();
let token = source.token();
let waiter = launch({
let token = token.clone();
async move { cancellable(&token, std::future::pending::<()>()).await }
});
yield_now().await;
source.cancel();
assert_eq!(waiter.await.unwrap(), Err(Cancelled));
});
}
#[test]
fn cancellation_between_state_check_and_waiter_poll_is_not_lost() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let source = CancellationSource::new();
let token = source.token();
token
.state
.cancel_after_waiter_check
.store(true, Ordering::Release);
token.cancelled().await;
assert!(source.is_cancelled());
});
}
#[test]
fn connection_deadline_has_its_own_typed_error() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
let deadline = Deadline::after(Duration::ZERO);
assert!(deadline.is_elapsed());
let result = runtime.run(connection_until(deadline, std::future::pending::<()>()));
assert_eq!(result, Err(ConnectionDeadlineElapsed));
}
#[test]
fn progress_keeps_an_operation_alive_beyond_one_idle_window() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
tokio::time::pause();
let reporter = ProgressReporter::new();
let watch = reporter.watch();
let operation = async move {
for _ in 0..3 {
tokio::time::advance(Duration::from_millis(10)).await;
reporter.report_progress();
}
7_u8
};
assert_eq!(
progress_timeout(Duration::from_millis(15), &watch, operation).await,
Ok(7)
);
});
}
#[test]
fn scheduled_progress_after_its_idle_deadline_is_not_observed_progress() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
tokio::time::pause();
let reporter = ProgressReporter::new();
let watch = reporter.watch();
let release_progress = Arc::new(BackendNotify::new());
let progress_task = launch({
let reporter = reporter.clone();
let release_progress = Arc::clone(&release_progress);
async move {
release_progress.notified().await;
reporter.report_progress();
}
});
let mut result = std::pin::pin!(progress_timeout(
Duration::from_millis(10),
&watch,
std::future::pending::<()>(),
));
std::future::poll_fn(|context| {
assert!(matches!(
result.as_mut().poll(context),
std::task::Poll::Pending
));
std::task::Poll::Ready(())
})
.await;
tokio::time::advance(Duration::from_millis(10)).await;
assert_eq!(result.await, Err(ProgressIdleElapsed));
release_progress.notify_one();
progress_task.await.unwrap();
});
}
#[test]
fn stalled_operation_expires_after_the_progress_idle_window() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
tokio::time::pause();
let reporter = ProgressReporter::new();
let watch = reporter.watch();
let mut result = std::pin::pin!(progress_timeout(
Duration::from_millis(10),
&watch,
std::future::pending::<()>(),
));
std::future::poll_fn(|context| {
assert!(matches!(
result.as_mut().poll(context),
std::task::Poll::Pending
));
std::task::Poll::Ready(())
})
.await;
tokio::time::advance(Duration::from_millis(10)).await;
assert_eq!(result.await, Err(ProgressIdleElapsed));
});
}
#[test]
fn dropping_a_task_cancels_it() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
tokio::time::pause();
let completed = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&completed);
let task = launch(async move {
sleep(Duration::from_millis(10)).await;
flag.store(true, Ordering::Release);
});
yield_now().await;
drop(task);
tokio::time::advance(Duration::from_millis(20)).await;
yield_now().await;
assert!(
!completed.load(Ordering::Acquire),
"a dropped task must not run to completion"
);
});
}
#[test]
fn a_detached_task_keeps_running_after_its_handle_is_dropped() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
tokio::time::pause();
let completed = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&completed);
launch(async move {
sleep(Duration::from_millis(10)).await;
flag.store(true, Ordering::Release);
})
.detach();
yield_now().await;
tokio::time::advance(Duration::from_millis(20)).await;
yield_now().await;
assert!(
completed.load(Ordering::Acquire),
"a detached task must keep running after its handle is dropped"
);
});
}
#[test]
fn join_awaits_both_futures_concurrently() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let (first, second) = join(async { 1_u8 }, async { 2_u8 }).await;
assert_eq!((first, second), (1, 2));
});
}
#[test]
fn task_group_collects_results_as_tasks_complete() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let mut group = TaskGroup::new();
for value in 0_u8..3 {
group.spawn(async move { value });
}
let mut collected = Vec::new();
while let Some(result) = group.join_next().await {
collected.push(result.unwrap());
}
collected.sort_unstable();
assert_eq!(collected, vec![0, 1, 2]);
assert!(group.is_empty());
});
}
#[test]
fn notify_wakes_a_registered_waiter() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let notify = Arc::new(Notify::new());
let waiter_notify = Arc::clone(¬ify);
let waiter = launch(async move {
waiter_notify.notified().await;
});
yield_now().await;
notify.notify_one();
waiter.await.unwrap();
});
}
#[test]
fn semaphore_serializes_access_to_a_single_permit() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let semaphore = Semaphore::new(1);
assert_eq!(semaphore.available_permits(), 1);
let first = semaphore.acquire().await;
assert_eq!(semaphore.available_permits(), 0);
let waiting = semaphore.clone();
let acquired_second = launch(async move { waiting.acquire().await });
yield_now().await;
assert_eq!(semaphore.available_permits(), 0);
drop(first);
let second = acquired_second.await.unwrap();
assert_eq!(semaphore.available_permits(), 0);
drop(second);
assert_eq!(semaphore.available_permits(), 1);
});
}
#[test]
fn bounded_channel_reports_capacity_and_disconnect_without_waiting() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let (sender, mut receiver) = channel::<u8>(1);
sender.try_send(1).unwrap();
assert!(matches!(sender.try_send(2), Err(TrySendError::Full(2))));
assert_eq!(receiver.try_recv(), Ok(1));
assert_eq!(receiver.try_recv(), Err(TryRecvError::Empty));
drop(sender);
assert_eq!(receiver.try_recv(), Err(TryRecvError::Disconnected));
});
}
#[test]
fn bounded_channel_delivers_values_in_order() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let (sender, mut receiver) = channel::<u8>(4);
sender.send(1).await.unwrap();
sender.send(2).await.unwrap();
assert_eq!(receiver.recv().await, Some(1));
assert_eq!(receiver.recv().await, Some(2));
drop(sender);
assert_eq!(receiver.recv().await, None);
});
}
#[test]
fn unbounded_channel_delivers_values_without_waiting() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let (sender, mut receiver) = unbounded_channel::<u8>();
sender.send(7).unwrap();
assert_eq!(receiver.recv().await, Some(7));
drop(sender);
assert_eq!(receiver.recv().await, None);
});
}
#[test]
fn oneshot_channel_delivers_its_single_value() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let (sender, receiver) = oneshot_channel::<u8>();
sender.send(9).unwrap();
assert_eq!(receiver.await, Ok(9));
});
}
#[test]
fn oneshot_receiver_observes_a_dropped_sender() {
let runtime = RuntimeBuilder::current_thread()
.enable_all()
.build()
.unwrap();
runtime.run(async {
let (sender, receiver) = oneshot_channel::<u8>();
drop(sender);
assert_eq!(receiver.await, Err(OneshotReceiverClosed));
});
}
}