packages feed

natskell-1.1.0.0: internal/Plumbing/Queue/TransactionalQueue.hs

module Queue.TransactionalQueue
  ( newQueue
  , newQueueWithCapacity
  ) where

import           Control.Concurrent.STM
import qualified Control.Monad
import           Data.List              (partition)
import           Numeric.Natural        (Natural)
import           Queue.API
    ( Queue (Queue)
    , QueueItem
    , TryEnqueueResult (..)
    , isConnectionScoped
    , isWithinPayloadLimit
    , payloadLimitRejections
    )

data TransactionalQueue = TransactionalQueue
                            { transactionalQueueItems  :: TBQueue QueueItem
                            , transactionalQueueClosed :: TMVar ()
                            }

newQueue :: IO Queue
newQueue = newQueueWithCapacity 1000

newQueueWithCapacity :: Natural -> IO Queue
newQueueWithCapacity capacity = do
  queueItems <- newTBQueueIO (max 1 capacity)
  queueClosed <- newEmptyTMVarIO
  let transactionalQueue =
        TransactionalQueue
          { transactionalQueueItems = queueItems
          , transactionalQueueClosed = queueClosed
          }
  pure $
    Queue
      (enqueue transactionalQueue)
      (enqueueSTM transactionalQueue)
      (tryEnqueue transactionalQueue)
      (tryEnqueueSTM transactionalQueue)
      (dequeue transactionalQueue)
      (close transactionalQueue)
      (open transactionalQueue)
      (openSTM transactionalQueue)
      (discardConnectionScoped transactionalQueue)
      (closeAndDiscardConnectionScoped transactionalQueue)
      (closeAndDiscardAll transactionalQueue)
      (discardOversizedPayloads transactionalQueue)

enqueue :: TransactionalQueue -> QueueItem -> IO (Either String ())
enqueue transactionalQueue = atomically . enqueueSTM transactionalQueue

enqueueSTM :: TransactionalQueue -> QueueItem -> STM (Either String ())
enqueueSTM transactionalQueue item =
  (do
    isOpen <- isEmptyTMVar (transactionalQueueClosed transactionalQueue)
    check isOpen
    writeTBQueue (transactionalQueueItems transactionalQueue) item
    pure (Right ()))
  `orElse`
  (Left "Queue is closed" <$ readTMVar (transactionalQueueClosed transactionalQueue))

tryEnqueue :: TransactionalQueue -> QueueItem -> IO TryEnqueueResult
tryEnqueue transactionalQueue = atomically . tryEnqueueSTM transactionalQueue

tryEnqueueSTM :: TransactionalQueue -> QueueItem -> STM TryEnqueueResult
tryEnqueueSTM transactionalQueue item = do
  isOpen <- isEmptyTMVar (transactionalQueueClosed transactionalQueue)
  if not isOpen
    then pure TryQueueClosed
    else do
      isFull <- isFullTBQueue (transactionalQueueItems transactionalQueue)
      if isFull
        then pure TryQueueFull
        else writeTBQueue (transactionalQueueItems transactionalQueue) item >> pure TryEnqueued

dequeue :: TransactionalQueue -> IO (Either String QueueItem)
dequeue transactionalQueue = atomically $ do
  isOpen <- isEmptyTMVar (transactionalQueueClosed transactionalQueue)
  if isOpen
    then Right <$> readTBQueue (transactionalQueueItems transactionalQueue)
    else pure (Left "Queue is closed")

close :: TransactionalQueue -> IO ()
close transactionalQueue = atomically $ do
  _ <- tryTakeTMVar (transactionalQueueClosed transactionalQueue)
  putTMVar (transactionalQueueClosed transactionalQueue) ()

open :: TransactionalQueue -> IO ()
open = atomically . openSTM

openSTM :: TransactionalQueue -> STM ()
openSTM transactionalQueue =
  Control.Monad.void (tryTakeTMVar (transactionalQueueClosed transactionalQueue))

discardConnectionScoped :: TransactionalQueue -> IO ()
discardConnectionScoped transactionalQueue = atomically $ do
  discardConnectionScopedSTM transactionalQueue

closeAndDiscardConnectionScoped :: TransactionalQueue -> STM ()
closeAndDiscardConnectionScoped transactionalQueue = do
  _ <- tryTakeTMVar (transactionalQueueClosed transactionalQueue)
  putTMVar (transactionalQueueClosed transactionalQueue) ()
  discardConnectionScopedSTM transactionalQueue

closeAndDiscardAll :: TransactionalQueue -> STM ()
closeAndDiscardAll transactionalQueue = do
  _ <- tryTakeTMVar (transactionalQueueClosed transactionalQueue)
  putTMVar (transactionalQueueClosed transactionalQueue) ()
  Control.Monad.void (flushTBQueue (transactionalQueueItems transactionalQueue))

discardConnectionScopedSTM :: TransactionalQueue -> STM ()
discardConnectionScopedSTM transactionalQueue = do
  queued <- flushTBQueue (transactionalQueueItems transactionalQueue)
  mapM_
    (writeTBQueue (transactionalQueueItems transactionalQueue))
    (filter (not . isConnectionScoped) queued)

discardOversizedPayloads :: TransactionalQueue -> Int -> STM [IO ()]
discardOversizedPayloads transactionalQueue maximumSize = do
  queued <- flushTBQueue (transactionalQueueItems transactionalQueue)
  let (retained, discarded) = partition (isWithinPayloadLimit maximumSize) queued
  mapM_ (writeTBQueue (transactionalQueueItems transactionalQueue)) retained
  pure (concatMap (payloadLimitRejections maximumSize) discarded)