1 #![warn(rust_2018_idioms)]
2 #![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support panic recovery
3 
4 use std::sync::Arc;
5 use std::thread::sleep;
6 use tokio::time::Duration;
7 
8 use tokio::runtime::Builder;
9 
10 #[cfg(panic = "unwind")]
11 struct PanicOnDrop;
12 
13 #[cfg(panic = "unwind")]
14 impl Drop for PanicOnDrop {
drop(&mut self)15     fn drop(&mut self) {
16         panic!("Well what did you expect would happen...");
17     }
18 }
19 
20 /// Checks that a suspended task can be aborted without panicking as reported in
21 /// issue #3157: <https://github.com/tokio-rs/tokio/issues/3157>.
22 #[test]
test_abort_without_panic_3157()23 fn test_abort_without_panic_3157() {
24     let rt = Builder::new_multi_thread()
25         .enable_time()
26         .worker_threads(1)
27         .build()
28         .unwrap();
29 
30     rt.block_on(async move {
31         let handle = tokio::spawn(async move { tokio::time::sleep(Duration::new(100, 0)).await });
32 
33         // wait for task to sleep.
34         tokio::time::sleep(Duration::from_millis(10)).await;
35 
36         handle.abort();
37         let _ = handle.await;
38     });
39 }
40 
41 /// Checks that a suspended task can be aborted inside of a current_thread
42 /// executor without panicking as reported in issue #3662:
43 /// <https://github.com/tokio-rs/tokio/issues/3662>.
44 #[test]
test_abort_without_panic_3662()45 fn test_abort_without_panic_3662() {
46     use std::sync::atomic::{AtomicBool, Ordering};
47     use std::sync::Arc;
48 
49     struct DropCheck(Arc<AtomicBool>);
50 
51     impl Drop for DropCheck {
52         fn drop(&mut self) {
53             self.0.store(true, Ordering::SeqCst);
54         }
55     }
56 
57     let rt = Builder::new_current_thread().build().unwrap();
58 
59     rt.block_on(async move {
60         let drop_flag = Arc::new(AtomicBool::new(false));
61         let drop_check = DropCheck(drop_flag.clone());
62 
63         let j = tokio::spawn(async move {
64             // NB: just grab the drop check here so that it becomes part of the
65             // task.
66             let _drop_check = drop_check;
67             futures::future::pending::<()>().await;
68         });
69 
70         let drop_flag2 = drop_flag.clone();
71 
72         let task = std::thread::spawn(move || {
73             // This runs in a separate thread so it doesn't have immediate
74             // thread-local access to the executor. It does however transition
75             // the underlying task to be completed, which will cause it to be
76             // dropped (but not in this thread).
77             assert!(!drop_flag2.load(Ordering::SeqCst));
78             j.abort();
79             j
80         })
81         .join()
82         .unwrap();
83 
84         let result = task.await;
85         assert!(drop_flag.load(Ordering::SeqCst));
86         assert!(result.unwrap_err().is_cancelled());
87 
88         // Note: We do the following to trigger a deferred task cleanup.
89         //
90         // The relevant piece of code you want to look at is in:
91         // `Inner::block_on` of `scheduler/current_thread.rs`.
92         //
93         // We cause the cleanup to happen by having a poll return Pending once
94         // so that the scheduler can go into the "auxiliary tasks" mode, at
95         // which point the task is removed from the scheduler.
96         let i = tokio::spawn(async move {
97             tokio::task::yield_now().await;
98         });
99 
100         i.await.unwrap();
101     });
102 }
103 
104 /// Checks that a suspended LocalSet task can be aborted from a remote thread
105 /// without panicking and without running the tasks destructor on the wrong thread.
106 /// <https://github.com/tokio-rs/tokio/issues/3929>
107 #[test]
remote_abort_local_set_3929()108 fn remote_abort_local_set_3929() {
109     struct DropCheck {
110         created_on: std::thread::ThreadId,
111         not_send: std::marker::PhantomData<*const ()>,
112     }
113 
114     impl DropCheck {
115         fn new() -> Self {
116             Self {
117                 created_on: std::thread::current().id(),
118                 not_send: std::marker::PhantomData,
119             }
120         }
121     }
122     impl Drop for DropCheck {
123         fn drop(&mut self) {
124             if std::thread::current().id() != self.created_on {
125                 panic!("non-Send value dropped in another thread!");
126             }
127         }
128     }
129 
130     let rt = Builder::new_current_thread().build().unwrap();
131     let local = tokio::task::LocalSet::new();
132 
133     let check = DropCheck::new();
134     let jh = local.spawn_local(async move {
135         futures::future::pending::<()>().await;
136         drop(check);
137     });
138 
139     let jh2 = std::thread::spawn(move || {
140         sleep(Duration::from_millis(10));
141         jh.abort();
142     });
143 
144     rt.block_on(local);
145     jh2.join().unwrap();
146 }
147 
148 /// Checks that a suspended task can be aborted even if the `JoinHandle` is immediately dropped.
149 /// issue #3964: <https://github.com/tokio-rs/tokio/issues/3964>.
150 #[test]
test_abort_wakes_task_3964()151 fn test_abort_wakes_task_3964() {
152     let rt = Builder::new_current_thread().enable_time().build().unwrap();
153 
154     rt.block_on(async move {
155         let notify_dropped = Arc::new(());
156         let weak_notify_dropped = Arc::downgrade(&notify_dropped);
157 
158         let handle = tokio::spawn(async move {
159             // Make sure the Arc is moved into the task
160             let _notify_dropped = notify_dropped;
161             tokio::time::sleep(Duration::new(100, 0)).await
162         });
163 
164         // wait for task to sleep.
165         tokio::time::sleep(Duration::from_millis(10)).await;
166 
167         handle.abort();
168         drop(handle);
169 
170         // wait for task to abort.
171         tokio::time::sleep(Duration::from_millis(10)).await;
172 
173         // Check that the Arc has been dropped.
174         assert!(weak_notify_dropped.upgrade().is_none());
175     });
176 }
177 
178 /// Checks that aborting a task whose destructor panics does not allow the
179 /// panic to escape the task.
180 #[test]
181 #[cfg(panic = "unwind")]
test_abort_task_that_panics_on_drop_contained()182 fn test_abort_task_that_panics_on_drop_contained() {
183     let rt = Builder::new_current_thread().enable_time().build().unwrap();
184 
185     rt.block_on(async move {
186         let handle = tokio::spawn(async move {
187             // Make sure the Arc is moved into the task
188             let _panic_dropped = PanicOnDrop;
189             tokio::time::sleep(Duration::new(100, 0)).await
190         });
191 
192         // wait for task to sleep.
193         tokio::time::sleep(Duration::from_millis(10)).await;
194 
195         handle.abort();
196         drop(handle);
197 
198         // wait for task to abort.
199         tokio::time::sleep(Duration::from_millis(10)).await;
200     });
201 }
202 
203 /// Checks that aborting a task whose destructor panics has the expected result.
204 #[test]
205 #[cfg(panic = "unwind")]
test_abort_task_that_panics_on_drop_returned()206 fn test_abort_task_that_panics_on_drop_returned() {
207     let rt = Builder::new_current_thread().enable_time().build().unwrap();
208 
209     rt.block_on(async move {
210         let handle = tokio::spawn(async move {
211             // Make sure the Arc is moved into the task
212             let _panic_dropped = PanicOnDrop;
213             tokio::time::sleep(Duration::new(100, 0)).await
214         });
215 
216         // wait for task to sleep.
217         tokio::time::sleep(Duration::from_millis(10)).await;
218 
219         handle.abort();
220         assert!(handle.await.unwrap_err().is_panic());
221     });
222 }
223 
224 // It's not clear where these tests belong. This was the place suggested by @Darksonn:
225 // https://github.com/tokio-rs/tokio/pull/6753#issuecomment-2271434176
226 /// Checks that a `JoinError` with a panic payload prints the expected text.
227 #[test]
228 #[cfg(panic = "unwind")]
test_join_error_display()229 fn test_join_error_display() {
230     let rt = Builder::new_current_thread().build().unwrap();
231 
232     rt.block_on(async move {
233         // `String` payload
234         let join_err = tokio::spawn(async move {
235             let value = 1234;
236             panic!("Format-args payload: {value}")
237         })
238         .await
239         .unwrap_err();
240 
241         // We can't assert the full output because the task ID can change.
242         let join_err_str = join_err.to_string();
243 
244         assert!(
245             join_err_str.starts_with("task ")
246                 && join_err_str.ends_with(" panicked with message \"Format-args payload: 1234\""),
247             "Unexpected join_err_str {join_err_str:?}"
248         );
249 
250         // `&'static str` payload
251         let join_err = tokio::spawn(async move { panic!("Const payload") })
252             .await
253             .unwrap_err();
254 
255         let join_err_str = join_err.to_string();
256 
257         assert!(
258             join_err_str.starts_with("task ")
259                 && join_err_str.ends_with(" panicked with message \"Const payload\""),
260             "Unexpected join_err_str {join_err_str:?}"
261         );
262 
263         // Non-string payload
264         let join_err = tokio::spawn(async move { std::panic::panic_any(1234i32) })
265             .await
266             .unwrap_err();
267 
268         let join_err_str = join_err.to_string();
269 
270         assert!(
271             join_err_str.starts_with("task ") && join_err_str.ends_with(" panicked"),
272             "Unexpected join_err_str {join_err_str:?}"
273         );
274     });
275 }
276 
277 /// Checks that a `JoinError` with a panic payload prints the expected text from `Debug`.
278 #[test]
279 #[cfg(panic = "unwind")]
test_join_error_debug()280 fn test_join_error_debug() {
281     let rt = Builder::new_current_thread().build().unwrap();
282 
283     rt.block_on(async move {
284         // `String` payload
285         let join_err = tokio::spawn(async move {
286             let value = 1234;
287             panic!("Format-args payload: {value}")
288         })
289         .await
290         .unwrap_err();
291 
292         // We can't assert the full output because the task ID can change.
293         let join_err_str = format!("{join_err:?}");
294 
295         assert!(
296             join_err_str.starts_with("JoinError::Panic(Id(")
297                 && join_err_str.ends_with("), \"Format-args payload: 1234\", ...)"),
298             "Unexpected join_err_str {join_err_str:?}"
299         );
300 
301         // `&'static str` payload
302         let join_err = tokio::spawn(async move { panic!("Const payload") })
303             .await
304             .unwrap_err();
305 
306         let join_err_str = format!("{join_err:?}");
307 
308         assert!(
309             join_err_str.starts_with("JoinError::Panic(Id(")
310                 && join_err_str.ends_with("), \"Const payload\", ...)"),
311             "Unexpected join_err_str {join_err_str:?}"
312         );
313 
314         // Non-string payload
315         let join_err = tokio::spawn(async move { std::panic::panic_any(1234i32) })
316             .await
317             .unwrap_err();
318 
319         let join_err_str = format!("{join_err:?}");
320 
321         assert!(
322             join_err_str.starts_with("JoinError::Panic(Id(") && join_err_str.ends_with("), ...)"),
323             "Unexpected join_err_str {join_err_str:?}"
324         );
325     });
326 }
327