5

If my program reaches an infinite loop, I would like it to actually get stuck: run forever, run out of memory or crash with a stack overflow.

I don't want it to quit immediately with the <<loop>> error message. How do I disable the run-time infinite loop detection?

9
  • 5
    I guess one could catch the exception and do some non-detected loop, e.g. let f () = f () in f () which is not detected by the black-holing mechanism. I don't know if there's an option to disable black-holing completely. Usually, it's a useful feature. Commented Jul 17, 2018 at 10:18
  • 2
    Note that you can only catch it inside IO, e.g. in the main of your program, or at the GHCi prompt. Can you elaborate about why do you want to disable black-holing? Commented Jul 17, 2018 at 10:30
  • 2
    @leftaroundabout I'm not so sure. e.g. especially in security-related applications, terminating too early when you should be looping could be an information leak. Commented Jul 17, 2018 at 15:39
  • 7
    I'm really interested as to why anyone would ever want this ... could you provide a use case for this behavior? The only one I can think of is how people tend to put for (;;); at the end of a C program so that the terminal sticks around long enough for them to read the output when they run it in Windows. If this is what you're doing, there are better ways to go about it, in Haskell and in C. Commented Jul 17, 2018 at 16:38
  • 1
    What happens if you compile with -threaded and run with +RTS -N2 -RTS? The threaded runtime generally handles blackholes differently (as greyholes) because some other thread could potentially be in the process of evaluating the thunk. Commented Jul 21, 2018 at 19:00

2 Answers 2

3
+50

Here's a horrible hack that might work. You can create a fake info table for the non-termination exception that's created when a blackhole loop is detected and make it loop.

Suppose you have a Haskell program:

-- Loop.hs
foo :: Int
foo = foo
main = print $ foo

If you compile this with:

$ ghc -O2 Loop.hs

and run it, it'll generate a Loop: <<loop>> error.

But, if you create an assembly file:

# halt_and_catch_fire.s
# version for Linux x86_64 only
.globl base_ControlziExceptionziBase_nonTermination_closure
.section .data
.align 8
base_ControlziExceptionziBase_nonTermination_closure:
    .quad loop
.section .text
.align 8
    .quad 0
    .quad 14            # FUN_STATIC
loop:   jmp loop

and compile it with your Haskell program (with appropriate linker flags to ignore the duplicate definition):

$ ghc -O2 Loop.hs halt_and_catch_fire.s -optl -zmuldefs

and run it, it'll lock up.

Note that the above assembly works on x86_64 Linux. On any other architecture (including 32-bit Linux), it would need to be modified, as the closure layout is very architecture-dependent.

Sign up to request clarification or add additional context in comments.

Comments

2

This isn’t really an answer but wouldn’t fit in a comment. In this situation one should ask what infinite loop detection is and how it works. I’m going to explain it by converting from Haskell to a Haskell-look-alike language with strict (non lazy) evaluation (think eg JavaScript with Haskell syntax) and then explaining it.

Haskell:

let f () = f () in f ()

Convert to strict:

let f () = make_thunk (\() -> eval_thunk f ()) in eval_thunk (make_thunk (\() -> f ()))

Now let’s do an optimisation:

let f () = eval_thunk f_unit
    f_unit = make_thunk (\() -> f ())
in
eval_thunk f_unit

Now let’s write down a fake definition for thunks:

data ThunkInner a = Done a | Fresh (() -> a) | Working
data Thunk a = Thunk { mutable inner :: ThunkInner a }
make_thunk f = Thunk (Fresh f)
eval_thunk t = case t.inner of
  | Done a -> a
  | Fresh f -> (t.inner <- Working; let result = f () in t.inner <- Done result; result)
  | Working -> error "<<loop>>"

So we see that we get an infinite loop when we try to evaluate something as part of working out its value. You can trace the above program by hand to see how this error could arise.

But what if that optimisation hadn’t been made? Well then (assuming you don’t optimise to strictness) you would get a stack overflow and could report this as a <<loop>> error. If it were optimised to be truly strict then maybe you would get a stack overflow error or maybe you would get an infinite loop.

One might ask how to avoid this error and the answer could be:

  1. Don’t write infinite loops
  2. Maybe compile with no optimisations

But you say that sometimes infinite loops are useful because eg event loops or long lived servers. The reply is that in Haskell infinite loops are not useful because to be useful you need IO. Consider a function be_useful :: IO () and now one can write:

f = be_useful >>= \() -> f

And now there are two steps in doing f:

  1. Evaluating the thunk
  2. Doing the IO actions in that value
  3. Doing the next IO action (computing the thunk again) and so on.

This need not <<loop>>.

1 Comment

Thank you for your comments!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.