hyperactor/
panic_handler.rs1use std::backtrace::Backtrace;
13use std::cell::RefCell;
14use std::future::Future;
15use std::ops::Deref;
16use std::panic;
17
18tokio::task_local! {
19 static BACKTRACE: RefCell<Option<String>>;
22}
23
24pub fn set_panic_hook() {
28 panic::update_hook(move |prev, info| {
29 let backtrace = Backtrace::force_capture();
32 let loc = info.location().map_or_else(
33 || "unavailable".to_owned(),
34 |loc: &panic::Location<'_>| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()),
35 );
36 let _result = BACKTRACE.try_with(|entry| match entry.try_borrow_mut() {
37 Ok(mut entry_ref) => {
38 *entry_ref = Some(format!("panicked at {loc}\n{backtrace}"));
39 }
40 Err(borrow_mut_error) => {
41 eprintln!(
42 "failed to store backtrace to task_local: {:?}",
43 borrow_mut_error
44 );
45 }
46 });
47 tracing::error!("stacktrace"=%backtrace, "panic at {loc}");
48
49 prev(info);
51 });
52}
53
54pub async fn with_backtrace_tracking<F>(f: F) -> F::Output
57where
58 F: Future,
59{
60 BACKTRACE.scope(RefCell::new(None), f).await
61}
62
63pub fn take_panic_backtrace() -> Result<String, anyhow::Error> {
66 BACKTRACE.try_with(|entry| {
67 entry.try_borrow_mut().map(|mut entry_ref| {
68 let result = match entry_ref.deref() {
69 Some(bt) => Ok(bt.to_string()),
70 None => Err(anyhow::anyhow!("nothing is stored in task_local")),
71 };
72 if result.is_ok() {
74 *entry_ref = None;
75 }
76 result
77 })
78 })??
79}
80
81#[cfg(test)]
82mod tests {
83 use futures::FutureExt;
84
85 use super::*;
86
87 async fn execute_panic() {
88 let result = async {
89 panic!("boom!");
90 }
91 .catch_unwind()
92 .await;
93 assert!(result.is_err());
94 }
95
96 #[tokio::test]
97 async fn test_with_tracking() {
98 set_panic_hook();
99 with_backtrace_tracking(async {
100 execute_panic().await;
101 assert!(take_panic_backtrace().is_ok());
103 assert!(take_panic_backtrace().is_err());
106 })
107 .await;
108
109 assert!(take_panic_backtrace().is_err());
112 }
113
114 #[tokio::test]
115 async fn test_without_tracking() {
116 set_panic_hook();
117 async {
118 execute_panic().await;
119 assert!(take_panic_backtrace().is_err());
121 }
122 .await;
123 }
124
125 #[tokio::test]
126 async fn test_without_init() {
127 with_backtrace_tracking(async {
129 execute_panic().await;
130 assert!(take_panic_backtrace().is_err());
132 })
133 .await;
134 }
135
136 #[tokio::test]
137 async fn test_nested_tasks() {
138 async fn verify_inner_panic(backtrace_captured: bool) {
139 let result = async {
140 panic!("wow!");
141 }
142 .catch_unwind()
143 .await;
144 assert!(result.is_err());
145 if backtrace_captured {
146 assert!(
147 take_panic_backtrace()
148 .unwrap()
149 .contains("verify_inner_panic")
150 );
151 } else {
152 assert!(take_panic_backtrace().is_err());
153 }
154 }
155
156 set_panic_hook();
157 with_backtrace_tracking(async {
158 execute_panic().await;
159 let result = tokio::task::spawn(async {
161 verify_inner_panic(false).await;
162 })
163 .await;
164 assert!(result.is_ok());
165
166 let result =
168 tokio::task::spawn(with_backtrace_tracking(verify_inner_panic(true))).await;
169 assert!(result.is_ok());
170
171 assert!(
173 take_panic_backtrace()
174 .unwrap()
175 .contains("test_nested_tasks")
176 );
177 })
178 .await;
179 }
180}