packages feed

supernova-0.0.3: src/Pulsar/Producer.hs

{-# LANGUAGE FlexibleContexts #-}

{- |
Module      : Pulsar.Producer
Description : Apache Pulsar client
License     : Apache-2.0
Maintainer  : gabriel.volpe@chatroulette.com
Stability   : experimental

The basic producer interaction looks as follows: http://pulsar.apache.org/docs/en/develop-binary-protocol/#producer

>>> LOOKUP
<<< LOOKUP_RESPONSE
>>> PRODUCER
<<< SUCCESS
>>> SEND 1
>>> SEND 2
<<< SEND_RECEIPT 1
<<< SEND_RECEIPT 2

When the program finishes, either succesfully or due to a failure, we close the producer.

>>> CLOSE_PRODUCER
<<< SUCCESS
-}
module Pulsar.Producer where

import           Control.Concurrent.Async       ( async )
import           Control.Monad.Catch            ( bracket_ )
import           Control.Concurrent.MVar
import           Control.Monad.IO.Class         ( MonadIO
                                                , liftIO
                                                )
import           Control.Monad.Managed          ( managed_
                                                , runManaged
                                                )
import           Control.Monad.Reader           ( MonadReader
                                                , ask
                                                )
import           Data.IORef
import           Data.Text                      ( Text )
import           Pulsar.AppState
import qualified Pulsar.Core                   as C
import           Pulsar.Connection              ( PulsarCtx(..) )
import           Pulsar.Types

{- | An abstract 'Producer' able to 'send' messages of type 'PulsarMessage'. -}
newtype Producer m = Producer
  { send :: PulsarMessage -> m () -- ^ Produces a single message.
  }

data ProducerState = ProducerState
  { stSeqId :: SeqId -- an incremental message sequence counter
  , stName :: Text   -- a unique name
  }

mkSeqId :: MonadIO m => IORef ProducerState -> m SeqId
mkSeqId ref = liftIO $ atomicModifyIORef
  ref
  (\(ProducerState s n) -> let s' = s + 1 in (ProducerState s' n, s))

{- | Create a new 'Producer' by supplying a 'PulsarCtx' (returned by 'Pulsar.connect') and a 'Topic'. -}
newProducer
  :: (MonadIO m, MonadReader PulsarCtx m, MonadIO f) => Topic -> m (Producer f)
newProducer topic = do
  (Ctx conn app _) <- ask
  pid              <- mkProducerId app
  pname            <- liftIO $ mkProducer conn pid app
  pst              <- liftIO $ newIORef (ProducerState 0 pname)
  var              <- liftIO newEmptyMVar
  let release = newReq app >>= \(r, v) -> C.closeProducer conn v pid r
      handler = managed_ (bracket_ (pure ()) release) >> liftIO (readMVar var)
  worker <- liftIO $ async (runManaged handler)
  addWorker app (worker, var)
  return $ Producer (dispatch conn pid app pst)
 where
  newReq app = mkRequestId app
  dispatch conn pid app pst msg = do
    sid <- mkSeqId pst
    var <- registerSeqId app pid sid
    liftIO $ C.send conn var pid sid msg
  mkProducer conn pid app = do
    (req1, var1) <- newReq app
    C.lookup conn var1 req1 topic
    (req2, var2) <- newReq app
    C.newProducer conn var2 req2 pid topic