5

我遇到了一种情况,根据小型转储,某些文件导致递归下降解析器中的堆栈溢出。不幸的是,我无法获得一个这样做的文件示例以重现问题(客户有保密问题),这让我目前在诊断真正的问题时有点手足无措。

显然解析器需要一些关注,但现在我的首要任务是保持程序运行。作为权宜之计,我能做些什么来防止这导致整个程序崩溃?

我的第一选择是找到某种方法来预测我的堆栈空间不足,这样我就可以在溢出发生之前优雅地中止解析器。无法解析文件是可接受的选项。第二种选择是让它发生,捕获错误并记录它,然后继续处理其余数据。

解析是在Parallel.ForEach()循环中发生的。如果有帮助,我愿意将其换成其他方法。

编辑: 如果我能得到当前线程堆栈的大小和堆栈指针的位置,那将是真正的杀手。这可能吗?

编辑 2: 我终于设法从某人身上提取了一个示例文件,并在调试器中捕获了错误。事实证明,它根本不是属于我们的代码——异常发生在HtmlAgilityPack的某个地方。所以看起来我将不得不尝试找到一个完全不同的策略。

4

2 回答 2

3

Stack 在桌面 CLR 上默认有 1 MB 的限制,但您可以增加它

您可以使用延续传递样式来使用堆而不是堆栈。

在 C# 5.0 中,编译器提供了异步机制来自动执行此过程。我还没有在最新版本中尝试过这个。正如 Alex 所提到的,C# 中不支持尾调用优化,这可能是采用 F# 来解析问题的一个足够大的理由。这是一些关于使用 F# 进行词法分析和解析的材料。 YMMV,如本文所示。

您还需要图形周期检测以使您的程序在存在错误输入的情况下保持稳定。

作为收集更多信息的一种方式,您可以通过一个累加器整数来跟踪调用堆栈的深度。这不会直接转化为所述调用堆栈消耗的内存,但它会给你一个大致的概念。例如,当该数字大于某个用户可配置或预定义的阈值时,您可以抛出并捕获您自己的异常。

public void Recursive(int acc)
{
    if (acc > myLimit)
       throw new MyOverflowException(acc); 

    Recursive(acc+1);
}

然后在呼叫站点:

try { Recursive(0); } catch (MyOverflowException) { /* handle it*/ }

根据要求,我会将您链接到 Eric Lippert 关于这个主题的精彩博客。

于 2012-08-02T19:13:03.000 回答
0

由于 SOE 导致的线程崩溃将导致整个过程中断,您对此无能为力。

作为恢复措施,您可以将解析器作为单独的进程启动,并设置 IPC 机制与子进程通信。这样,子进程可以在不影响主进程的情况下自由死亡。

于 2012-08-02T19:55:17.893 回答