@@ -10,6 +10,7 @@ module Control.RefCount (
1010 , mkRefCounter1
1111 , addReference
1212 , removeReference
13+ , removeReferenceN
1314 , upgradeWeakReference
1415 , readRefCount
1516 ) where
@@ -21,7 +22,9 @@ import Control.Monad.Class.MonadThrow
2122import Control.Monad.Primitive
2223import Data.Maybe
2324import Data.Primitive.PrimVar
25+ import Data.Word
2426import GHC.Stack
27+ import Text.Printf
2528
2629-- | A reference counter with an optional finaliser action. Once the reference
2730-- count reaches @0@, the finaliser will be run.
@@ -89,6 +92,43 @@ removeReference RefCounter{countVar, finaliser} = mask_ $ do
8992 assertWithCallStack (prevCount > 0 ) $ pure ()
9093 when (prevCount == 1 ) $ sequence_ finaliser
9194
95+ -- TODO: remove uses of this API. Eventually all references should be singular,
96+ -- and not use patterns where if A contains B then N references on A becomes N
97+ -- references on B. Instead this should be a single reference from A to B,
98+ -- irrespective of the number of references to A.
99+ {-# SPECIALISE removeReferenceN :: HasCallStack => RefCounter IO -> Word64 -> IO () #-}
100+ -- | Decrease the reference counter by @n@. @n@ must be a positive number.
101+ --
102+ -- The count must be known (from context) to be non-zero and at least as large
103+ -- as @n@. Typically this will be because the caller has @n@ references already
104+ -- (that they took out themselves or were given).
105+ removeReferenceN :: (HasCallStack , PrimMonad m , MonadMask m ) => RefCounter m -> Word64 -> m ()
106+ removeReferenceN RefCounter {countVar, finaliser} n = mask_ $ do
107+ -- n should be positive
108+ assert (n > 0 ) $ pure ()
109+ let ! n' = fromIntegralChecked n
110+ prevCount <- fetchSubInt countVar n'
111+ -- the reference count must not already be 0, because then the finaliser
112+ -- will have run already
113+ assertWithCallStack (prevCount > 0 ) $ pure ()
114+ -- the reference count can not go below zero
115+ assertWithCallStack (prevCount >= n') $ pure ()
116+ when (prevCount <= n') $ sequence_ finaliser
117+
118+ -- TODO: remove when removeReferenceN is removed
119+ {-# INLINABLE fromIntegralChecked #-}
120+ -- | Like 'fromIntegral', but throws an error when @(x :: a) /= fromIntegral
121+ -- (fromIntegral x :: b)@.
122+ fromIntegralChecked :: (HasCallStack , Integral a , Integral b , Show a ) => a -> b
123+ fromIntegralChecked x
124+ | x'' == x
125+ = x'
126+ | otherwise
127+ = error $ printf " fromIntegralChecked: conversion failed, %s /= %s" (show x) (show x'')
128+ where
129+ x' = fromIntegral x
130+ x'' = fromIntegral x'
131+
92132-- | Try to turn a \"weak\" reference on something into a proper reference.
93133-- This is by analogy with @deRefWeak :: Weak v -> IO (Maybe v)@, but for
94134-- reference counts.
@@ -120,6 +160,7 @@ upgradeWeakReference RefCounter{countVar} = do
120160 then return True
121161 else casLoop prevCount'
122162
163+ -- TODO: remove when removeRefenceN is removed
123164{-# SPECIALISE readRefCount :: RefCounter IO -> IO RefCount #-}
124165-- | Warning: reading the current reference count is inherently racy as there is
125166-- no way to reliably act on the information. It can be useful for debugging.
0 commit comments