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

akka.persistence.pg.query.scaladsl.BaseEventsPublisher.scala Maven / Gradle / Ivy

The newest version!
package akka.persistence.pg.query.scaladsl

import akka.actor.{ActorLogging, ActorRef}
import akka.persistence.JournalProtocol.{RecoverySuccess, ReplayMessagesFailure}
import akka.persistence.Persistence
import akka.persistence.pg.journal.PgAsyncWriteJournal._
import akka.persistence.query.EventEnvelope
import akka.stream.actor.ActorPublisher
import akka.stream.actor.ActorPublisherMessage.{Cancel, Request}

import scala.concurrent.duration.FiniteDuration

// FIXME needs a be rewritten as a GraphStage (since 2.5.0)
abstract class BaseEventsPublisher(
    fromOffset: Long,
    toOffset: Long,
    refreshInterval: FiniteDuration,
    maxBufSize: Int,
    writeJournalPluginId: String
) extends ActorPublisher[EventEnvelope]
    with DeliveryBuffer[EventEnvelope]
    with ActorLogging {

  private[akka] object Continue

  val tickTask = context.system.scheduler.schedule(refreshInterval, refreshInterval, self, Continue)(context.dispatcher)

  override def postStop(): Unit = {
    tickTask.cancel()
    ()
  }

  val journal: ActorRef = Persistence(context.system).journalFor(writeJournalPluginId)

  var currOffset = fromOffset

  var newEventsWhileReplaying = false

  def receive = init

  def init: Receive = {
    case request: Request =>
      log.debug(s"Received first request: $request")
      receiveInitialRequest()
    case Continue =>
    // skip, wait for first Request
    case Cancel =>
      val origSender = sender()
      log.debug(s"Received a 'Cancel' message from $origSender")
      context.stop(self)
  }

  def receiveInitialRequest(): Unit = {
    log.debug(s"received initial request -> subscribing to all events")
    subscribe()
    replay()
  }

  def subscribe(): Unit

  def replay(): Unit = {
    // reset flag
    newEventsWhileReplaying = false
    val limit = maxBufSize - buf.size
    log.debug("request replay from [{}] to [{}]", currOffset, toOffset)
    context become replaying
    requestReplayFromJournal(limit)
  }

  def requestReplayFromJournal(limit: Int): Unit

  def replaying: Receive = {

    case RecoverySuccess(highestRowId) =>
      log.debug(s"completed currOffset [$currOffset]")
      receiveRecoverySuccess(highestRowId)

    case ReplayMessagesFailure(cause) =>
      log.error(cause, s"replay failed due to [${cause.getMessage}]")
      deliverBuf()
      onErrorThenStop(cause)

    case request: Request =>
      log.debug(s"Received request: $request")
      deliverBuf()

    case Continue => // skip during replay

    case Cancel =>
      val origSender = sender()
      log.info(s"Streaming cancelled: $origSender")
      context.stop(self)

    case NewEventAppended =>
      // save row id so know if we can ask a replay as soon as possible
      log.debug(s"Received event notification while replaying")
      newEventsWhileReplaying = true
  }

  def idle: Receive = {
    case Continue | NewEventAppended => if (timeForReplay) replay()
    case _: Request                  => receiveIdleRequest()
    case Cancel                      => context.stop(self)
  }

  def timeForReplay: Boolean =
    (buf.isEmpty || buf.size <= maxBufSize / 2) && (currOffset <= toOffset)

  def receiveIdleRequest(): Unit = {
    deliverBuf()
    if (buf.isEmpty && currOffset > toOffset) {
      log.debug(s"stopping while idle: buffer is empty and $currOffset > $toOffset")
      onCompleteThenStop()
    }
  }

  def receiveRecoverySuccess(highestRowId: Long): Unit = {
    deliverBuf()
    if (buf.isEmpty && currOffset > toOffset) {
      log.debug(s"stopping after recovery: buffer is empty and $currOffset > $toOffset")
      onCompleteThenStop()
    } else {
      if (newEventsWhileReplaying || highestRowId > currOffset) replay()
      else context.become(idle)
    }
  }

  override def unhandled(message: Any): Unit =
    log.warning(s"Got unexpected message: $message")
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy