packages feed

pgmq-inspect-0.7.0.0: src/Pgmq/Inspect/Outbox.hs

-- | Bounded transport state, with generation tags checked immediately before writing.
module Pgmq.Inspect.Outbox
  ( Outbox,
    Generation,
    SubscriptionTag,
    newOutbox,
    enqueue,
    enqueueTagged,
    takeOutgoing,
    clearQueue,
    queuedFrames,
    tagCurrent,
    overflowFrame,
  )
where

import Control.Concurrent.STM
import Control.Monad (forM_, void, when)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Data.Unique (Unique)
import Numeric.Natural (Natural)
import Pgmq.Inspect.Frames

type Generation = Unique

type SubscriptionTag = (TVar (Map Text Generation), Text, Generation)

data Outbox = Outbox !(TBQueue (Maybe SubscriptionTag, ServerFrame)) !(TVar Bool)

newOutbox :: Natural -> IO Outbox
newOutbox capacity = Outbox <$> newTBQueueIO capacity <*> newTVarIO False

enqueue :: Outbox -> ServerFrame -> STM ()
enqueue out = enqueueTagged out Nothing

enqueueTagged :: Outbox -> Maybe SubscriptionTag -> ServerFrame -> STM ()
enqueueTagged (Outbox frames overflow) tag frame = do
  full <- isFullTBQueue frames
  when full $ void (readTBQueue frames) >> writeTVar overflow True
  writeTBQueue frames (tag, frame)

tagCurrent :: SubscriptionTag -> STM Bool
tagCurrent (generations, queue, generation) = (== Just generation) . Map.lookup queue <$> readTVar generations

takeOutgoing :: Outbox -> STM (Bool, ServerFrame)
takeOutgoing box@(Outbox frames overflow) = do
  (tag, frame) <- readTBQueue frames
  valid <- maybe (pure True) tagCurrent tag
  if not valid
    then takeOutgoing box
    else do
      dropped <- readTVar overflow
      writeTVar overflow False
      pure (dropped, frame)

queuedFrames :: Outbox -> STM Natural
queuedFrames (Outbox frames _) = lengthTBQueue frames

overflowFrame :: ServerFrame
overflowFrame = ErrorFrame "overflow" "The server dropped frames because this connection was not reading them; re-subscribe or re-read the queue over HTTP." Nothing

-- | Explicit subscribe/unsubscribe discards any frames still queued for that name.
clearQueue :: Outbox -> Text -> STM ()
clearQueue (Outbox frames _) name = do
  queued <- flushTBQueue frames
  forM_ queued $ \item@(_, frame) -> when (frameQueue frame /= Just name) (writeTBQueue frames item)
  where
    frameQueue (Snapshot q _ _) = Just q
    frameQueue (Update q _ _) = Just q
    frameQueue (ErrorFrame _ _ q) = q
    frameQueue _ = Nothing