use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::num::NonZeroUsize;
use std::panic::AssertUnwindSafe;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::time::{Duration, Instant};
use async_channel::{Receiver, Sender};
use async_executor::LocalExecutor;
use futures_lite::future::{self, FutureExt};
use crate::error::{ShutdownOutcome, SpawnError};
use crate::lifecycle::{Lifecycle, CLOSED, RUNNING};
use crate::task::{BridgeCompletionGuard, BridgeDriver, Completion, Task};
pub struct LocalDomain {
executor: LocalExecutor<'static>,
inbox: Receiver<InboxCommand>,
sender: Sender<InboxCommand>,
shared: Arc<Shared>,
_not_send_or_sync: PhantomData<Rc<()>>,
}
#[derive(Clone)]
pub struct LocalSpawner {
sender: Sender<InboxCommand>,
shared: Weak<Shared>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct RunStats {
pub drive_steps: usize,
pub elapsed: Duration,
pub inbox_commands: usize,
}
struct Shared {
lifecycle: Lifecycle,
gate: Mutex<()>,
accepted_tasks: AtomicUsize,
}
impl Shared {
fn complete_one(&self) {
let previous = self.accepted_tasks.fetch_sub(1, Ordering::AcqRel);
debug_assert!(previous > 0, "local accepted task count underflow");
}
}
struct AcceptedGuard(Option<Arc<Shared>>);
type RunCommand = Box<dyn FnOnce(&LocalExecutor<'static>) + Send + 'static>;
impl AcceptedGuard {
fn new(shared: Arc<Shared>) -> Self {
Self(Some(shared))
}
}
impl Drop for AcceptedGuard {
fn drop(&mut self) {
if let Some(shared) = self.0.take() {
shared.complete_one();
}
}
}
struct InboxCommand {
run: Option<RunCommand>,
cancel: Option<Box<dyn FnOnce() + Send + 'static>>,
}
#[derive(Clone, Copy)]
struct DriveProgress {
made_progress: bool,
inbox_commands: usize,
}
impl InboxCommand {
fn run(mut self, executor: &LocalExecutor<'static>, running: bool) {
if running {
if let Some(run) = self.run.take() {
run(executor);
}
} else if let Some(cancel) = self.cancel.take() {
cancel();
}
}
fn cancel(mut self) {
if let Some(cancel) = self.cancel.take() {
cancel();
}
}
}
impl LocalDomain {
pub fn new() -> Self {
let (sender, inbox) = async_channel::unbounded();
Self {
executor: LocalExecutor::new(),
inbox,
sender,
shared: Arc::new(Shared {
lifecycle: Lifecycle::new(),
gate: Mutex::new(()),
accepted_tasks: AtomicUsize::new(0),
}),
_not_send_or_sync: PhantomData,
}
}
pub fn spawner(&self) -> LocalSpawner {
LocalSpawner {
sender: self.sender.clone(),
shared: Arc::downgrade(&self.shared),
}
}
pub fn spawn_local<F, T>(&self, future: F) -> Result<Task<T>, SpawnError>
where
F: Future<Output = T> + 'static,
T: 'static,
{
let _gate = self
.shared
.gate
.lock()
.expect("local lifecycle mutex poisoned");
if self.shared.lifecycle.load() != RUNNING {
return Err(SpawnError::Closed);
}
self.shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
let guard = AcceptedGuard::new(Arc::clone(&self.shared));
let task = self.executor.spawn(async move {
let _guard = guard;
future.await
});
Ok(Task::direct(task))
}
pub fn is_empty(&self) -> bool {
self.shared.accepted_tasks.load(Ordering::Acquire) == 0
&& self.inbox.is_empty()
&& self.executor.is_empty()
}
pub fn try_tick(&self) -> bool {
self.try_drive_step().made_progress
}
pub fn run_n(&self, max_steps: usize) -> usize {
let mut drive_steps = 0;
while drive_steps < max_steps {
if !self.try_drive_step().made_progress {
break;
}
drive_steps += 1;
}
drive_steps
}
pub fn run_for(&self, budget: Duration) -> RunStats {
let started = Instant::now();
let mut stats = RunStats::default();
while started.elapsed() < budget {
let progress = self.try_drive_step();
if !progress.made_progress {
break;
}
stats.drive_steps += 1;
stats.inbox_commands += progress.inbox_commands;
}
stats.elapsed = started.elapsed();
stats
}
fn try_drive_step(&self) -> DriveProgress {
if let Ok(command) = self.inbox.try_recv() {
command.run(&self.executor, self.shared.lifecycle.load() != CLOSED);
let _ = self.executor.try_tick();
DriveProgress {
made_progress: true,
inbox_commands: 1,
}
} else {
DriveProgress {
made_progress: self.executor.try_tick(),
inbox_commands: 0,
}
}
}
pub async fn tick(&self) {
if self.try_tick() {
return;
}
future::race(async { self.executor.tick().await }, async {
if let Ok(command) = self.inbox.recv().await {
command.run(&self.executor, self.shared.lifecycle.load() != CLOSED);
}
})
.await;
}
pub async fn run<F: Future>(&self, future: F) -> F::Output {
future::race(future, async {
loop {
self.tick().await;
}
})
.await
}
pub async fn shutdown_graceful(mut self) {
self.begin_close();
while self.shared.accepted_tasks.load(Ordering::Acquire) != 0 {
self.tick().await;
}
self.shared.lifecycle.finish_close();
self.cancel_inbox();
}
pub async fn shutdown_until<D>(mut self, deadline: D) -> ShutdownOutcome
where
D: Future,
{
self.begin_close();
let drained = async {
while self.shared.accepted_tasks.load(Ordering::Acquire) != 0 {
self.tick().await;
}
};
let completed = future::race(
async {
drained.await;
true
},
async {
deadline.await;
false
},
)
.await;
if completed {
self.shared.lifecycle.finish_close();
self.cancel_inbox();
ShutdownOutcome::Completed
} else {
let remaining = self.shared.accepted_tasks.load(Ordering::Acquire);
self.shutdown_now_inner();
ShutdownOutcome::TimedOut {
remaining_tasks: remaining,
}
}
}
pub fn shutdown_now(mut self) {
self.shutdown_now_inner();
}
fn begin_close(&self) {
let _gate = self
.shared
.gate
.lock()
.expect("local lifecycle mutex poisoned");
self.shared.lifecycle.begin_close();
}
fn cancel_inbox(&mut self) {
while let Ok(command) = self.inbox.try_recv() {
command.cancel();
}
}
fn shutdown_now_inner(&mut self) {
self.begin_close();
self.cancel_inbox();
self.shared.lifecycle.finish_close();
}
}
impl Drop for LocalDomain {
fn drop(&mut self) {
self.shutdown_now_inner();
}
}
impl LocalSpawner {
pub fn spawn<F, T>(&self, future: F) -> Result<Task<T>, SpawnError>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let Some(shared) = self.shared.upgrade() else {
return Err(SpawnError::Closed);
};
let _gate = shared.gate.lock().expect("local lifecycle mutex poisoned");
if shared.lifecycle.load() != RUNNING {
return Err(SpawnError::Closed);
}
shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
let completed_shared = Arc::clone(&shared);
let (task, driver) = Task::bridge(move || completed_shared.complete_one());
let command = remote_command(future, driver);
match self.sender.try_send(command) {
Ok(()) => Ok(task),
Err(error) => {
error.into_inner().cancel();
Err(SpawnError::Closed)
}
}
}
pub fn dispatch<F>(&self, callback: F) -> Result<(), SpawnError>
where
F: FnOnce() + Send + 'static,
{
self.dispatch_future(async move { callback() })
}
pub fn dispatch_future<F>(&self, future: F) -> Result<(), SpawnError>
where
F: Future<Output = ()> + Send + 'static,
{
let Some(shared) = self.shared.upgrade() else {
return Err(SpawnError::Closed);
};
let _gate = shared.gate.lock().expect("local lifecycle mutex poisoned");
if shared.lifecycle.load() != RUNNING {
return Err(SpawnError::Closed);
}
shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
let guard = AcceptedGuard::new(Arc::clone(&shared));
let command = dispatch_command(future, guard);
match self.sender.try_send(command) {
Ok(()) => Ok(()),
Err(error) => {
error.into_inner().cancel();
Err(SpawnError::Closed)
}
}
}
}
fn dispatch_command<F>(future: F, guard: AcceptedGuard) -> InboxCommand
where
F: Future<Output = ()> + Send + 'static,
{
InboxCommand {
run: Some(Box::new(move |executor| {
executor
.spawn(async move {
let _guard = guard;
let _ = AssertUnwindSafe(future).catch_unwind().await;
})
.detach();
})),
cancel: None,
}
}
fn remote_command<F, T>(future: F, driver: BridgeDriver<T>) -> InboxCommand
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let state = Arc::new(Mutex::new(Some((future, driver))));
let run_state = Arc::clone(&state);
let cancel_state = Arc::clone(&state);
InboxCommand {
run: Some(Box::new(move |executor| {
let Some((future, driver)) = run_state
.lock()
.expect("remote command mutex poisoned")
.take()
else {
return;
};
if driver.is_cancel_requested() {
driver.complete(Completion::Cancelled);
return;
}
executor
.spawn(async move {
let guard = BridgeCompletionGuard::new(driver.clone());
let user = async move {
match AssertUnwindSafe(future).catch_unwind().await {
Ok(value) => Completion::Completed(value),
Err(payload) => Completion::Panicked(payload),
}
};
let cancelled = async move {
driver.clone().cancelled().await;
Completion::Cancelled
};
guard.finish(user.race(cancelled).await);
})
.detach();
})),
cancel: Some(Box::new(move || {
if let Some((_future, driver)) = cancel_state
.lock()
.expect("remote command mutex poisoned")
.take()
{
driver.complete(Completion::Cancelled);
}
})),
}
}
impl Default for LocalDomain {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
fn _local_domain_is_not_send_or_sync(_: &RefCell<LocalDomain>, _: NonZeroUsize) {}