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

com.github.norwae.circuit4stream.Tolerance.scala Maven / Gradle / Ivy

package com.github.norwae.circuit4stream

import java.time.Instant

import scala.concurrent.duration.FiniteDuration
import scala.util.Try

/**
  * decides if the circuit breaker should open after a result has
  * been received. A Tolerance implementation may provide an
  * "event log", which creates history between implementations
  * without depending on mutable state.
  *
  * The Tolerance implementation may inspect both successful
  * and failed elements - and has the ability to trigger on
  *
  * @tparam A type of elements produced
  */
trait Tolerance[-A] {
  /** Event log type. Usually some kind of collection.
    * The implementation of [[Tolerance.apply()]] should perform any housekeeping
    * required to avoid it growing without bound.
    */
  type EventLog

  /**
    * Initial value of the event log
    *
    * @return initial value
    */
  def initialLog: EventLog

  /**
    * Evaluates an event log to determine if the circuit should
    * open given this recent history.
    *
    * @param events event log
    * @param next   next event
    * @return pair: an updated event log for future invocations, boolean indicating if the circuit should open
    */
  def apply(events: EventLog, next: Try[A]): (EventLog, Boolean)
}

object Tolerance {

  def failureFraction(toleratedFraction: Double, per: FiniteDuration, minimumEvents: Int = 1) =
    new FailureFraction(toleratedFraction, per, minimumEvents)

  def failureFrequency(incidents: Int, per: FiniteDuration) =
    new FailureFrequency(incidents, per)

  /**
    * Tolerates failures (within a certain timeframe) up to a fraction of total
    * seen elements. The event log will create a single object for each event within
    * the evaluated time frame, but not retain a reference to the result. If there were fewer elements
    * in the timeframe than some defined minimum, the breaker will stay closed, in order to avoid an initial
    * failure triggering the breaker immediately
    *
    * This class is intentionally not declared as a case class to allow reuse
    * of this logic in special-case implementations
    * @param toleratedFraction fraction of elements that may fail. Inclusive
    * @param per               timeframe to evaluate
    * @param minimumEvents     minimum of events to evaluate the condition on
    */
  class FailureFraction(toleratedFraction: Double, per: FiniteDuration, minimumEvents: Int) extends Tolerance[Any] {
    require(toleratedFraction <= 1.0)

    final class Event(val time: Instant, val failed: Boolean)

    override type EventLog = Vector[Event]

    override def initialLog: EventLog = Vector.empty

    override def apply(events: EventLog, next: Try[Any]): (EventLog, Boolean) = {
      val cutoff = Instant.now().minusMillis(per.toMillis)
      val relevantEvents = events.filter(_.time isAfter cutoff) :+ new Event(Instant.now(), next.isFailure)

      if (relevantEvents.length < minimumEvents) {
        (relevantEvents, false)
      } else {
        val bad = relevantEvents.count(_.failed)

        val failedFraction = bad.toDouble / relevantEvents.length

        (relevantEvents, failedFraction > toleratedFraction)
      }
    }
  }

  /**
    * Tolerates up to a total number of events for a given timeframe. The
    * event log will create an object for each failure encountered during the
    * timeframe.
    *
    * This class is intentionally not declared as a case class to allow reuse
    * of this logic in special-case implementation.
    *
    * @param incidents nr of incidents after which the breaker should open
    * @param per duration to evaluate
    */
  class FailureFrequency(incidents: Int, per: FiniteDuration) extends Tolerance[Any] {
    type EventLog = Vector[Instant]

    override def initialLog: EventLog = Vector.empty

    override def apply(events: EventLog, next: Try[Any]): (EventLog, Boolean) = {
      val cutoff = Instant.now().minusMillis(per.toMillis)
      val relevantEvents = events.filter(_.isAfter(cutoff))
      val nextLog =
        if (next.isFailure) relevantEvents :+ Instant.now()
        else relevantEvents

      (nextLog, nextLog.length >= incidents)
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy