async_dropper_simple/
default.rs1#![cfg(not(feature = "no-default-bound"))]
7
8use std::ops::{Deref, DerefMut};
9use std::time::Duration;
10
11use crate::AsyncDrop;
12
13#[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 pub fn inner(&self) -> &T {
43 &self.inner
44 }
45
46 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 self.dropped = true;
99
100 let timeout = self.timeout;
102
103 let mut this = std::mem::take(self);
106
107 self.dropped = true;
109
110 match timeout {
112 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 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 self.dropped = true;
142
143 let timeout = self.timeout;
145
146 let mut this = std::mem::take(self);
149
150 self.dropped = true;
152
153 match timeout {
154 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 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 struct Test {
187 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 #[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 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 #[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 #[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 #[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}