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

com.github.takezoe.retry.CircuitBreakerPolicy.scala Maven / Gradle / Ivy

package com.github.takezoe.retry

import scala.concurrent.duration._
import java.util.concurrent.atomic.LongAdder
import java.util.concurrent.atomic.AtomicReference
import CircuitBreakerPolicy._

case class CircuitBreakerContext(
  state: State = Close,
  failureCount: Int = 0,
  successCount: Int = 0,
  lastFailure: Option[FailureInfo] = None
)

case class FailureInfo(
    timestampMillis: Long,
    exception: Throwable
)

class CircuitBreakerPolicy(
    val failureThreshold: Int,
    val successThreshold: Int,
    val retryDuration: FiniteDuration,
    val onClose: (CircuitBreakerContext) => Unit = _ => (),
    val onOpen: (CircuitBreakerContext) => Unit = _ => (),
    val onHalfOpen: (CircuitBreakerContext) => Unit = _ => ()
){
    private val context = new AtomicReference[CircuitBreakerContext](CircuitBreakerContext())

    def failed(e: Throwable): Unit = {
        // if failureThreashod <= 0 then the circuit breaker never closes.
        if (failureThreshold > 0) {
            val nextContext = context.get() match {
                case c @ CircuitBreakerContext(Close, failureCount, _, _) => 
                    if (failureCount + 1 >= failureThreshold) {
                        val nextContext = c.copy(state = Open, failureCount = failureCount + 1, successCount = 0, lastFailure = Some(FailureInfo(System.currentTimeMillis(), e)))
                        onOpen(nextContext)
                        nextContext
                    } else {
                        val nextContext = c.copy(failureCount = failureCount + 1)
                        nextContext
                    }
                case c @ CircuitBreakerContext(Open, failureCount, _, _) =>
                    val nextContext = c.copy(failureCount = failureCount + 1, successCount = 0)
                    nextContext
                case c @ CircuitBreakerContext(HalfOpen, failureCount, _, _) => 
                    val nextContext = c.copy(state = Open, failureCount = failureCount + 1, successCount = 0, lastFailure = Some(FailureInfo(System.currentTimeMillis(), e)))
                    onOpen(nextContext)
                    nextContext
            }
            context.set(nextContext)
        } 
    }

    def succeeded(): Unit = {
        // if failureThreashod <= 0 then the circuit breaker never closes.
        if (failureThreshold > 0) {
            val nextContext = context.get() match {
                case c @ CircuitBreakerContext(Close, _, successCount, _) =>
                  val nextContext = c.copy(failureCount = 0, successCount = successCount + 1)
                  nextContext
                case c @ CircuitBreakerContext(Open, _, successCount, _) =>
                    if (successCount + 1 >= successThreshold) {
                        val nextContext = c.copy(state = Close, failureCount = 0, successCount = successCount + 1, lastFailure = None)
                        onClose(nextContext)
                        nextContext
                    } else {
                        val nextContext = c.copy(state = HalfOpen, failureCount = 0, successCount = successCount + 1, lastFailure = None)
                        onHalfOpen(nextContext)
                        nextContext
                    }
                case c @ CircuitBreakerContext(HalfOpen, _, successCount, _) =>
                    if (successCount + 1 >= successThreshold) {
                        val nextContext = c.copy(state = Close, failureCount = 0, successCount = successCount + 1, lastFailure = None)
                        onClose(nextContext)
                        nextContext
                    } else {
                        val nextContext = c.copy(failureCount = 0, successCount = successCount + 1)
                        nextContext
                    }
            }
            context.set(nextContext)
        }
    }

    def getContext(): CircuitBreakerContext = context.get()
}

object CircuitBreakerPolicy {
    sealed trait State
    case object Open extends State
    case object Close extends State
    case object HalfOpen extends State

    val NeverOpen = new CircuitBreakerPolicy(0, 0, 0.second)

    def apply(
        failureThreshold: Int, 
        successThreshold: Int, 
        retryDuration: FiniteDuration,
        onClose: (CircuitBreakerContext) => Unit = _ => (),
        onOpen: (CircuitBreakerContext) => Unit = _ => (),
        onHalfOpen: (CircuitBreakerContext) => Unit = _ => ()        
    ): CircuitBreakerPolicy = {
        new CircuitBreakerPolicy(
            failureThreshold = failureThreshold,
            successThreshold = successThreshold,
            retryDuration = retryDuration,
            onClose = onClose,
            onOpen = onOpen,
            onHalfOpen = onHalfOpen
        )
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy