{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-| Module: Internal.Rc Description: Reference counted boxes. This module provides a reference-counted cell type 'Rc', which contains a value and a finalizer. When the reference count reaches zero, the value is dropped and the finalizer is run. -} module Internal.Rc ( Rc , new , get , incr , decr , release ) where import Control.Concurrent.STM -- | A reference-counted container for a value of type @a@. newtype Rc a = Rc (TVar (Maybe (RcState a))) deriving(Eq) data RcState a = RcState { refCount :: !Int , value :: a , finalizer :: STM () } -- | @'new' val finalizer@ creates a new 'Rc' containing the value @val@, with -- an initial reference count of 1. When the reference count drops to zero, the -- finalizer will be run. new :: a -> STM () -> STM (Rc a) new value finalizer = fmap Rc $ newTVar $ Just RcState { refCount = 1 , value , finalizer } -- | Increment the reference count. incr :: Rc a -> STM () incr (Rc tv) = modifyTVar' tv $ fmap $ \s@RcState{refCount} -> s { refCount = refCount + 1 } -- | Decrement the reference count. If this brings the count to zero, run the -- finalizer and release the value. decr :: Rc a -> STM () decr (Rc tv) = readTVar tv >>= \case Nothing -> pure () Just RcState{refCount=1, finalizer} -> do writeTVar tv Nothing finalizer Just s@RcState{refCount} -> writeTVar tv $ Just s { refCount = refCount - 1 } -- | Release the value immediately, and run the finalizer, regardless of the -- current reference count. release :: Rc a -> STM () release (Rc tv) = readTVar tv >>= \case Nothing -> pure () Just RcState{finalizer} -> do finalizer writeTVar tv Nothing -- | Fetch the value, or 'Nothing' if it has been released. get :: Rc a -> STM (Maybe a) get (Rc tv) = fmap value <$> readTVar tv