use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use super::AsyncDrop;
#[derive(Debug)]
pub struct AsyncDropGuard<T: Debug>(Option<T>);
impl<T: Debug> AsyncDropGuard<T> {
pub fn new(v: T) -> Self {
Self(Some(v))
}
pub fn into_box(self) -> AsyncDropGuard<Box<T>> {
self.map_unsafe(Box::new)
}
pub fn map_unsafe<U: Debug>(mut self, fun: impl FnOnce(T) -> U) -> AsyncDropGuard<U> {
AsyncDropGuard(self.0.take().map(fun))
}
}
impl<T: Debug + AsyncDrop> AsyncDropGuard<T> {
pub async fn async_drop(&mut self) -> Result<(), T::Error> {
self.0
.take()
.expect("Value already dropped")
.async_drop_impl()
.await
}
}
impl<T: Debug> Drop for AsyncDropGuard<T> {
fn drop(&mut self) {
match &self.0 {
Some(v) => {
if std::thread::panicking() {
log::error!("Forgot to call async_drop on {:?}", v);
eprintln!("Forgot to call async_drop on {:?}", v);
} else {
panic!("Forgot to call async_drop on {:?}", v);
}
}
None => {
()
}
}
}
}
impl<T: Debug> Deref for AsyncDropGuard<T> {
type Target = T;
fn deref(&self) -> &T {
self.0
.as_ref()
.expect("Value already dropped")
}
}
impl<T: Debug> DerefMut for AsyncDropGuard<T> {
fn deref_mut(&mut self) -> &mut T {
self.0
.as_mut()
.expect("Value already dropped")
}
}
#[cfg(test)]
mod tests {
use super::{AsyncDrop, AsyncDropGuard};
use async_trait::async_trait;
use std::fmt::{self, Debug};
use std::future::Future;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
struct MyStructWithDrop<F, FA, FS>
where
F: Future<Output = Result<(), &'static str>> + Send,
FA: Fn() -> F + Send,
FS: Fn() + Send,
{
on_async_drop: FA,
on_sync_drop: FS,
}
impl<F, FA, FS> Debug for MyStructWithDrop<F, FA, FS>
where
F: Future<Output = Result<(), &'static str>> + Send,
FA: Fn() -> F + Send,
FS: Fn() + Send,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MyStructWithDrop").finish()
}
}
#[async_trait]
impl<F, FA, FS> AsyncDrop for MyStructWithDrop<F, FA, FS>
where
F: Future<Output = Result<(), &'static str>> + Send,
FA: Fn() -> F + Send,
FS: Fn() + Send,
{
type Error = &'static str;
async fn async_drop_impl(&mut self) -> Result<(), &'static str> {
let r = (self.on_async_drop)();
r.await
}
}
impl<F, FA, FS> Drop for MyStructWithDrop<F, FA, FS>
where
F: Future<Output = Result<(), &'static str>> + Send,
FA: Fn() -> F + Send,
FS: Fn() + Send,
{
fn drop(&mut self) {
(self.on_sync_drop)();
}
}
struct MyStructWithoutDrop<F, FA>
where
F: Future<Output = Result<(), &'static str>> + Send,
FA: Fn() -> F + Send,
{
on_async_drop: FA,
}
impl<F, FA> Debug for MyStructWithoutDrop<F, FA>
where
F: Future<Output = Result<(), &'static str>> + Send,
FA: Fn() -> F + Send,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MyStructWithoutDrop").finish()
}
}
#[async_trait]
impl<F, FA> AsyncDrop for MyStructWithoutDrop<F, FA>
where
F: Future<Output = Result<(), &'static str>> + Send,
FA: Fn() -> F + Send,
{
type Error = &'static str;
async fn async_drop_impl(&mut self) -> Result<(), &'static str> {
let r = (self.on_async_drop)();
r.await
}
}
#[tokio::test]
#[should_panic(expected = "Forgot to call async_drop on MyStructWithoutDrop")]
async fn given_type_without_drop_when_forgetting_to_call_async_drop_then_panics() {
let _obj = AsyncDropGuard::new(MyStructWithoutDrop {
on_async_drop: || async { Ok(()) },
});
}
#[tokio::test]
#[should_panic(expected = "Forgot to call async_drop on MyStructWithDrop")]
async fn given_type_with_drop_when_forgetting_to_call_async_drop_then_panics() {
let _obj = AsyncDropGuard::new(MyStructWithDrop {
on_async_drop: || async { Ok(()) },
on_sync_drop: || (),
});
}
#[tokio::test]
async fn given_type_without_drop_when_calling_async_drop_then_calls_async_drop_impl() {
let called = AtomicI32::new(0);
let mut obj = AsyncDropGuard::new(MyStructWithoutDrop {
on_async_drop: || async {
let prev_value = called.swap(1, Ordering::SeqCst);
assert_eq!(0, prev_value);
Ok(())
},
});
obj.async_drop().await.unwrap();
assert_eq!(1, called.load(Ordering::SeqCst));
}
#[tokio::test]
async fn given_type_with_drop_when_calling_async_drop_then_calls_async_drop_impl_and_then_calls_drop(
) {
let called = AtomicI32::new(0);
let mut obj = AsyncDropGuard::new(MyStructWithDrop {
on_async_drop: || async {
let prev_value = called.swap(1, Ordering::SeqCst);
assert_eq!(0, prev_value);
Ok(())
},
on_sync_drop: || {
let prev_value = called.swap(2, Ordering::SeqCst);
assert_eq!(1, prev_value);
},
});
obj.async_drop().await.unwrap();
assert_eq!(2, called.load(Ordering::SeqCst));
}
#[tokio::test]
async fn given_type_without_drop_when_async_drop_fails_then_returns_error() {
let mut obj = AsyncDropGuard::new(MyStructWithoutDrop {
on_async_drop: || async { Err("My error") },
});
assert_eq!(Err("My error"), obj.async_drop().await);
}
#[tokio::test]
async fn given_type_with_drop_when_async_drop_fails_then_returns_error_and_still_calls_drop() {
let called = AtomicBool::new(false);
let mut obj = AsyncDropGuard::new(MyStructWithDrop {
on_async_drop: || async { Err("My error") },
on_sync_drop: || {
called.store(true, Ordering::SeqCst);
},
});
assert_eq!(Err("My error"), obj.async_drop().await);
assert_eq!(true, called.load(Ordering::SeqCst));
}
}