packages feed

supernova-0.0.3: src/Pulsar/AppState.hs

{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# LANGUAGE GeneralizedNewtypeDeriving, TemplateHaskell #-}

module Pulsar.AppState where

import           Control.Concurrent.Async       ( Async )
import           Control.Concurrent.Chan
import           Control.Concurrent.MVar
import           Control.Monad.IO.Class
import qualified Data.Binary                   as B
import           Data.Foldable                  ( traverse_ )
import           Data.IORef
import           Lens.Family
import           Lens.Family.TH
import           Pulsar.Protocol.Frame          ( Response(..) )

newtype ReqId = ReqId B.Word64 deriving (Eq, Num, Show)
newtype SeqId = SeqId B.Word64 deriving (Eq, Num, Show)
newtype ProducerId = PId B.Word64 deriving (Eq, Num, Show)
newtype ConsumerId = CId B.Word64 deriving (Eq, Num, Show)

newtype Permits = Permits B.Word32 deriving (Eq, Num, Show)

{- | It represents a running worker in the background along with a synchronizer. -}
type Worker = (Async (), MVar ())

{- | It represents a list of registered sequence ids for a producer. -}
type ProducerSeqs = [(SeqId, MVar Response)]

data AppState = AppState
  { _appConsumers :: [(ConsumerId, Chan Response)]    -- a list of consumer identifiers associated with a communication channel
  , _appConsumerId :: ConsumerId                      -- an incremental counter to assign unique consumer ids
  , _appProducerId :: ProducerId                      -- an incremental counter to assign unique producer ids
  , _appRequestId :: ReqId                            -- an incremental counter to assign unique request ids for all commands
  , _appWorkers :: [Worker]                           -- a list of workers for consumers and producers that run in the background
  , _appResponse :: [(ReqId, MVar Response)]          -- a list of registered requests that need a Request Id
  , _appSendReceipts :: [(ProducerId, ProducerSeqs)]  -- a list of registered messages sent by a specific producer
  }
$(makeLenses ''AppState)

mkConsumerId :: MonadIO m => Chan Response -> IORef AppState -> m ConsumerId
mkConsumerId chan ref = liftIO $ atomicModifyIORef ref $ \app ->
  let cid = app ^. appConsumerId
      f   = over appConsumers ((cid, chan) :) app
  in  (over appConsumerId (+ 1) f, cid)

mkProducerId :: MonadIO m => IORef AppState -> m ProducerId
mkProducerId ref = liftIO $ atomicModifyIORef ref $ \app ->
  let pid = app ^. appProducerId
      f   = over appSendReceipts ((pid, []) :) app
  in  (over appProducerId (+ 1) f, pid)

mkRequestId :: MonadIO m => IORef AppState -> m (ReqId, MVar Response)
mkRequestId ref = liftIO $ do
  var <- newEmptyMVar
  atomicModifyIORef ref $ \app ->
    let req = app ^. appRequestId
        f   = over appResponse ((req, var) :) app
    in  (over appRequestId (+ 1) f, (req, var))

addWorker :: MonadIO m => IORef AppState -> (Async (), MVar ()) -> m ()
addWorker ref nw =
  liftIO $ atomicModifyIORef ref $ \app -> (over appWorkers (nw :) app, ())

{- | Register a response for a request and unregister request. -}
registerReqResponse :: MonadIO m => IORef AppState -> ReqId -> Response -> m ()
registerReqResponse ref rid resp = liftIO $ do
  maybeVar <- atomicModifyIORef ref
    $ \app -> (over appResponse h app, lookup rid $ app ^. appResponse)
  traverse_ (`putMVar` resp) maybeVar
  where h = filter ((rid /=) . fst) -- unregister request

getProducerSeqs pid xs = filter (\(p, _) -> pid == p) xs >>= snd

updateProducerSeqs pid g xs =
  (\(p, ys) -> if p == pid then (p, g) else (p, ys)) <$> xs

registerSeqId
  :: MonadIO m => IORef AppState -> ProducerId -> SeqId -> m (MVar Response)
registerSeqId ref pid sid = liftIO $ do
  var <- newEmptyMVar
  atomicModifyIORef ref $ \app ->
    let xs = app ^. appSendReceipts
        g  = (sid, var) : getProducerSeqs pid xs
        h  = updateProducerSeqs pid g xs
    in  (set appSendReceipts h app, var)

{- | Register a response for a message sent and unregister sequence id. -}
registerSendReceipt
  :: MonadIO m => IORef AppState -> ProducerId -> SeqId -> Response -> m ()
registerSendReceipt ref pid sid resp = liftIO $ do
  maybeVar <- atomicModifyIORef ref
    $ \app -> (over appSendReceipts h app, g $ app ^. appSendReceipts)
  traverse_ (`putMVar` resp) maybeVar
 where
  h xs =
    let f = filter (\(s, _) -> s /= sid) $ getProducerSeqs pid xs
    in  updateProducerSeqs pid f xs
  g = lookup sid . getProducerSeqs pid