16
votes

When I run something like:

Prelude> cycle "ab"

I can see an infinite printing of "ab". To stop it I just use Ctrl+c. And it works.

When I run:

Prelude Data.List> nub $ cycle "ab"

I am not able to stop it.

Question:

  • Why it so?
  • How can I stop this operation?

Update:

 Ubuntu: version 18.10  
 GHCi:   version 8.2.2
1
I can reproduce this with GHCi 8.4.4 as described. ghci does not respond to repeated sigint or sigterm. Similar question herethat other guy
You can open another terminal window, find the process ID of the ghci command that is running and kill it.Bob Dalgleish
Also have a look at this: CTRL+Z should work on linux stackoverflow.com/questions/30877019/…Lorenzo
@thatotherguy The similar question cannot get reproduced in GHC 8.0.1Zeta
Can you please add your GHCi version and operating system to the question?Zeta

1 Answers

12
votes

Great question! However, since How to abort execution in GHCI? already focuses on your second part, let's not repeat that here. Instead, let's focus on the first.

Why it so?

GHC optimizes loops aggressively. It optimizes them even further if there is no allocation that it's even a known bug:

19.2.1. Bugs in GHC

  • GHC’s runtime system implements cooperative multitasking, with context switching potentially occurring only when a program allocates. This means that programs that do not allocate may never context switch. This is especially true of programs using STM, which may deadlock after observing inconsistent state. See Trac #367 for further discussion. [emphasis mine]

    If you are hit by this, you may want to compile the affected module with -fno-omit-yields (see -f*: platform-independent flags). This flag ensures that yield points are inserted at every function entrypoint (at the expense of a bit of performance).

If we check -fomit-yields, we find:

-fomit-yields

Default: yield points enabled

Tells GHC to omit heap checks when no allocation is being performed. While this improves binary sizes by about 5%, it also means that threads run in tight non-allocating loops will not get preempted in a timely fashion. If it is important to always be able to interrupt such threads, you should turn this optimization off. Consider also recompiling all libraries with this optimization turned off, if you need to guarantee interruptibility. [emphasis mine]

nub $ cycle "ab" is a tight, non-allocating loop, although last $ repeat 1 is an even more obvious non-allocating example.

The "yield points enabled" is misleading: -fomit-yields is enabled by default. As the standard library is compiled with -fomit-yields, all functions in the standard library that lead to tight, non-allocating loops may show that behaviour in GHCi, as you never recompile them.

We can verify that with the following program:

-- Test.hs
myLast :: [a] -> Maybe a
myLast [x]    = Just x
myLast (_:xs) = myLast xs
myLast _      = Nothing

main = print $ myLast $ repeat 1

We can use C-c to quit it if we run it in GHCi without compiling beforehand:

$ ghci Test.hs
[1 of 1] Compiling Main             ( Test.hs, interpreted )
Ok, one module loaded.
*Main> :main            <pressing C-c after a while>
Interrupted.

If we compile it and then rerun it in GHCi, it will hang:

$ ghc Test.hs
[1 of 1] Compiling Main             ( Test.hs, Test.o )
Linking Test.exe ...

$ ghci Test.hs
Ok, one module loaded.
*Main> :main
<hangs indefinitely>

Note that you need -dynamic if you don't use Windows, as otherwise GHCi will recompile the source file. However, if we use -fno-omit-yield, we suddenly can quit again (in Windows).

We can verify that again with another small snippet:

Prelude> last xs = case xs of [x] -> x ; (_:ys) -> last ys
Prelude> last $ repeat 1
^CInterrupted

As ghci doesn't use any optimizations, it also doesn't use -fomit-yield (and therefore has -fno-omit-yield enabled). Our new variant of last doesn't yield the same behaviour as Prelude.last as it isn't compiled with fomit-yield.

Now that we know why this happens, we know that we will experience that behaviour throughout the standard library, as the standard library is compiled with -fomit-yield.