packages feed

capnp-0.17.0.0: lib/Internal/Rc.hs

{-# 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