#![cfg(feature = "no-default-bound")]
use std::ops::{Deref, DerefMut};
use std::time::Duration;
use crate::AsyncDrop;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[allow(dead_code)]
pub struct AsyncDropper<T: AsyncDrop + Send + 'static> {
dropped: bool,
timeout: Option<Duration>,
inner: Option<T>,
}
impl<T: AsyncDrop + Send + 'static> AsyncDropper<T> {
pub fn new(inner: T) -> Self {
Self {
dropped: false,
timeout: None,
inner: Some(inner),
}
}
pub fn with_timeout(timeout: Duration, inner: T) -> Self {
Self {
dropped: false,
timeout: Some(timeout),
inner: Some(inner),
}
}
pub fn inner(&self) -> &T {
self.inner
.as_ref()
.expect("failed to retreive inner content")
}
pub fn inner_mut(&mut self) -> &mut T {
self.inner
.as_mut()
.expect("failed to retrieve inner content")
}
}
impl<T: AsyncDrop + Send> Default for AsyncDropper<T> {
fn default() -> Self {
Self {
dropped: false,
timeout: None,
inner: None,
}
}
}
impl<T> Deref for AsyncDropper<T>
where
T: AsyncDrop + Send + Default,
{
type Target = T;
fn deref(&self) -> &T {
self.inner()
}
}
impl<T> DerefMut for AsyncDropper<T>
where
T: AsyncDrop + Send + Default,
{
fn deref_mut(&mut self) -> &mut T {
self.inner_mut()
}
}
#[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]
impl<T: AsyncDrop + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
compile_error!(
"either 'async-std' or 'tokio' features must be enabled for the async-dropper crate"
)
}
}
#[cfg(all(feature = "async-std", feature = "tokio"))]
impl<T: AsyncDrop + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
compile_error!(
"'async-std' and 'tokio' features cannot both be specified for the async-dropper crate"
)
}
}
#[cfg(all(feature = "tokio", not(feature = "async-std")))]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
impl<T: AsyncDrop + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
if !self.dropped {
use async_scoped::TokioScope;
self.dropped = true;
let timeout = self.timeout;
let mut this = std::mem::take(self);
self.dropped = true;
match timeout {
Some(d) => {
TokioScope::scope_and_block(|s| {
s.spawn(tokio::time::timeout(d, async move {
this.inner
.take()
.expect(
"unexpectedly failed to take ownership AsyncDropper inner data",
)
.async_drop()
.await;
}))
});
}
None => {
TokioScope::scope_and_block(|s| {
s.spawn(async move {
this.inner
.take()
.expect(
"unexpectedly failed to take ownership AsyncDropper inner data",
)
.async_drop()
.await;
})
});
}
}
}
}
}
#[cfg(all(feature = "async-std", not(feature = "tokio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "async-std")))]
impl<T: AsyncDrop + Send + 'static> Drop for AsyncDropper<T> {
fn drop(&mut self) {
if !self.dropped {
use async_scoped::AsyncStdScope;
self.dropped = true;
let timeout = self.timeout;
let mut this = std::mem::take(self);
self.dropped = true;
match timeout {
Some(d) => {
AsyncStdScope::scope_and_block(|s| {
s.spawn(async_std::future::timeout(d, async move {
this.inner
.take()
.expect(
"unexpectedly failed to take ownership AsyncDropper inner data",
)
.async_drop()
.await;
}))
});
}
None => {
AsyncStdScope::scope_and_block(|s| {
s.spawn(async move {
this.inner
.take()
.expect(
"unexpectedly failed to take ownership AsyncDropper inner data",
)
.async_drop()
.await;
})
});
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use crate::{AsyncDrop, AsyncDropper};
struct Test {
counter: Arc<AtomicU32>,
}
#[async_trait]
impl AsyncDrop for Test {
async fn async_drop(&mut self) {
self.counter.store(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_secs(1)).await;
self.counter.store(2, Ordering::SeqCst);
}
}
#[cfg(feature = "tokio")]
#[tokio::test(flavor = "multi_thread")]
async fn tokio_works() {
let start = std::time::Instant::now();
let counter = Arc::new(AtomicU32::new(0));
let wrapped_t = AsyncDropper::new(Test {
counter: counter.clone(),
});
drop(wrapped_t);
assert!(
start.elapsed() > Duration::from_millis(500),
"two seconds have passed since drop"
);
assert_eq!(
counter.load(Ordering::SeqCst),
2,
"async_drop() ran to completion"
);
}
#[cfg(feature = "tokio")]
#[tokio::test(flavor = "multi_thread")]
async fn tokio_works_with_timeout() {
let start = std::time::Instant::now();
let counter = Arc::new(AtomicU32::new(0));
let wrapped_t = AsyncDropper::with_timeout(
Duration::from_millis(500),
Test {
counter: counter.clone(),
},
);
drop(wrapped_t);
assert!(
start.elapsed() > Duration::from_millis(500),
"two seconds have passed since drop"
);
assert_eq!(
counter.load(Ordering::SeqCst),
1,
"async_drop() did not run to completion (should have timed out)"
);
}
#[cfg(feature = "async-std")]
#[async_std::test]
async fn async_std_works() {
let start = std::time::Instant::now();
let counter = Arc::new(AtomicU32::new(0));
let wrapped_t = AsyncDropper::new(Test {
counter: counter.clone(),
});
drop(wrapped_t);
assert!(
start.elapsed() > Duration::from_millis(500),
"two seconds have passed since drop"
);
assert_eq!(
counter.load(Ordering::SeqCst),
2,
"async_drop() ran to completion"
);
}
#[cfg(feature = "async-std")]
#[async_std::test]
async fn async_std_works_with_timeout() {
let start = std::time::Instant::now();
let counter = Arc::new(AtomicU32::new(0));
let wrapped_t = AsyncDropper::with_timeout(
Duration::from_millis(500),
Test {
counter: counter.clone(),
},
);
drop(wrapped_t);
assert!(
start.elapsed() > Duration::from_millis(500),
"two seconds have passed since drop"
);
assert_eq!(
counter.load(Ordering::SeqCst),
1,
"async_drop() did not run to completion (should have timed out)"
);
}
}