Reputation: 4326
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.
Upvotes: 0
Views: 82
Reputation: 27023
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 the return ()
branch with print cval
.
Compare the output you get with and without the "never executed" statement. You might find it's somewhat more executed than you think.
Upvotes: 1