6

太气人了!>_<

我编写了一个庞大而复杂的 Haskell 库。我写了一个小测试程序,到目前为止,我已经花了大约 8 个小时试图弄清楚为什么它一直在我身上崩溃。有时 GHC 会抱怨“奇怪的封闭类型”。有时我只是得到一个段错误。显然问题是内存损坏。

库本身是 100% 纯 Haskell。但是,测试程序使用了几个与数组相关的不安全 GHC 原语。这显然是导致问题的原因。事实上,如果我注释掉该writeArray#行,程序就会停止崩溃。但这完全是在炒我的面条……据我所知,我使用的所有数组边界都是完全有效的。程序将它们全部打印出来,它们都是正数并且小于数组大小。

我编写了第二个程序,它与第一个程序做同样的事情,但不涉及庞大而复杂的库。我已经尝试了又尝试了,但我根本无法让它崩溃。我所做的一切似乎都不会让它崩溃,但它对实际数组的作用几乎完全相同。

有没有人有任何进一步的故障排除提示?有什么方法可以追踪内存损坏的确切时刻吗?(而不仅仅是系统注意到损坏的那一刻。)


更新:

问题有什么作用?

好吧,本质上,它创建了一个表示像素缓冲区的数组。它生成一个线程,该线程遍历每个像素并将相应的值写入其中。它会产生第二个线程来读取数组,并使用相当复杂的协议将像素写入网络套接字。(因此我要测试的大型图书馆。)

如果我不生成作者线程,崩溃就会消失。如果我在作者线程中注释掉writeArray'调用,崩溃就会消失。在写入每个像素之前,写入器线程会打印出像素坐标数组索引。它打印出来的所有东西看起来都很完美。然而......它不会停止崩溃。

我几乎想知道 GHC 的数组原语是否不是线程安全的。(如果有任何不同,读取器线程看起来像的数组副本已被不安全地冻结,而写入器线程继续同时对其进行变异。)

但是,我编写了一个程序,它做同样的事情,但不通过网络发送流量。这个程序在每一个细节上都能完美运行。只有真正复杂的程序是行不通的。这有多烦人?!

这有效:http ://hpaste.org/70987

这不是:http ://hpaste.org/70988

4

6 回答 6

6

您已经在记录您对不安全原语的使用。

您是否编写了一个程序来查看这些日志是否违反不变量?

于 2012-07-06T12:24:46.620 回答
6

将已知不安全的函数替换为安全、经过检查的版本。检查您的日志以查找将导致的异常,并修复您的代码。

于 2012-07-06T13:41:45.223 回答
6

可能测试程序和带库的程序的区别在于,后一种情况下分配更多,所以GC调用更频繁。

读取器线程看起来像的数组副本已被不安全地冻结,而写入器线程继续并发地对其进行变异。

可能 GC 无法跟踪冻结后仍然引用可变数组。在这种情况下,GC 可能会移动冻结的数组,但 writeArray# 使用旧指针执行写入。

于 2012-07-06T13:42:26.187 回答
5

可能会修复:

我改变了一些东西,代码不再崩溃。这可能只是侥幸,或者我可能已经“真正”解决了这个问题。很难说。

假设:

问题似乎是从并发线程中读取同一数组的可变和不可变副本。(即使简化的测试确实做到了这一点并且不会崩溃。)

我让网络线程从一个不相关的不可变数组中读取,并且崩溃停止了。我什至添加了一个循环来将数据从一个可变数组复制到另一个新的可变数组,然后新的数组被冻结并检查。这似乎完美无缺。

因此,这似乎只是 GHC 处理对同一数组的两个版本的并发访问的一个小故障。

(要么是那个,要么是侥幸。有几次我在程序中改变了一些东西,它停止了崩溃,然后又开始崩溃了......)

更新:这似乎已完全修复。自从我做了这个改动后,我再也没有发生过崩溃。感谢所有在这里帮助我的人。:-)

于 2012-07-06T14:08:59.877 回答
4

我知道的唯一有用的是使用 Debug.Trace

import Debug.Trace

debug = flip trace

进而

main = (1 + 2) `debug` "adding or whatever and whatnot (also can have code here)"
于 2012-07-06T10:54:52.240 回答
0

出于 GC 的目的,GHC 以不同的方式处理可变和不可变的指针数组。写入可变数组需要将对象放在可变列表中并在卡表中设置一个位。不可变数组没有这些。所以当你尝试写入一个已经被冻结的可变数组时,你可以写一个指向老年代的新生代指针。在次要 GC 期间不会扫描老年代,因此当次要 GC 运行时,会收集该值。然后你segfault因为数组指向垃圾。

解决方案:不要冻结数组。如果必须,请unsafePerformIO用于读取。

于 2017-01-26T17:00:03.317 回答