簡體   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