I have a 20,000 line Haskell program which I’ve been hacking on (does some IO and pure computations), and suddenly it hangs. I don’t really know which of my recent changes is causing this. Unfortunately I didn’t keep very good track of any of the recent changes. In the future I’ll commit more often and maybe fix things like this by reverting a commit.
I put it into the debugger but it’s not enlightening. Setting different breakpoints just shows that some of the breakpoints are never reached, but I’m not sure how to triangulate to specific lines as due to laziness things are not run in sequential order.
Normally this is as simple as accidentally introducing a self referential let, and you don’t find out until later on when your program finally hits that call path. I remember discussing a GHC proposal to add a warning about self-referential variables, but I can’t remember if it went anywhere. I’ll check.
Oh, I completely forgot my old technique for finding these self referential bombs. Enable profiling and find the binding with the most entries. Usually the thing at the top was the culprit.
As an experiment, I configured info table profiling, run a faulty hanging function in a separate thread, and after I delay threw an asynchronous exception to that thread. My hope was that the IPEBacktrace would contain an entry for the infinitely looping function. Alas, it seems that asynchronous exceptions don’t carry backtraces (?) I guess it makes sense, given that they come from “outside” of the killed thread.
Standard Chartered has a NoRecursion extension in their Mu Haskell compiler. I heard they wanted to add that to GHC proper, but I don’t think it went anywhere. Maybe it could help avoid these issues.