All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itv.bucky.backend.fs2rabbit.AmqpClientConnectionManager.scala Maven / Gradle / Ivy

package com.itv.bucky.backend.fs2rabbit

import cats.effect._
import cats.effect.implicits._
import cats.effect.std.Dispatcher
import cats.implicits._
import com.itv.bucky.publish._
import com.itv.bucky.{AmqpClientConfig, Publisher, publish}
import com.rabbitmq.client.Channel
import com.typesafe.scalalogging.StrictLogging
import dev.profunktor.fs2rabbit.effects.MessageEncoder
import dev.profunktor.fs2rabbit.interpreter.RabbitClient
import dev.profunktor.fs2rabbit.model
import dev.profunktor.fs2rabbit.model.{AMQPChannel, PublishingFlag}

import scala.collection.immutable.TreeMap
import scala.util.Try

class AmqpClientConnectionManager[F[_]: Async](
    amqpConfig: AmqpClientConfig,
    pendingConfirmListener: PendingConfirmListener[F],
    dispatcher: Dispatcher[F],
    client: RabbitClient[F]
)(implicit publishCommandEncoder: MessageEncoder[F, PublishCommand], amqpChannel: AMQPChannel)
    extends StrictLogging {

  val publishChannel: Channel = amqpChannel.value

  private def synchroniseIfNeeded[T](f: => T): T = publishChannel.synchronized(f)
  private def runWithChannelSync[T](action: F[T]): F[T] =
    synchroniseIfNeeded {
      Async[F].fromTry(Try {
        dispatcher.unsafeRunSync(action)
      })
    }

  def addConfirmListeningToPublisher(publisher: Publisher[F, PublishCommand]): Publisher[F, PublishCommand] = (cmd: PublishCommand) =>
    for {
      deliveryTag <- Ref.of[F, Option[Long]](None)
      _ <- (for {
        signal <- Deferred[F, Option[Throwable]]
        _ <- runWithChannelSync {
          for {
            nextPublishSeq <- Async[F].blocking(publishChannel.getNextPublishSeqNo)
            _              <- deliveryTag.set(Some(nextPublishSeq))
            _              <- pendingConfirmListener.pendingConfirmations.update(_ + (nextPublishSeq -> signal))
            _              <- publisher(cmd)
          } yield ()
        }
        _ <- signal.get.flatMap(maybeError => maybeError.traverse(Async[F].raiseError[Unit]))
      } yield ())
        .timeout(amqpConfig.publishingTimeout)
        .recoverWith { case e =>
          runWithChannelSync {
            for {
              dl          <- deliveryTag.get
              deliveryTag <- Async[F].fromOption(dl, new RuntimeException("Timeout occurred before a delivery tag could be obtained.", e))
              _           <- pendingConfirmListener.pop(deliveryTag, multiple = false)
              _           <- Async[F].raiseError[Unit](e)
            } yield ()
          }
        }
    } yield ()

}

private[bucky] object AmqpClientConnectionManager extends StrictLogging {

  def apply[F[_]](
      config: AmqpClientConfig,
      client: RabbitClient[F],
      amqpChannel: AMQPChannel,
      dispatcher: Dispatcher[F]
  )(implicit
      F: Async[F],
      publishCommandEncoder: MessageEncoder[F, PublishCommand]
  ): F[AmqpClientConnectionManager[F]] =
    for {
      pendingConfirmations <- Ref.of[F, TreeMap[Long, Deferred[F, Option[Throwable]]]](TreeMap.empty)
      pendingReturn        <- Ref.of[F, Option[Throwable]](None)
      publishChannel = amqpChannel.value
      _               <- Async[F].blocking(publishChannel.confirmSelect)
      confirmListener <- Async[F].blocking(PendingConfirmListener(pendingConfirmations, pendingReturn, dispatcher))
      _               <- Async[F].blocking(publishChannel.addConfirmListener(confirmListener))
      _               <- Async[F].blocking(publishChannel.addReturnListener(confirmListener))
    } yield new AmqpClientConnectionManager(config, confirmListener, dispatcher, client)(implicitly, implicitly, amqpChannel)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy