[英]How can I catch a stack overflow in a Rust child thread?
我有一个可以嵌套很深的递归算法(它是反编译器的一部分)。 我知道通常你不会只增加堆栈大小,因为堆栈溢出通常表示无限递归,但在这种情况下,算法有时可能只需要更大的堆栈,所以我在带有堆栈的子线程中运行算法可以使用 CLI 标志增加的大小:
fn main() -> Result<(), Box<std::io::Error>> {
// Process arguments
let args: Cli = Cli::from_args();
let child = thread::Builder::new()
.name("run".into())
.stack_size(args.stack_size * 1024 * 1024)
.spawn(move || -> Result<(), Box<std::io::Error>> { run(args)?; Ok(()) })
.unwrap();
child.join().unwrap()?;
Ok(())
}
fn run(args: Cli) -> Result<(), Box<std::io::Error>> {
...
}
这很好用,将--stack-size=20
传递给应用程序,运行线程将获得 20MB 的堆栈,只要足够,它就会愉快地运行。
除了,也就是说,第一次运行它时,只有 8MB 的默认堆栈,你会得到这个错误:
thread 'run' has overflowed its stack
fatal runtime error: stack overflow
我想捕获此错误,而是打印一条消息,提醒用户他们可以通过--stack-size=X
为反编译器提供更大的堆栈。
如何捕获 Rust 子线程的堆栈溢出?
“中止恐慌”:
堆栈溢出属于我所看到的称为“中止恐慌”的类别。 使用catch_unwind()
无法捕获和恢复它们。 OP 建议使用子进程将故障与应用程序的 rest 隔离开来,这似乎是一种合理的解决方法。
这是 Reddit 上的一个很好的长线程,讨论“堆栈探针”(以及其他内容)。 这可能是一种防止线程溢出的方法。 如果您想了解更多信息,请参阅 Module compiler_builtins::probestack的文档。
此参考资料的一些摘录:
堆栈探测的目的是提供 static 保证,如果线程有保护页,则保证堆栈溢出会命中该保护页。
最后值得注意的是,在撰写本文时,LLVM 仅支持 x86 和 x86_64 上的堆栈探测。
需要注意的是。 我看到有人提到堆栈探测功能并不完全安全。 这对于大多数应用程序来说可能无关紧要,但对于通过网站自动化提供的编译器之类的东西来说可能无关紧要。
避免递归
递归算法更容易编写代码,但在许多情况下效率低于循环迭代树等数据结构。 循环方法更难编码,但速度更快,使用更少 memory。如果树遍历是要解决的问题,网上有很多示例可供参考。 一些避免递归的算法使用它们自己以编程方式声明的堆栈,例如向量、列表或其他类似堆栈的结构。 Morris Traversal等其他算法不需要维护堆栈数据结构。 重新处理有问题的递归逻辑是减少堆栈溢出机会的一种方法。
实现自己的堆栈
有关如何在 Python 中将递归函数转换为迭代函数的与语言无关的示例,这里有一个通用方法。 在我转换为 Rust 的递归多键快速排序代码遇到堆栈问题后,我写了这个答案。我用它来排序后缀 arrays,这需要数万次深度递归调用。 在我按照描述的方法进行转换后,应用程序能够毫无问题地处理非常大的文本块。
对于非致命的可恢复恐慌:
如果 panic 不是致命的/不可恢复的,则可以使用std::panic
crate 捕获它们并获取诊断信息。
有关控制恐慌的更多信息,请参阅 Rust 版指南的使用 std::panic 控制恐慌部分。 这是std::panic
上文档的链接。
use std::env;
use std::thread;
use std::panic::catch_unwind;
use std::panic;
fn main() -> Result<(), Box<std::io::Error>> {
let args = env::args().collect::<Vec<String>>();
panic::set_hook(Box::new(move |panic_info| {
match panic_info.location() {
Some(loc) => {
println!("Panic in file {} line {}.", loc.file(), loc.line());
},
None => { println!("No panic info provided..."); },
}
}));
let child = thread::spawn(move || {
catch_unwind(|| {
run(args);
});
});
child.join();
Ok(())
}
fn run(args: Vec<String>) -> Result<(), Box<std::io::Error>> {
println!("Args from command line: {:?}", args);
panic!("uh oh!");
Ok(())
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.