use crate::fiber::{Fiber, FiberInner};
use crate::utils::{BoxFuture, block_on, lock};
use crate::{CordisError, ErrorCode, Result};
use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
pub struct AsyncDisposer {
callback: Option<Box<dyn FnOnce() -> BoxFuture<Result<()>> + Send + 'static>>,
}
impl AsyncDisposer {
pub fn from_sync<F>(callback: F) -> Self
where
F: FnOnce() -> Result<()> + Send + 'static,
{
Self {
callback: Some(Box::new(move || Box::pin(async move { callback() }))),
}
}
pub fn infallible<F>(callback: F) -> Self
where
F: FnOnce() + Send + 'static,
{
Self::from_sync(move || {
callback();
Ok(())
})
}
pub fn from_async<F, Fut>(callback: F) -> Self
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
Self {
callback: Some(Box::new(move || Box::pin(callback()))),
}
}
pub async fn run(mut self) -> Result<()> {
match self.callback.take() {
Some(callback) => callback().await,
None => Ok(()),
}
}
}
impl Debug for AsyncDisposer {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("AsyncDisposer")
.field("pending", &self.callback.is_some())
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EffectMeta {
pub label: String,
pub children: Vec<EffectMeta>,
}
impl EffectMeta {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
children: Vec::new(),
}
}
}
pub(crate) struct EffectCell {
pub(crate) id: u64,
owner: Weak<FiberInner>,
disposed: AtomicBool,
disposer: Mutex<Option<AsyncDisposer>>,
children: Mutex<Vec<Arc<EffectCell>>>,
meta: Mutex<EffectMeta>,
}
impl EffectCell {
pub(crate) fn new(
id: u64,
owner: Weak<FiberInner>,
label: impl Into<String>,
disposer: AsyncDisposer,
) -> Arc<Self> {
Arc::new(Self {
id,
owner,
disposed: AtomicBool::new(false),
disposer: Mutex::new(Some(disposer)),
children: Mutex::new(Vec::new()),
meta: Mutex::new(EffectMeta::new(label)),
})
}
async fn dispose(self: &Arc<Self>) -> Result<()> {
if self.disposed.swap(true, Ordering::AcqRel) {
return Ok(());
}
if let Some(owner) = self.owner.upgrade() {
owner.remove_effect(self.id);
}
let children = {
let mut children = lock(&self.children);
std::mem::take(&mut *children)
};
let mut first_error = None;
for child in children.into_iter().rev() {
if let Err(error) = Box::pin(child.dispose()).await {
if first_error.is_none() {
first_error = Some(error);
}
}
}
let disposer = lock(&self.disposer).take();
if let Some(disposer) = disposer {
if let Err(error) = disposer.run().await {
if first_error.is_none() {
first_error = Some(error);
}
}
}
match first_error {
Some(error) => Err(error),
None => Ok(()),
}
}
pub(crate) fn cancel(&self) {
if self.disposed.swap(true, Ordering::AcqRel) {
return;
}
lock(&self.disposer).take();
lock(&self.children).clear();
if let Some(owner) = self.owner.upgrade() {
owner.remove_effect(self.id);
}
}
fn adopt(self: &Arc<Self>, child: Arc<EffectCell>) -> Result<()> {
let mut children = lock(&self.children);
if self.disposed.load(Ordering::Acquire) {
return Err(CordisError::new(ErrorCode::InactiveEffect));
}
if child.disposed.load(Ordering::Acquire) {
return Ok(());
}
if let Some(owner) = child.owner.upgrade() {
owner.remove_effect(child.id);
}
lock(&self.meta).children.push(lock(&child.meta).clone());
children.push(child);
Ok(())
}
}
#[derive(Clone)]
pub struct EffectHandle {
pub(crate) cell: Arc<EffectCell>,
}
impl EffectHandle {
pub(crate) fn new(cell: Arc<EffectCell>) -> Self {
Self { cell }
}
pub fn dispose(&self) -> Result<()> {
block_on(self.dispose_async())
}
pub async fn dispose_async(&self) -> Result<()> {
self.cell.dispose().await
}
pub fn cancel(&self) {
self.cell.cancel();
}
pub fn adopt(&self, child: EffectHandle) -> Result<()> {
self.cell.adopt(child.cell)
}
pub fn meta(&self) -> EffectMeta {
lock(&self.cell.meta).clone()
}
pub fn is_disposed(&self) -> bool {
self.cell.disposed.load(Ordering::Acquire)
}
pub fn owner(&self) -> Option<Fiber> {
self.cell.owner.upgrade().map(Fiber::from_inner)
}
}
impl Debug for EffectHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("EffectHandle")
.field("id", &self.cell.id)
.field("meta", &self.meta())
.field("disposed", &self.is_disposed())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn dispose_runs_adopted_children_in_reverse_order() {
let runs = Arc::new(Mutex::new(Vec::new()));
let parent = EffectCell::new(
1,
Weak::new(),
"parent",
AsyncDisposer::from_sync(|| Ok(())),
);
for id in [2_u64, 3] {
let runs = runs.clone();
let child = EffectCell::new(
id,
Weak::new(),
"child",
AsyncDisposer::from_sync(move || {
runs.lock().unwrap().push(id);
Ok(())
}),
);
parent.adopt(child).unwrap();
}
block_on(parent.dispose()).unwrap();
assert_eq!(*runs.lock().unwrap(), vec![3, 2]);
}
#[test]
fn adopt_racing_parent_disposal_is_rejected() {
let parent = EffectCell::new(
1,
Weak::new(),
"parent",
AsyncDisposer::from_sync(|| Ok(())),
);
let child_ran = Arc::new(AtomicBool::new(false));
let child_ran_in_disposer = child_ran.clone();
let child = EffectCell::new(
2,
Weak::new(),
"child",
AsyncDisposer::from_sync(move || {
child_ran_in_disposer.store(true, Ordering::SeqCst);
Ok(())
}),
);
let gate = lock(&parent.children);
let adopting_parent = parent.clone();
let adopt_handle = thread::spawn(move || adopting_parent.adopt(child));
thread::sleep(Duration::from_millis(50));
let disposing_parent = parent.clone();
let dispose_handle = thread::spawn(move || block_on(disposing_parent.dispose()));
thread::sleep(Duration::from_millis(50));
drop(gate);
assert!(adopt_handle.join().unwrap().is_err());
dispose_handle.join().unwrap().unwrap();
assert!(!child_ran.load(Ordering::SeqCst));
}
}