1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
use syn::DeriveInput;
#[proc_macro_derive(AsyncDrop)]
pub fn derive_async_drop(items: proc_macro::TokenStream) -> proc_macro::TokenStream {
match syn::parse2::<DeriveInput>(items.into()) {
Ok(derive_input) => proc_macro2::TokenStream::from_iter(
[gen_preamble(&derive_input), gen_impl(&derive_input).into()].into_iter(),
)
.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn make_shared_default_name(ident: &proc_macro2::Ident) -> proc_macro2::Ident {
quote::format_ident!("_shared_default_{}", ident)
}
/// Default implementation of deriving async drop that does nothing
/// you're expected to use either the 'tokio' feature or 'async-std'
fn gen_preamble(DeriveInput { ident, .. }: &DeriveInput) -> proc_macro2::TokenStream {
let shared_default_name = make_shared_default_name(ident);
quote::quote!(
#[derive(Debug)]
pub enum AsyncDropError {
UnexpectedError(Box<dyn std::error::Error>),
Timeout,
}
/// What to do when a drop fails
#[derive(Debug, PartialEq, Eq)]
pub enum DropFailAction {
// Ignore the failed drop
Continue,
// Elevate the drop failure to a full on panic
Panic,
}
#[async_trait]
trait AsyncDrop: Default + PartialEq + Eq {
/// Operative drop that does async operations, returning
async fn async_drop(&mut self) -> Result<(), AsyncDropError> {
Ok(())
}
/// Timeout for drop operation, meant to be overriden if needed
fn drop_timeout(&self) -> Duration {
Duration::from_secs(3)
}
/// What to do what a drop fails
fn drop_fail_action(&self) -> DropFailAction {
DropFailAction::Continue
}
}
/// Utility function unique to #ident which retrieves a shared mutable single default instance of it
/// that single default instance is compared to other instances and indicates whether async drop
/// should be called
#[allow(non_snake_case)]
fn #shared_default_name() -> &'static std::sync::Mutex<#ident> {
#[allow(non_upper_case_globals)]
static #shared_default_name: std::sync::OnceLock<std::sync::Mutex<#ident>> = std::sync::OnceLock::new();
#shared_default_name.get_or_init(|| std::sync::Mutex::new(#ident::default()))
}
)
.into()
}
#[cfg(all(not(feature = "async-std"), not(feature = "tokio")))]
fn gen_impl(_: &DeriveInput) -> proc_macro::TokenStream {
panic!("either 'async-std' or 'tokio' features must be enabled for the async-dropper crate")
}
/// Tokio implementation of AsyncDrop
#[cfg(feature = "tokio")]
fn gen_impl(DeriveInput { ident, .. }: &DeriveInput) -> proc_macro2::TokenStream {
let shared_default_name = make_shared_default_name(ident);
quote::quote!(
#[automatically_derived]
#[async_trait]
impl Drop for #ident {
fn drop(&mut self) {
// We consider a self that is completley equivalent to it's default version to be dropped
let thing = #shared_default_name();
if *thing.lock().unwrap() == *self {
return;
}
// Ensure that the default_version is manually dropped
let mut original = Self::default();
std::mem::swap(&mut original, self);
// Spawn a task to do the drop
let task = ::tokio::spawn(async move {
let drop_fail_action = original.drop_fail_action();
match ::tokio::time::timeout(
original.drop_timeout(),
original.async_drop(),
).await {
Err(e) => {
match drop_fail_action {
DropFailAction::Continue => {}
DropFailAction::Panic => {
panic!("async drop failed: {e}");
}
}
},
Ok(_) => {},
}
});
// Perform a synchronous wait
::futures::executor::block_on(task).unwrap();
}
}
)
.into()
}
/// async-std implementation of AsyncDrop
#[cfg(feature = "async-std")]
fn gen_impl(DeriveInput { ident, ..}: &DeriveInput) -> proc_macro2::TokenStream {
let shared_default_name = make_shared_default_name(ident);
quote::quote!(
#[automatically_derived]
#[async_trait]
impl Drop for #ident {
fn drop(&mut self) {
// We consider a self that is completley equivalent to it's default version to be dropped
let thing = #shared_default_name();
if *thing.lock().unwrap() == *self {
return;
}
// Swap out the existing with a completely default
let mut original = Self::default();
std::mem::swap(&mut original, self);
// Spawn a task to do the drop
let task = ::async_std::task::spawn(async move {
let drop_fail_action = original.drop_fail_action();
match ::async_std::future::timeout(
original.drop_timeout(),
original.async_drop(),
).await {
Err(e) => {
match drop_fail_action {
DropFailAction::Continue => {}
DropFailAction::Panic => {
panic!("async drop failed: {e}");
}
}
},
Ok(_) => {},
}
});
// Perform synchronous wait
::futures::executor::block_on(task);
}
}
)
.into()
}