kafka-effectful-0.3.1.0: src/Kafka/Effectful/Producer/Interpreter.hs
-- The 'EffectHandler' type synonym in effectful-core expands to a
-- constraint that GHC's redundant-constraint check flags on the
-- handler's signature, even though the constraint is required for
-- 'interpret' to type-check. Suppress the warning at the file level.
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
module Kafka.Effectful.Producer.Interpreter
( -- * Interpreter
runKafkaProducer,
)
where
import Control.Concurrent.MVar qualified as Concurrent
import Data.Foldable (for_)
import Effectful (Eff, IOE, (:>))
import Effectful qualified
import Effectful.Dispatch.Dynamic (EffectHandler, interpret)
import Effectful.Error.Static (Error, throwError)
import Effectful.Exception qualified as Exception
import Kafka.Effectful.Producer.Effect (KafkaProducer (..))
import Kafka.Producer qualified as K
import Kafka.Producer.ProducerProperties (ProducerProperties)
import Kafka.Transaction qualified as K
import Kafka.Types (KafkaError)
-- | Run the 'KafkaProducer' effect.
--
-- Acquires a producer handle from the given properties and releases it
-- when the effect scope ends. Errors are thrown via the 'Error' effect.
runKafkaProducer ::
(IOE :> es, Error KafkaError :> es) =>
ProducerProperties ->
Eff (KafkaProducer : es) a ->
Eff es a
runKafkaProducer props action =
Exception.bracket
acquire
(Effectful.liftIO . K.closeProducer)
(\producer -> interpret (handleProducer producer) action)
where
acquire = do
result <- Effectful.liftIO $ K.newProducer props
case result of
Left err -> throwError err
Right producer -> pure producer
handleProducer ::
(IOE :> es, Error KafkaError :> es) =>
K.KafkaProducer ->
EffectHandler KafkaProducer es
handleProducer producer _env = \case
ProduceMessage record -> do
mbErr <- Effectful.liftIO $ K.produceMessage producer record
for_ mbErr throwError
ProduceMessage' record cb -> do
res <- Effectful.liftIO $ K.produceMessage' producer record cb
case res of
Left (K.ImmediateError err) -> throwError err
Right () -> pure ()
ProduceMessageBatch records -> Effectful.liftIO $ do
-- This is a per-record loop, not a batch send, and it saves no
-- network round-trips over calling produceMessage yourself. The
-- value here is the batch-shaped signature and the failed-record
-- result, not throughput.
--
-- hw-kafka-client exports no batch produce at all. It removed its
-- Haskell-level produceMessageBatch in 72e6f6d (Oct 2021, before
-- v5.3.0), and that function was itself a mapM over produceMessage.
-- Real batching would need a binding for librdkafka's
-- rd_kafka_produce_batch, which the package has never had.
--
-- Tracked as upstream issue 'hw-kafka-client-no-produce-batch-binding';
-- run `mori upstream-issues show hw-kafka-client-no-produce-batch-binding`.
results <- mapM (\r -> (r,) <$> K.produceMessage producer r) records
pure [(r, err) | (r, Just err) <- results]
ProduceMessageSync record -> do
var <- Effectful.liftIO Concurrent.newEmptyMVar
res <-
Effectful.liftIO $
K.produceMessage' producer record (Concurrent.putMVar var)
case res of
Left (K.ImmediateError err) -> throwError err
Right () -> do
Effectful.liftIO $ K.flushProducer producer
report <- Effectful.liftIO $ Concurrent.takeMVar var
case report of
K.DeliverySuccess _ offset -> pure offset
K.DeliveryFailure _ err -> throwError err
K.NoMessageError err -> throwError err
FlushProducer ->
Effectful.liftIO $ K.flushProducer producer
InitTransactions timeout ->
throwOnJust $ K.initTransactions producer timeout
BeginTransaction ->
throwOnJust $ K.beginTransaction producer
CommitTransaction timeout ->
Effectful.liftIO $ K.commitTransaction producer timeout
AbortTransaction timeout ->
throwOnJust $ K.abortTransaction producer timeout
SendOffsetsToTransaction consumer record timeout ->
Effectful.liftIO $
K.commitOffsetMessageTransaction producer consumer record timeout
AskProducerHandle ->
pure producer
where
throwOnJust action' = do
mbErr <- Effectful.liftIO action'
for_ mbErr throwError