547 lines
18 KiB
Haskell
547 lines
18 KiB
Haskell
{-# LANGUAGE MagicHash #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE UnboxedTuples #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE RecursiveDo #-}
|
|
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
|
|
import Data.Set (Set)
|
|
import qualified Data.Set as Set
|
|
import System.IO.Unsafe
|
|
import Control.Monad
|
|
import Control.Concurrent
|
|
import Data.Foldable
|
|
import Data.These
|
|
import Unsafe.Coerce
|
|
import Control.Monad.Fix
|
|
import Data.List.NonEmpty (NonEmpty (..))
|
|
import Data.Semigroup
|
|
|
|
import UIO
|
|
import Test.UIO
|
|
|
|
main :: IO ()
|
|
main = do
|
|
testDualWeak
|
|
testWeakChain
|
|
testGraphChain
|
|
testGraphX
|
|
testGraphO
|
|
|
|
type UIO = IO
|
|
|
|
data WeakBagInput a
|
|
data WeakBagOutput a
|
|
data WeakMutVarInput a = WeakMutVarInput (WeakWithLifespan (IORef a))
|
|
data WeakMutVarOutput a = WeakMutVarOutput (IORef a)
|
|
|
|
data WeakWithLifespan a = WeakWithLifespan (Weak a) Lifespan
|
|
|
|
newWeakMutVar :: a -> UIO (WeakMutVarInput a, WeakMutVarOutput a)
|
|
newWeakMutVar a = do
|
|
r <- newIORef a
|
|
l <- lifespanOfIORef r
|
|
w <- newWeakWithLifespan l r
|
|
pure (WeakMutVarInput w, WeakMutVarOutput r)
|
|
|
|
writeWeakMutVar :: WeakMutVarInput a -> UIO (Effect a)
|
|
writeWeakMutVar target = mapEffect const =<< modifyWeakMutVar target
|
|
|
|
modifyWeakMutVar :: WeakMutVarInput a -> UIO (Effect (a -> a))
|
|
modifyWeakMutVar (WeakMutVarInput w) = Effect <$> do
|
|
mapWeakWithLifespan' (\r f -> atomicModifyIORef' r $ \v -> (f v, ())) w
|
|
|
|
readWeakMutVar :: WeakMutVarOutput a -> Coeffect a
|
|
readWeakMutVar (WeakMutVarOutput r) = Coeffect $ readIORef r
|
|
|
|
testEffect1 :: IO ()
|
|
testEffect1 = do
|
|
(i, o) <- newWeakMutVar "A"
|
|
e <- traceEffect id =<< writeWeakMutVar i
|
|
performGCUntilFinalizersQuiesce
|
|
putStrLn "X"
|
|
runEffect e "B"
|
|
touch o
|
|
_ <- runCoeffect (readWeakMutVar o)
|
|
performGCUntilFinalizersQuiesce
|
|
putStrLn "Y"
|
|
runEffect e "C"
|
|
putStrLn "Z"
|
|
|
|
testEffect2 :: IO ()
|
|
testEffect2 = do
|
|
(ia, oa) <- newWeakMutVar (1 :: Int)
|
|
(ib, ob) <- newWeakMutVar 1
|
|
modifyA <- traceEffect (const "modifyA") =<< modifyWeakMutVar ia
|
|
modifyB <- traceEffect (const "modifyB") =<< modifyWeakMutVar ib
|
|
let readA = readWeakMutVar oa
|
|
incrementA <- mapEffect (\() -> (+1)) modifyA
|
|
addToB <- mapEffect (\n -> (+n)) modifyB
|
|
addAToB <- withCoeffect readA addToB
|
|
e <- incrementA `andThen` addAToB
|
|
performGCUntilFinalizersQuiesce
|
|
runEffect e ((), ())
|
|
touch oa
|
|
|
|
runEffect :: Effect a -> a -> IO ()
|
|
runEffect (Effect e) a = deRefWeakWithLifespan e >>= \case
|
|
Nothing -> pure ()
|
|
Just f -> f a
|
|
|
|
traceEffect :: (a -> String) -> Effect a -> UIO (Effect a)
|
|
traceEffect f = weakEffect $ putStrLn . f
|
|
|
|
-- Run the given IO action if the provided Effect is still runnable. Note that the IO action may run even if the effect is *not* runnable, depending on garbage collection timing.
|
|
weakEffect :: (a -> IO ()) -> Effect a -> UIO (Effect a)
|
|
weakEffect f (Effect e) = fmap Effect $ forWeakWithLifespan' e $ \fe a -> do
|
|
f a
|
|
fe a
|
|
|
|
runCoeffect :: Coeffect a -> IO a
|
|
runCoeffect (Coeffect c) = c
|
|
|
|
|
|
emptyWeak :: Weak a
|
|
emptyWeak = unsafeCoerce emptyWeakUnit
|
|
|
|
{-# NOINLINE emptyWeakUnit #-}
|
|
emptyWeakUnit :: Weak ()
|
|
emptyWeakUnit = unsafePerformIO $ do
|
|
w <- mkWeakPtr () Nothing
|
|
finalize w
|
|
pure w
|
|
|
|
newWeakWithLifespan :: Lifespan -> a -> UIO (WeakWithLifespan a)
|
|
newWeakWithLifespan (Lifespan l) a = do
|
|
deRefWeak l >>= \case
|
|
Nothing -> pure $ WeakWithLifespan emptyWeak (Lifespan l)
|
|
Just r -> do
|
|
w <- mkWeakWithIORefKey r a
|
|
pure $ WeakWithLifespan w (Lifespan l)
|
|
|
|
underlyingLifespan :: WeakWithLifespan a -> Lifespan
|
|
underlyingLifespan (WeakWithLifespan _ l) = l
|
|
|
|
deRefWeakWithLifespan :: WeakWithLifespan a -> UIO (Maybe a)
|
|
deRefWeakWithLifespan (WeakWithLifespan w _) = deRefWeak w
|
|
|
|
-- Applies the function strictly; usually this is what you want, so that extra data is not retained
|
|
forWeakWithLifespan' :: WeakWithLifespan a -> (a -> b) -> UIO (WeakWithLifespan b)
|
|
forWeakWithLifespan' = flip mapWeakWithLifespan'
|
|
|
|
-- Applies the function strictly; usually this is what you want, so that extra data is not retained
|
|
mapWeakWithLifespan' :: (a -> b) -> WeakWithLifespan a -> UIO (WeakWithLifespan b)
|
|
mapWeakWithLifespan' f (WeakWithLifespan w l) = do
|
|
deRefWeak w >>= \case
|
|
Nothing -> pure $ WeakWithLifespan emptyWeak l
|
|
Just v -> newWeakWithLifespan l $! f v
|
|
|
|
bothAlive :: WeakWithLifespan a -> WeakWithLifespan b -> UIO (WeakWithLifespan (a, b))
|
|
bothAlive = undefined
|
|
|
|
-- This is impossible because it assumes that weaks *do actual work* when you retrieve them. This isn't what we want.
|
|
alignAlive :: WeakWithLifespan a -> WeakWithLifespan b -> UIO (WeakWithLifespan (These a b))
|
|
alignAlive = undefined
|
|
|
|
data Effect a = Effect (WeakWithLifespan (a -> UIO ())) -- Take an `a` and do a side effect with it
|
|
|
|
data Coeffect a = Coeffect (UIO a) -- Read an `a` without doing any side effects
|
|
|
|
-- Like `also`, but also guarantees order. But I'm not sure what the semantics should really be here, since we could want coeffects ordered separately from effects
|
|
andThen :: Effect a -> Effect b -> UIO (Effect (a, b))
|
|
andThen = also
|
|
|
|
also :: Effect a -> Effect b -> UIO (Effect (a, b))
|
|
also (Effect ea) (Effect eb) = Effect <$> do
|
|
myLifespan <- unionLifespan (underlyingLifespan ea) (underlyingLifespan eb)
|
|
newWeakWithLifespan myLifespan $ \(a, b) -> do
|
|
deRefWeakWithLifespan ea >>= \case
|
|
Nothing -> pure ()
|
|
Just fa -> fa a
|
|
deRefWeakWithLifespan eb >>= \case
|
|
Nothing -> pure ()
|
|
Just fb -> fb b
|
|
|
|
mapEffect :: (b -> a) -> Effect a -> UIO (Effect b)
|
|
mapEffect f (Effect e) = Effect <$> mapWeakWithLifespan' (\fe -> fe . f) e
|
|
|
|
withCoeffect :: Coeffect a -> Effect a -> UIO (Effect ())
|
|
withCoeffect (Coeffect c) (Effect e) = Effect <$> do
|
|
let f fe () = do
|
|
v <- c
|
|
fe v
|
|
mapWeakWithLifespan' f e
|
|
|
|
coAlso :: Coeffect a -> Coeffect b -> Coeffect (a, b)
|
|
coAlso = undefined
|
|
|
|
--TODO: This should use a WeakBag
|
|
newtype Lifespan = Lifespan (Weak (IORef [LifespanBacklink]))
|
|
newtype LifespanBacklink = LifespanBacklink (IORef [LifespanBacklink])
|
|
|
|
modifyWeakIORef :: Weak (IORef a) -> (a -> a) -> IO ()
|
|
modifyWeakIORef w f = deRefWeak w >>= \case
|
|
Nothing -> pure ()
|
|
Just r -> atomicModifyIORef' r $ \v -> (f v, ())
|
|
|
|
lifespanOfIORef :: IORef a -> UIO Lifespan
|
|
lifespanOfIORef basis = do
|
|
mine <- newIORef []
|
|
w <- mkWeakWithIORefKey basis mine -- This exploits the fact that System.Mem.Weak references keep the value alive even when the weak reference itself dies.
|
|
pure $ Lifespan w
|
|
|
|
-- Return a lifespan
|
|
unionLifespan :: Lifespan -> Lifespan -> UIO Lifespan
|
|
unionLifespan (Lifespan a) (Lifespan b) = do
|
|
r <- newIORef []
|
|
w <- mkWeakWithIORefKey r r
|
|
modifyWeakIORef a (LifespanBacklink r :)
|
|
modifyWeakIORef b (LifespanBacklink r :)
|
|
pure $ Lifespan w
|
|
|
|
intersectionLifespan :: Lifespan -> Lifespan -> UIO Lifespan
|
|
intersectionLifespan a b = undefined
|
|
|
|
-- If we do readWeakMutVar v `bind` writeWeakMutVar v', we should only keep v alive if `v'`'s output side is alive
|
|
bind :: IO a -> (a -> IO b) -> IO b
|
|
bind = undefined
|
|
|
|
data Event a = Event
|
|
{ _event_items :: WeakBagInput (a -> IO ()) -- Allows adding items to the weak bag. Does not keep the weak bag alive; if the bag is gone, adding an item does nothing.
|
|
, _event_currentValue :: WeakMutVarOutput (Maybe a) -- Allows seeing the current state of the event: if Nothing, it either isn't firing or hasn't fired yet this frame; if Just, it has fired this frame.
|
|
--TODO: Position in topological ordering; only needed when we introduce Merge
|
|
}
|
|
|
|
data Trigger a = Trigger
|
|
{ _trigger_items :: WeakBagOutput (a -> IO ()) -- Allows retrieving the contents of the weak bag
|
|
, _trigger_currentValue :: WeakMutVarInput (Maybe a)
|
|
}
|
|
|
|
newEvent :: IO (Trigger a, Event a)
|
|
newEvent = undefined
|
|
|
|
fireTrigger :: Trigger a -> a -> IO ()
|
|
fireTrigger = undefined
|
|
|
|
data Hold a = Hold
|
|
{
|
|
}
|
|
|
|
newHold :: a -> Event a -> IO (Hold a)
|
|
newHold = undefined
|
|
|
|
-- 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
|
|
w <- mkWeakWithIORefKey b "b is still alive"
|
|
performGCUntilFinalizersQuiesce
|
|
Just "b is still alive" <- deRefWeak w
|
|
touch a
|
|
|
|
testGraphChain :: IO ()
|
|
testGraphChain = do
|
|
do
|
|
(nai, neo) <- buildGraphChain
|
|
performGCUntilFinalizersQuiesce
|
|
["a", "b", "c", "d", "e"] <- toList <$> findForwardVals nai
|
|
["a", "b", "c", "d", "e"] <- toList <$> findBackwardVals neo
|
|
touch (nai, neo)
|
|
do
|
|
(nai, _) <- buildGraphChain
|
|
performGCUntilFinalizersQuiesce
|
|
[] <- toList <$> findForwardVals nai
|
|
touch nai
|
|
do
|
|
(_, neo) <- buildGraphChain
|
|
performGCUntilFinalizersQuiesce
|
|
[] <- toList <$> findBackwardVals neo
|
|
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)
|
|
|
|
buildGraphX :: IO (NodeInput String, NodeInput String, NodeOutput String, NodeOutput String)
|
|
buildGraphX = do
|
|
(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
|
|
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)
|
|
|
|
testGraphO :: IO ()
|
|
testGraphO = do
|
|
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
|
|
(nai, nao) <- newNode "a"
|
|
(nbi, nbo) <- newNode "b"
|
|
(nci, nco) <- newNode "c"
|
|
link nao nbi
|
|
link nbo nci
|
|
link nco nai
|
|
pure (nai, nao)
|
|
|
|
data NodeInput a = NodeInput
|
|
{ _nodeInput_key :: !Int
|
|
, _nodeInput_contents :: !(Weak (IORef (Maybe a)))
|
|
, _nodeInput_forwardLinks :: !(IORef (IntMap (NodeInput a)))
|
|
, _nodeInput_backLinks :: !(Weak (IORef (IntMap (NodeOutput a))))
|
|
}
|
|
|
|
data NodeOutput a = NodeOutput
|
|
{ _nodeOutput_key :: !Int
|
|
, _nodeOutput_contents :: !(Weak (IORef (Maybe a)))
|
|
, _nodeOutput_forwardLinks :: !(Weak (IORef (IntMap (NodeInput a))))
|
|
, _nodeOutput_backLinks :: !(IORef (IntMap (NodeOutput a)))
|
|
}
|
|
|
|
{-# 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
|
|
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
|
|
pure
|
|
( NodeInput
|
|
{ _nodeInput_key = nodeId
|
|
, _nodeInput_contents = w
|
|
, _nodeInput_backLinks = wBack
|
|
, _nodeInput_forwardLinks = forwardLinks
|
|
}
|
|
, NodeOutput
|
|
{ _nodeOutput_key = nodeId
|
|
, _nodeOutput_contents = w
|
|
, _nodeOutput_backLinks = backLinks
|
|
, _nodeOutput_forwardLinks = wForward
|
|
}
|
|
)
|
|
|
|
getNodeContents :: Weak (IORef (Maybe a)) -> IO (Maybe a)
|
|
getNodeContents w = do
|
|
deRefWeak w >>= \case
|
|
Nothing -> pure Nothing
|
|
Just r -> readIORef r
|
|
|
|
link :: NodeOutput a -> NodeInput a -> IO ()
|
|
link aOut bIn = do
|
|
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, ())
|
|
|
|
findForwardVals :: Ord a => NodeInput a -> IO (Set a)
|
|
findForwardVals i = Set.fromList . IntMap.elems <$> findForward i
|
|
|
|
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
|
|
getNodeContents (_nodeInput_contents thisIn) >>= \case
|
|
Nothing -> go found restToSearch
|
|
Just this -> do
|
|
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
|
|
|
|
findBackward :: forall a. NodeOutput a -> IO (IntMap a)
|
|
findBackward o = go mempty (IntMap.singleton (_nodeOutput_key o) o)
|
|
where
|
|
go :: IntMap a -> IntMap (NodeOutput a) -> IO (IntMap a)
|
|
go found toSearch = case IntMap.minViewWithKey toSearch of
|
|
Nothing -> pure found
|
|
Just ((k, thisOut), restToSearch) -> do
|
|
getNodeContents (_nodeOutput_contents thisOut) >>= \case
|
|
Nothing -> go found restToSearch
|
|
Just this -> do
|
|
newLinks <- readIORef (_nodeOutput_backLinks thisOut)
|
|
go (IntMap.insert k this found) (IntMap.union restToSearch (newLinks `IntMap.difference` found))
|
|
|
|
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 #)
|