First the Question - is the behavior below expected logically, or a bug to be reported for GHC?
The code below will leak memory (tested on ghc-8.8.4
) because ghc
seems to add join point
and jumps to it at the end of the loop, building up the stack.
{-# OPTIONS_GHC -fno-full-laziness #-}
module Main where
import Control.Concurrent.Async (async,waitCatch)
import Data.IORef
import GHC.Conc
main :: IO ()
main = do
val <- newIORef 0 :: IO (IORef Int)
let loop1 = do
cval <- readIORef val
threadDelay 1
writeIORef val (cval + 1)
case cval > 100000000 of
True -> error "done"
False -> loop1
loop1 -- Deliberately, add this to cause space leak, but this statement is never executed because of case branching above
loop1Async <- async loop1
res <- waitCatch loop1Async
return ()
Compiling with -O2 -rtsopts -threaded
and running with +RTS -s -hT -N
will show space leak because of growing stack.
Looking at core output, it seems the leak is due to join
(I guess it is a join point
) and a jump to it at the end of the loop which grows the stack (if I have read the core correctly). Removing the last statement in loop1
fixes the leak.
ghc core
output is here.
Update: Based on feedback in comments, it seems to be logical behavior, not a bug in ghc
. So, an answer explaining stack increase would be good to have. This helps us understand what is going on here. ghc core
output has been posted above.
I would certainly expect that line to cause the use of stack space. That extra call to
loop1
is hardly irrelevant.Change the test constant to
10
instead of a larger number, and replace thereturn ()
branch withprint cval
.Compare the output you get with and without the "never executed" statement. You might find it's somewhat more executed than you think.