繁体   English   中英

如何在 Rust 子线程中捕获堆栈溢出?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM