Skip to content

Thread GC frees roots before thread actually finishes

In the following program, an IORef is garbage collected after a NonTermination exception, but is subsequently accessed:

import Control.Exception as E
import Data.IORef
import System.Mem.Weak

main :: IO ()
main = do
    ref <- newIORef 'x'
    weak <- mkWeakIORef ref $ putStrLn "IORef finalized"
    let check = deRefWeak weak >>= \m -> case m of
            Nothing -> putStrLn "IORef was GCed"
            Just ref' -> do
                x <- readIORef ref'
                putStrLn $ "IORef still alive, and contains " ++ show x
    let loop = loop
    check
    loop `catch` \ex -> do
        putStrLn $ "caught exception: " ++ show (ex :: SomeException)
        check
    readIORef ref >>= print

Output:

IORef still alive, and contains 'x'
IORef finalized
caught exception: <<loop>>
IORef was GCed
'x'

The same happens with other thread deadlocks, such as:

  • newEmptyMVar >>= takeMVar
  • atomically retry

It does not happen when a StackOverflow or UserInterrupt exception is caught.

This also affects ForeignPtr; see the attached "database" example. This is what really triggered #7170 (closed). I marked this "Runtime crash" because it can lead to a ForeignPtr being accessed after the garbage collector finalized it.

Trac metadata
Trac field Value
Version 7.6.3
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Runtime System
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information