How can I stop infinite evaluation in GHCi?

后端 未结 1 2142
一个人的身影
一个人的身影 2021-02-19 02:37

When I run something like:

Prelude> cycle \"ab\"

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

1条回答
  •  [愿得一人]
    2021-02-19 03:20

    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            
    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
    
    

    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.

    0 讨论(0)
提交回复
热议问题