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()
}