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

akka.persistence.chaos.journal.ChaosJournalStorage.scala Maven / Gradle / Ivy

package akka.persistence.chaos.journal

import java.util.concurrent.{ThreadLocalRandom, TimeUnit}

import akka.actor.{ActorRef, Status}
import akka.persistence.PersistentRepr
import akka.persistence.inmemory.JournalEntry
import akka.persistence.inmemory.extension.InMemoryJournalStorage
import akka.serialization.Serialization

import scala.concurrent.duration.FiniteDuration
import scala.util.control.NoStackTrace

class WriteFailedException(payloads: Seq[PersistentRepr])
    extends RuntimeException(s"write failed for payloads = [${ payloads.map(_.payload) }]") with NoStackTrace

class ReplayFailedException(payloads: Seq[PersistentRepr])
    extends RuntimeException(s"recovery failed after replaying payloads = [${ payloads.map(_.payload) }]") with NoStackTrace

class ReadHighestFailedException
    extends RuntimeException("recovery failed when reading highest sequence number") with NoStackTrace

class ChaosJournalStorage(serialization: Serialization) extends InMemoryJournalStorage(serialization) {

  private val config = context.system.settings.config.getConfig("akka.persistence.chaos.journal")
  private val writeFailureRate = config.getDouble("write-failure-rate")
  private val replayFailureRate = config.getDouble("replay-failure-rate")
  private val readHighestFailureRate = config.getDouble("read-highest-failure-rate")
  private val minWriteDelay = FiniteDuration(config.getDuration("min-write-delay", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
  private val maxWriteDelay = FiniteDuration(config.getDuration("max-write-delay", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)

  private def random = ThreadLocalRandom.current

  override def highestSequenceNr(ref: ActorRef, persistenceId: String, fromSequenceNr: Long): Unit =
    chaosly(
      ref,
      readHighestFailureRate,
      super.highestSequenceNr(ref, persistenceId, fromSequenceNr),
      new ReadHighestFailedException()
    )

  override def writelist(ref: ActorRef, xs: scala.collection.immutable.Seq[JournalEntry]): Unit =
    chaosly(ref, writeFailureRate, delayedWritelist(ref, xs), new WriteFailedException(xs.map(_.repr)))

  private def delayedWritelist(ref: ActorRef, xs: scala.collection.immutable.Seq[JournalEntry]): Unit = {
    import context.dispatcher
    val delay = randomDelay(minWriteDelay, maxWriteDelay)
    context.system.scheduler.scheduleOnce(delay, self, SuperWriteList(ref, xs))
  }

  private def randomDelay(min: FiniteDuration, max: FiniteDuration): FiniteDuration = {
    if(min == max) min
    else FiniteDuration(random.nextLong(min.toMillis, max.toMillis), TimeUnit.MILLISECONDS)
  }

  override def messages(ref: ActorRef, persistenceId: String, fromSequenceNr: Long, toSequenceNr: Long, max: Long, all: Boolean): Unit = {
    val wholeJournal = journal.filter(_._1 == persistenceId).flatMap(_._2).map(_.repr).toList
    chaosly(
      ref,
      replayFailureRate,
      super.messages(ref, persistenceId, fromSequenceNr, toSequenceNr, max, all),
      new ReplayFailedException(wholeJournal)
    )
  }

  private def chaosly(ref: ActorRef, rate: Double, success: => Unit, failure: => Exception): Unit = {
    if (shouldFail(rate)) {
      ref ! Status.Failure(failure)
    } else {
      success
    }
  }

  private def shouldFail(rate: Double): Boolean =
    random.nextDouble() < rate

  override def receive: Receive = super.receive orElse {
    case SuperWriteList(ref, xs) => super.writelist(ref, xs)
  }

  private case class SuperWriteList(ref: ActorRef, xs: scala.collection.immutable.Seq[JournalEntry])
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy