packages feed

rec-def-0.2: tests/spaceleak.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NumericUnderscores #-}

{-|
This test checks that resolved cells do no longer hold on to references to
dependent cells, to avoid space leaks, especially with the “constant global”
cells like 'RS.empty'.
-}

module Main where

import qualified Data.Set as S
import qualified Data.Recursive.Set as RS
import Data.Recursive.Internal
import Data.Propagator.Purify

import Data.IORef
import System.Mem.Weak
import System.Mem
import System.Exit
import System.Environment
import Control.Concurrent

main = do
    n <- length <$> getArgs

    putStrLn "Test 1: Normal GC (sanity check)"
    gc <- newIORef False
    let x = 1000_000_001 + n
    addFinalizer x $ do
        putStrLn "Finalizer running"
        writeIORef gc True
    let s = RS.insert x s
    print (RS.get s)

    putStrLn "Running GC"
    performMajorGC
    threadDelay 1_000_000

    readIORef gc >>= \case
        True -> putStrLn "GC seems to be working"
        False -> putStrLn "This really ought to work" >> exitFailure


    putStrLn "Test 2: Dependency on RS.empty"
    gc <- newIORef False
    let x = 1000_000_002 + n
    addFinalizer x $ do
        putStrLn "Finalizer running"
        writeIORef gc True
    let s = RS.insert x RS.empty
    print (RS.get s)

    putStrLn "Running GC"
    performMajorGC
    threadDelay 1_000_000

    readIORef gc >>= \case
        True -> putStrLn "Good!"
        False -> putStrLn "We got a leak" >> exitFailure

    putStrLn "Test 3: Dependency on constant set"
    gc <- newIORef False
    let x0 = 1000_000_003 + n
    let s' = RS.mk (S.singleton x0)
    let x1 = 1000_000_004 + n
    addFinalizer x1 $ do
        putStrLn "Finalizer running"
        writeIORef gc True
    let s = RS.insert x1 s'
    print (RS.get s)

    putStrLn "Running GC"
    performMajorGC
    threadDelay 1_000_000

    readIORef gc >>= \case
        True -> putStrLn "Good!"
        False -> putStrLn "We got a leak" >> exitFailure

    putStrLn "Test 4: Dependency on recursive set"
    gc <- newIORef False
    let x0 = 1000_000_005 + n
    let sr = RS.insert x0 sr
    let x1 = 1000_000_007 + n
    addFinalizer x1 $ do
        putStrLn "Finalizer running"
        writeIORef gc True
    let s = RS.insert x1 sr
    print (RS.get s)

    putStrLn "Running GC"
    performMajorGC
    threadDelay 1_000_000

    readIORef gc >>= \case
        True -> putStrLn "Good!"
        False -> putStrLn "We got a leak" >> exitFailure

    -- This is needed, else RS.empty itself is GC’ed
    putStrLn "Keeping a few things alive"
    print (RS.get s')
    print (RS.get sr)
    print (RS.get RS.empty :: S.Set Int)