如此令人生气!> _<
我写了一个庞大而复杂的Haskell库。我写了一个小测试程序,到目前为止,我花了大约8个小时试图弄清楚为什么它一直在崩溃我。有时GHC抱怨“奇怪的封闭类型”。有时我只是得到一个段错误。 显然问题是内存损坏。
库本身是100%纯Haskell。但是,测试程序使用了与阵列相关的几个不安全的GHC原语。这显然是导致问题的原因。实际上,如果我注释掉writeArray#
行,程序就会停止崩溃。但是这对我的面条来说是完全煎炸的...尽我所知,我使用的所有数组边界都是完全有效的。程序将它们打印出来,并且它们都是正数且小于数组大小。
我编写了第二个程序,它与第一个程序完全相同,但没有涉及庞大,复杂的库。我尝试过并试过,但我根本无法让它崩溃。我所做的一切似乎都没有让它崩溃,但它与实际的数组几乎完全相同。
有没有人有进一步的故障排除提示?有什么方法可以追踪内存损坏的确切时刻吗? (而不仅仅是系统注意到腐败的那一刻。)
更新
问题是什么?
基本上,它创建了一个表示像素缓冲区的数组。它产生一个迭代每个像素的线程,并将相应的值写入其中。它产生了第二个读取数组的线程,并使用相当复杂的协议将像素写入网络套接字。 (因此,我正在尝试测试的大型库。)
如果我没有产生编写器线程,崩溃就会消失。如果我在编写器线程中注释掉writeArray'
调用,那么崩溃就会消失。在写入每个像素之前,写入器线程打印出像素坐标和数组索引。它打印出来的一切看起来都很完美。然而......它不会停止崩溃。
我几乎想知道GHC的数组原语是否不是线程安全的。 (如果它有任何区别,读者线程看起来像数组的副本已被不安全冻结,而编写器线程继续同时改变它。)
但是,我编写的程序完全相同,但没有通过网络发送流量。 此程序在每个细节上都能完美运行。只有真正复杂的程序才行不通。这有多烦人?!
答案 0 :(得分:6)
您已经记录了对不安全原语的使用。
您是否编写了一个程序来查看这些日志中是否存在违反不变量的行为?
答案 1 :(得分:6)
使用安全的已检查版本替换已知的不安全功能。检查日志以查找将导致的异常,并修复代码。
答案 2 :(得分:6)
测试程序和带有库的程序之间的区别可能是后者有更多的分配,因此更频繁地调用GC。
读者线程的数组副本已经存在 不安全冻结,而编写器线程继续同时进行 改变它。
可能GC无法跟踪冻结后仍然引用可变阵列。在这种情况下,GC可能会移动冻结的数组,但writeArray#使用旧指针执行写入。
答案 3 :(得分:5)
可以修复:
我改变了一些东西,代码不再崩溃了。这可能只是一个侥幸,或者我可能“真正”解决了这个问题。很难说。
<强>假设:强>
问题似乎是从并发线程读取同一数组的可变和不可变副本。 (即使简化的测试确实如此,也不会崩溃。)
我从一个不相关的不可变数组中读取了网络线程,并且崩溃停止了。我甚至添加了一个循环来将数据从一个可变数组复制到另一个新的可变数组,然后新鲜的数据被冻结并检查。这似乎完美无缺。
所以看起来这只是GHC处理对同一阵列的两个版本的并发访问的一个小故障。
(不管怎样,或者是侥幸。有几次我在程序中改变了一些东西而且它已经停止崩溃,然后再次开始崩溃......)
更新:这似乎是完全修复的。自从我做了这个改动之后,我没有再有过崩溃了。感谢所有帮助我的人。 : - )
答案 4 :(得分:4)
我唯一知道的就是使用Debug.Trace
import Debug.Trace
与
debug = flip trace
然后
main = (1 + 2) `debug` "adding or whatever and whatnot (also can have code here)"
答案 5 :(得分:0)
GHC以不同的方式处理可变和不可变的指针数组。写入可变数组需要将对象放在可变列表上,并在卡表中设置一个位。不可变数组都没有这些。因此,当您尝试写入已经冻结的可变数组时,您可以编写一个指向旧代的新生成指针。在较小的GC期间不会扫描旧一代,因此当次要GC运行时,将收集该值。然后你就会出现段错误,因为数组指向垃圾。
解决方案:不要冻结阵列。如果必须,请使用unsafePerformIO
进行读取。