2023-04-07 21:51:35 +00:00
{- # LANGUAGE MagicHash # -}
{- # LANGUAGE LambdaCase # -}
{- # LANGUAGE UnboxedTuples # -}
{- # LANGUAGE ScopedTypeVariables # -}
import System.Mem
import System.Mem.Weak
import Data.IORef
import GHC.IORef
import GHC.STRef
import GHC.IO
import GHC.Weak
import GHC.Prim
import Control.Monad.Primitive
import Data.IntMap.Strict ( IntMap )
import qualified Data.IntMap.Strict as IntMap
2023-04-07 22:09:31 +00:00
import Data.Set ( Set )
import qualified Data.Set as Set
2023-04-07 21:51:35 +00:00
import System.IO.Unsafe
import Control.Monad
import Control.Concurrent
2023-04-07 22:09:31 +00:00
import Data.Foldable
2023-04-07 21:51:35 +00:00
main :: IO ()
main = do
testDualWeak
2023-04-08 01:48:29 +00:00
testWeakChain
testGraphChain
testGraphX
testGraphO
2023-04-07 21:51:35 +00:00
-- Demonstrate that a Weak's key keeps its value alive, even if the Weak is dead
testWeakChain :: IO ()
testWeakChain = do
a <- newIORef ()
b <- newIORef ()
_ <- mkWeakWithIORefKey a b
2023-04-08 01:48:29 +00:00
w <- mkWeakWithIORefKey b " b is still alive "
2023-04-07 21:51:35 +00:00
performGCUntilFinalizersQuiesce
2023-04-08 01:48:29 +00:00
Just " b is still alive " <- deRefWeak w
2023-04-07 21:51:35 +00:00
touch a
testGraphChain :: IO ()
testGraphChain = do
do
( nai , neo ) <- buildGraphChain
performGCUntilFinalizersQuiesce
2023-04-08 01:48:29 +00:00
[ " a " , " b " , " c " , " d " , " e " ] <- toList <$> findForwardVals nai
[ " a " , " b " , " c " , " d " , " e " ] <- toList <$> findBackwardVals neo
2023-04-07 21:51:35 +00:00
touch ( nai , neo )
do
( nai , _ ) <- buildGraphChain
performGCUntilFinalizersQuiesce
2023-04-08 01:48:29 +00:00
[] <- toList <$> findForwardVals nai
2023-04-07 21:51:35 +00:00
touch nai
do
( _ , neo ) <- buildGraphChain
performGCUntilFinalizersQuiesce
2023-04-08 01:48:29 +00:00
[] <- toList <$> findBackwardVals neo
2023-04-07 21:51:35 +00:00
touch neo
buildGraphChain :: IO ( NodeInput String , NodeOutput String )
buildGraphChain = do
( nai , nao ) <- newNode " a "
( nbi , nbo ) <- newNode " b "
( nci , nco ) <- newNode " c "
( ndi , ndo ) <- newNode " d "
( nei , neo ) <- newNode " e "
link nao nbi
link nbo nci
link nco ndi
link ndo nei
pure ( nai , neo )
2023-04-08 01:48:29 +00:00
buildGraphX :: IO ( NodeInput String , NodeInput String , NodeOutput String , NodeOutput String )
buildGraphX = do
2023-04-07 21:51:35 +00:00
( nai , nao ) <- newNode " a "
( nbi , nbo ) <- newNode " b "
( nci , nco ) <- newNode " c "
( ndi , ndo ) <- newNode " d "
( nei , neo ) <- newNode " e "
link nao nci
link nbo nci
link nco ndi
link nco nei
2023-04-08 01:48:29 +00:00
pure ( nai , nbi , ndo , neo )
testGraphX :: IO ()
testGraphX = do
do
( nai , nbi , ndo , neo ) <- buildGraphX
performGCUntilFinalizersQuiesce
[ " a " , " c " , " d " , " e " ] <- toList <$> findForwardVals nai
[ " b " , " c " , " d " , " e " ] <- toList <$> findForwardVals nbi
[ " a " , " b " , " c " , " d " ] <- toList <$> findBackwardVals ndo
[ " a " , " b " , " c " , " e " ] <- toList <$> findBackwardVals neo
touch ( nai , nbi , ndo , neo )
do
( nai , nbi , ndo , neo ) <- buildGraphX
performGCUntilFinalizersQuiesce
[ " a " , " c " , " d " ] <- toList <$> findForwardVals nai
[ " b " , " c " , " d " ] <- toList <$> findForwardVals nbi
touch ( nai , nbi , ndo )
do
( nai , nbi , ndo , neo ) <- buildGraphX
performGCUntilFinalizersQuiesce
[] <- toList <$> findForwardVals nai
[] <- toList <$> findForwardVals nbi
touch ( nai , nbi )
2023-04-07 21:51:35 +00:00
2023-04-07 22:09:31 +00:00
testGraphO :: IO ()
testGraphO = do
2023-04-08 01:48:29 +00:00
do
( nai , nao ) <- buildGraphO
performGCUntilFinalizersQuiesce
[ " a " , " b " , " c " ] <- toList <$> findBackward nao
[ " a " , " b " , " c " ] <- toList <$> findForward nai
touch ( nai , nao )
do
( _ , nao ) <- buildGraphO
performGCUntilFinalizersQuiesce
[] <- toList <$> findBackward nao
touch nao
do
( nai , _ ) <- buildGraphO
performGCUntilFinalizersQuiesce
[] <- toList <$> findForward nai
touch nai
buildGraphO :: IO ( NodeInput String , NodeOutput String )
buildGraphO = do
2023-04-07 22:09:31 +00:00
( nai , nao ) <- newNode " a "
( nbi , nbo ) <- newNode " b "
( nci , nco ) <- newNode " c "
link nao nbi
link nbo nci
link nco nai
2023-04-08 01:48:29 +00:00
pure ( nai , nao )
2023-04-07 21:51:35 +00:00
data NodeInput a = NodeInput
{ _nodeInput_key :: ! Int
2023-04-08 01:48:29 +00:00
, _nodeInput_contents :: ! ( Weak ( IORef ( Maybe a ) ) )
, _nodeInput_forwardLinks :: ! ( IORef ( IntMap ( NodeInput a ) ) )
, _nodeInput_backLinks :: ! ( Weak ( IORef ( IntMap ( NodeOutput a ) ) ) )
2023-04-07 21:51:35 +00:00
}
data NodeOutput a = NodeOutput
{ _nodeOutput_key :: ! Int
2023-04-08 01:48:29 +00:00
, _nodeOutput_contents :: ! ( Weak ( IORef ( Maybe a ) ) )
, _nodeOutput_forwardLinks :: ! ( Weak ( IORef ( IntMap ( NodeInput a ) ) ) )
, _nodeOutput_backLinks :: ! ( IORef ( IntMap ( NodeOutput a ) ) )
2023-04-07 21:51:35 +00:00
}
{- # NOINLINE globalNodeIdRef # -}
globalNodeIdRef :: IORef Int
globalNodeIdRef = unsafePerformIO $ newIORef 1
newNodeId :: IO Int
newNodeId = atomicModifyIORef' globalNodeIdRef $ \ n -> ( succ n , n )
newNode :: a -> IO ( NodeInput a , NodeOutput a )
newNode v = do
nodeId <- newNodeId
backLinks <- newIORef mempty
forwardLinks <- newIORef mempty
2023-04-08 01:48:29 +00:00
contentsRef <- newIORef $ Just v
w <- mkWeakWithIORefKey backLinks contentsRef
wBack <- mkWeakWithIORefKey backLinks backLinks
wForward <- mkWeakWithIORefKeyWithFinalizer forwardLinks forwardLinks $ do
writeIORef finalizerDidRunRef True
deRefWeak w >>= \ case
Nothing -> pure ()
Just contentsRef' -> do
writeIORef contentsRef' Nothing
deRefWeak wBack >>= \ case
Nothing -> pure ()
Just backLinks' -> do
writeIORef backLinks' mempty
--TODO: Clear out all the nodes forward of us
2023-04-07 21:51:35 +00:00
pure
( NodeInput
{ _nodeInput_key = nodeId
, _nodeInput_contents = w
2023-04-08 01:48:29 +00:00
, _nodeInput_backLinks = wBack
, _nodeInput_forwardLinks = forwardLinks
2023-04-07 21:51:35 +00:00
}
, NodeOutput
{ _nodeOutput_key = nodeId
, _nodeOutput_contents = w
2023-04-08 01:48:29 +00:00
, _nodeOutput_backLinks = backLinks
, _nodeOutput_forwardLinks = wForward
2023-04-07 21:51:35 +00:00
}
)
2023-04-08 01:48:29 +00:00
getNodeContents :: Weak ( IORef ( Maybe a ) ) -> IO ( Maybe a )
getNodeContents w = do
deRefWeak w >>= \ case
Nothing -> pure Nothing
Just r -> readIORef r
2023-04-07 21:51:35 +00:00
link :: NodeOutput a -> NodeInput a -> IO ()
link aOut bIn = do
2023-04-08 01:48:29 +00:00
deRefWeak ( _nodeInput_backLinks bIn ) >>= \ case
Nothing -> pure ()
Just backLinks -> deRefWeak ( _nodeOutput_forwardLinks aOut ) >>= \ case
Nothing -> pure ()
Just forwardLinks -> do
atomicModifyIORef' forwardLinks $ \ m -> ( IntMap . insert ( _nodeInput_key bIn ) bIn m , () )
atomicModifyIORef' backLinks $ \ m -> ( IntMap . insert ( _nodeOutput_key aOut ) aOut m , () )
2023-04-07 21:51:35 +00:00
2023-04-07 22:09:31 +00:00
findForwardVals :: Ord a => NodeInput a -> IO ( Set a )
findForwardVals i = Set . fromList . IntMap . elems <$> findForward i
2023-04-07 21:51:35 +00:00
findForward :: forall a . NodeInput a -> IO ( IntMap a )
findForward i = go mempty ( IntMap . singleton ( _nodeInput_key i ) i )
where
go :: IntMap a -> IntMap ( NodeInput a ) -> IO ( IntMap a )
go found toSearch = case IntMap . minViewWithKey toSearch of
Nothing -> pure found
Just ( ( k , thisIn ) , restToSearch ) -> do
2023-04-08 01:48:29 +00:00
getNodeContents ( _nodeInput_contents thisIn ) >>= \ case
2023-04-07 21:51:35 +00:00
Nothing -> go found restToSearch
Just this -> do
2023-04-08 01:48:29 +00:00
newLinks <- readIORef ( _nodeInput_forwardLinks thisIn )
go ( IntMap . insert k this found ) ( IntMap . union restToSearch ( newLinks ` IntMap . difference ` found ) )
findBackwardVals :: Ord a => NodeOutput a -> IO ( Set a )
findBackwardVals i = Set . fromList . IntMap . elems <$> findBackward i
2023-04-07 21:51:35 +00:00
findBackward :: forall a . NodeOutput a -> IO ( IntMap a )
2023-04-07 22:09:31 +00:00
findBackward o = go mempty ( IntMap . singleton ( _nodeOutput_key o ) o )
2023-04-07 21:51:35 +00:00
where
go :: IntMap a -> IntMap ( NodeOutput a ) -> IO ( IntMap a )
go found toSearch = case IntMap . minViewWithKey toSearch of
Nothing -> pure found
2023-04-08 01:48:29 +00:00
Just ( ( k , thisOut ) , restToSearch ) -> do
getNodeContents ( _nodeOutput_contents thisOut ) >>= \ case
2023-04-07 21:51:35 +00:00
Nothing -> go found restToSearch
Just this -> do
2023-04-08 01:48:29 +00:00
newLinks <- readIORef ( _nodeOutput_backLinks thisOut )
go ( IntMap . insert k this found ) ( IntMap . union restToSearch ( newLinks ` IntMap . difference ` found ) )
2023-04-07 21:51:35 +00:00
testDualWeak :: IO ()
testDualWeak = do
do
target <- newIORef ()
r <- mkWeakWithIORefKey target ()
( w , a , b ) <- newDualWeak target
performGC
threadDelay 1000000
Just () <- deRefWeak r
Just _ <- getDualWeak w
touch ( a , b )
performGCUntilFinalizersQuiesce
do
target <- newIORef ()
r <- mkWeakWithIORefKey target ()
( w , a , _ ) <- newDualWeak target
performGC
threadDelay 1000000
Nothing <- deRefWeak r
Nothing <- getDualWeak w
touch a
performGCUntilFinalizersQuiesce
do
target <- newIORef ()
r <- mkWeakWithIORefKey target ()
( w , _ , b ) <- newDualWeak target
performGC
threadDelay 1000000
performGC
Nothing <- deRefWeak r
Nothing <- getDualWeak w
touch b
performGCUntilFinalizersQuiesce
performGCUntilFinalizersQuiesce :: IO ()
performGCUntilFinalizersQuiesce = do
writeIORef finalizerDidRunRef False
performGC
fence <- createFinalizerFence -- Based on my shaky memory, I think finalizers are processed in a queue, and therefore this fence will run after all the other finalizers in the GC we just finished. However, it may run at ANY point in the subsequent GC, so we can't rely on it to know that *that* GC had no finalizers run.
performGC
waitForFinalizerFence fence
readIORef finalizerDidRunRef >>= \ case
True -> performGCUntilFinalizersQuiesce
False -> pure ()
{- # NOINLINE finalizerDidRunRef # -}
finalizerDidRunRef :: IORef Bool
finalizerDidRunRef = unsafePerformIO $ newIORef False
newtype FinalizerFence = FinalizerFence ( MVar () )
createFinalizerFence :: IO FinalizerFence
createFinalizerFence = do
r <- newIORef ()
v <- newEmptyMVar
_ <- mkWeakWithIORefKeyWithFinalizer r () $ putMVar v ()
pure $ FinalizerFence v
waitForFinalizerFence :: FinalizerFence -> IO ()
waitForFinalizerFence ( FinalizerFence v ) = takeMVar v
newtype DualWeak a = DualWeak ( Weak ( Weak ( IORef ( Maybe a ) ) ) )
newtype Ticket = Ticket ( IORef () )
-- | Creates a weak reference to `a` which only remains alive if *both* tickets are alive
newDualWeak :: a -> IO ( DualWeak a , Ticket , Ticket )
newDualWeak v = do
vRef <- newIORef $ Just v
tInner <- newIORef ()
wInner <- mkWeakWithIORefKey tInner vRef
tOuter <- newIORef ()
wOuter <- mkWeakWithIORefKeyWithFinalizer tOuter wInner $ do
writeIORef finalizerDidRunRef True
deRefWeak wInner >>= \ case
Nothing -> pure ()
Just vRef' -> do
writeIORef vRef' Nothing
pure ( DualWeak wOuter , Ticket tOuter , Ticket tInner )
getDualWeak :: DualWeak a -> IO ( Maybe a )
getDualWeak ( DualWeak wOuter ) = do
deRefWeak wOuter >>= \ case
Nothing -> pure Nothing
Just wInner -> deRefWeak wInner >>= \ case
Nothing -> pure Nothing
Just vRef -> readIORef vRef
mkWeakWithIORefKey :: IORef a -> b -> IO ( Weak b )
mkWeakWithIORefKey ( IORef ( STRef r # ) ) v = IO $ \ s ->
case mkWeakNoFinalizer # r # v s of ( # s1 , w # ) -> ( # s1 , Weak w # )
mkWeakWithIORefKeyWithFinalizer :: IORef a -> b -> IO () -> IO ( Weak b )
mkWeakWithIORefKeyWithFinalizer ( IORef ( STRef r # ) ) v ( IO f ) = IO $ \ s ->
case mkWeak # r # v f s of ( # s1 , w # ) -> ( # s1 , Weak w # )