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
loop1is hardly irrelevant.Change the test constant to
10instead 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.