async_dropper_simple/
default.rs

1//! Implementation of simple AsyncDropper that does not require `Default`
2//!
3//! This implementation might be preferrable for people who cannot reasonably
4//! implement `Default` for their struct, but have easily accessible `Eq`,`PartialEq`,
5//! `Hash`, and/or `Clone` instances.
6#![cfg(not(feature = "no-default-bound"))]
7
8use std::ops::{Deref, DerefMut};
9use std::time::Duration;
10
11use crate::AsyncDrop;
12
13/// Wrapper struct that enables `async_drop()` behavior.
14///
15/// This version requires a `Default` implementation.
16#[derive(Default)]
17#[allow(dead_code)]
18pub struct AsyncDropper<T: AsyncDrop + Default + Send + 'static> {
19    dropped: bool,
20    timeout: Option<Duration>,
21    inner: T,
22}
23
24impl<T: AsyncDrop + Default + Send + 'static> AsyncDropper<T> {
25    pub fn new(inner: T) -> Self {
26        Self {
27            dropped: false,
28            timeout: None,
29            inner,
30        }
31    }
32
33    pub fn with_timeout(timeout: Duration, inner: T) -> Self {
34        Self {
35            dropped: false,
36            timeout: Some(timeout),
37            inner,
38        }
39    }
40
41    /// Get a reference to the inner data
42    pub fn inner(&self) -> &T {
43        &self.inner
44    }
45
46    /// Get a mutable refrence to inner data
47    pub fn inner_mut(&mut self) -> &mut T {
48        &mut self.inner
49    }
50}
51
52impl<T> Deref for AsyncDropper<T>
53where
54    T: AsyncDrop + Send + Default,
55{
56    type Target = T;
57
58    fn deref(&self) -> &T {
59        self.inner()
60    }
61}
62
63impl<T> DerefMut for AsyncDropper<T>
64where
65    T: AsyncDrop + Send + Default,
66{
67    fn deref_mut(&mut self) -> &mut T {
68        self.inner_mut()
69    }
70}
71
72#[cfg(all(not(feature = "tokio"), not(feature = "async-std")))]
73impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
74    fn drop(&mut self) {
75        compile_error!(
76            "either 'async-std' or 'tokio' features must be enabled for the async-dropper crate"
77        )
78    }
79}
80
81#[cfg(all(feature = "async-std", feature = "tokio"))]
82impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
83    fn drop(&mut self) {
84        compile_error!(
85            "'async-std' and 'tokio' features cannot both be specified for the async-dropper crate"
86        )
87    }
88}
89
90#[cfg(all(feature = "tokio", not(feature = "async-std")))]
91#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
92impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
93    fn drop(&mut self) {
94        if !self.dropped {
95            use async_scoped::TokioScope;
96
97            // Set the original instance to be dropped
98            self.dropped = true;
99
100            // Save the timeout on the original instance
101            let timeout = self.timeout;
102
103            // Swap out the current instance with default
104            // (i.e. `this` is now original instance, and `self` is a default instance)
105            let mut this = std::mem::take(self);
106
107            // Set the default instance to note that it's dropped
108            self.dropped = true;
109
110            // Create task
111            match timeout {
112                // If a timeout was specified, use it when performing async_drop
113                Some(d) => {
114                    TokioScope::scope_and_block(|s| {
115                        s.spawn(tokio::time::timeout(d, async move {
116                            this.inner.async_drop().await;
117                        }))
118                    });
119                }
120                // If no timeout was specified, perform async_drop() indefinitely
121                None => {
122                    TokioScope::scope_and_block(|s| {
123                        s.spawn(async move {
124                            this.inner.async_drop().await;
125                        })
126                    });
127                }
128            }
129        }
130    }
131}
132
133#[cfg(all(feature = "async-std", not(feature = "tokio")))]
134#[cfg_attr(docsrs, doc(cfg(feature = "async-std")))]
135impl<T: AsyncDrop + Default + Send + 'static> Drop for AsyncDropper<T> {
136    fn drop(&mut self) {
137        if !self.dropped {
138            use async_scoped::AsyncStdScope;
139
140            // Set the original instance to be dropped
141            self.dropped = true;
142
143            // Save the timeout on the original instance
144            let timeout = self.timeout;
145
146            // Swap out the current instance with default
147            // (i.e. `this` is now original instance, and `self` is a default instance)
148            let mut this = std::mem::take(self);
149
150            // Set the default instance to note that it's dropped
151            self.dropped = true;
152
153            match timeout {
154                // If a timeout was specified, use it when performing async_drop
155                Some(d) => {
156                    AsyncStdScope::scope_and_block(|s| {
157                        s.spawn(async_std::future::timeout(d, async move {
158                            this.inner.async_drop().await;
159                        }))
160                    });
161                }
162                // If no timeout was specified, perform async_drop() indefinitely
163                None => {
164                    AsyncStdScope::scope_and_block(|s| {
165                        s.spawn(async move {
166                            this.inner.async_drop().await;
167                        })
168                    });
169                }
170            }
171        }
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use std::sync::atomic::{AtomicU32, Ordering};
178    use std::sync::Arc;
179    use std::time::Duration;
180
181    use async_trait::async_trait;
182
183    use crate::{AsyncDrop, AsyncDropper};
184
185    /// Testing struct
186    struct Test {
187        // This counter is used as an indicator of how far async_drop() gets
188        // - 0 means async_drop() never ran
189        // - 1 means async_drop() started but did not complete
190        // - 2 means async_drop() completed
191        counter: Arc<AtomicU32>,
192    }
193
194    impl Default for Test {
195        fn default() -> Self {
196            Self {
197                counter: Arc::new(AtomicU32::new(0)),
198            }
199        }
200    }
201
202    #[async_trait]
203    impl AsyncDrop for Test {
204        async fn async_drop(&mut self) {
205            self.counter.store(1, Ordering::SeqCst);
206            tokio::time::sleep(Duration::from_secs(1)).await;
207            self.counter.store(2, Ordering::SeqCst);
208        }
209    }
210
211    /// Ensure that non-`Default`-bounded dropper works with tokio
212    #[cfg(feature = "tokio")]
213    #[tokio::test(flavor = "multi_thread")]
214    async fn tokio_works() {
215        let start = std::time::Instant::now();
216        let counter = Arc::new(AtomicU32::new(0));
217
218        // Create and perform drop
219        let wrapped_t = AsyncDropper::new(Test {
220            counter: counter.clone(),
221        });
222        drop(wrapped_t);
223
224        assert!(
225            start.elapsed() > Duration::from_millis(500),
226            "two seconds have passed since drop"
227        );
228        assert_eq!(
229            counter.load(Ordering::SeqCst),
230            2,
231            "async_drop() ran to completion"
232        );
233    }
234
235    // TODO: this test is broken *because* of the timeout bug
236    // see: https://github.com/t3hmrman/async-dropper/pull/17
237    /// Ensure that non-`Default`-bounded dropper works with tokio with a timeout
238    #[cfg(feature = "tokio")]
239    #[tokio::test(flavor = "multi_thread")]
240    async fn tokio_works_with_timeout() {
241        let start = std::time::Instant::now();
242        let counter = Arc::new(AtomicU32::new(0));
243        let wrapped_t = AsyncDropper::with_timeout(
244            Duration::from_millis(500),
245            Test {
246                counter: counter.clone(),
247            },
248        );
249        drop(wrapped_t);
250        assert!(
251            start.elapsed() > Duration::from_millis(500),
252            "two seconds have passed since drop"
253        );
254        assert_eq!(
255            counter.load(Ordering::SeqCst),
256            1,
257            "async_drop() did not run to completion (should have timed out)"
258        );
259    }
260
261    /// Ensure that non-`Default`-bounded dropper works with async-std
262    #[cfg(feature = "async-std")]
263    #[async_std::test]
264    async fn async_std_works() {
265        let start = std::time::Instant::now();
266        let counter = Arc::new(AtomicU32::new(0));
267
268        let wrapped_t = AsyncDropper::new(Test {
269            counter: counter.clone(),
270        });
271        drop(wrapped_t);
272
273        assert!(
274            start.elapsed() > Duration::from_millis(500),
275            "two seconds have passed since drop"
276        );
277        assert_eq!(
278            counter.load(Ordering::SeqCst),
279            2,
280            "async_drop() ran to completion"
281        );
282    }
283
284    // TODO: this test is broken *because* of the timeout bug
285    // see: https://github.com/t3hmrman/async-dropper/pull/17
286    /// Ensure that non-`Default`-bounded dropper works with async-std with a timeout
287    #[cfg(feature = "async-std")]
288    #[async_std::test]
289    async fn async_std_works_with_timeout() {
290        let start = std::time::Instant::now();
291        let counter = Arc::new(AtomicU32::new(0));
292        let wrapped_t = AsyncDropper::with_timeout(
293            Duration::from_millis(500),
294            Test {
295                counter: counter.clone(),
296            },
297        );
298        drop(wrapped_t);
299        assert!(
300            start.elapsed() > Duration::from_millis(500),
301            "two seconds have passed since drop"
302        );
303        assert_eq!(
304            counter.load(Ordering::SeqCst),
305            1,
306            "async_drop() did not run to completion (should have timed out)"
307        );
308    }
309}