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(¬ify_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