ki-0.1.0: src/Ki/Ctx.hs
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TypeApplications #-}
module Ki.Ctx
( Ctx (..),
newCtxSTM,
deriveCtx,
cancelCtx,
cancelCtxSTM,
ctxCancelToken,
)
where
import qualified Data.IntMap.Strict as IntMap
import Ki.CancelToken
import Ki.Prelude
data Ctx = Ctx
{ cancelTokenVar :: TVar (Maybe CancelToken),
childrenVar :: TVar (IntMap Ctx),
-- | The next id to assign to a child context. The child needs a unique identifier so it can delete itself from its
-- parent's children map if it's cancelled independently. Wrap-around seems ok; that's a *lot* of children for one
-- parent to have.
nextIdVar :: TVar Int,
-- | When I'm cancelled, this action removes myself from my parent's context. This isn't simply a pointer to the
-- parent 'Ctx' for three reasons:
--
-- * "Root" contexts don't have a parent, so it'd have to be a Maybe (one more pointer indirection)
-- * We don't really need a reference to the parent, because we only want to be able to remove ourselves from its
-- children map, so just storing the STM action that does exactly seems a bit safer, even if conceptually it's
-- a bit indirect.
-- * If we stored a reference to the parent, we'd also have to store our own id, rather than just currying it into
-- this action.
onCancel :: STM ()
}
newCtxSTM :: STM Ctx
newCtxSTM =
newCtxSTM_ (pure ())
newCtxSTM_ :: STM () -> STM Ctx
newCtxSTM_ onCancel = do
cancelTokenVar <- newTVar Nothing
childrenVar <- newTVar IntMap.empty
nextIdVar <- newTVar 0
pure Ctx {cancelTokenVar, childrenVar, nextIdVar, onCancel}
deriveCtx :: Ctx -> STM Ctx
deriveCtx context@Ctx {cancelTokenVar, childrenVar, nextIdVar} =
readTVar cancelTokenVar >>= \case
Nothing -> do
childId <- readTVar nextIdVar
writeTVar nextIdVar $! childId + 1
child <- newCtxSTM_ (modifyTVar' childrenVar (IntMap.delete childId))
children <- readTVar childrenVar
writeTVar childrenVar $! IntMap.insert childId child children
pure child
Just (CancelToken _) -> pure context
cancelCtx :: Ctx -> IO ()
cancelCtx context = do
token <- newCancelToken
atomically (cancelCtxSTM context token)
cancelCtxSTM :: Ctx -> CancelToken -> STM ()
cancelCtxSTM ctx@Ctx {onCancel} token =
whenCanceling ctx token do
cancelChildren ctx token
onCancel
ctxCancelSTM_ :: CancelToken -> Ctx -> STM ()
ctxCancelSTM_ token ctx =
whenCanceling ctx token (cancelChildren ctx token)
whenCanceling :: Ctx -> CancelToken -> STM () -> STM ()
whenCanceling Ctx {cancelTokenVar} token action =
readTVar cancelTokenVar >>= \case
Nothing -> do
writeTVar cancelTokenVar $! Just token
action
Just (CancelToken _) -> pure ()
cancelChildren :: Ctx -> CancelToken -> STM ()
cancelChildren Ctx {childrenVar} token = do
children <- readTVar childrenVar
for_ (IntMap.elems children) (ctxCancelSTM_ token)
ctxCancelToken :: Ctx -> STM CancelToken
ctxCancelToken Ctx {cancelTokenVar} =
readTVar cancelTokenVar >>= \case
Nothing -> retry
Just token -> pure token