Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.0.0] CircuitBreaker.state() returns the wrong state. #3565

Open
hoc081098 opened this issue Jan 18, 2025 · 2 comments
Open

[2.0.0] CircuitBreaker.state() returns the wrong state. #3565

hoc081098 opened this issue Jan 18, 2025 · 2 comments

Comments

@hoc081098
Copy link
Contributor

hoc081098 commented Jan 18, 2025

Version

2.0.0

Reproduce

I've copied the sample code from https://arrow-kt.io/learn/resilience/circuitbreaker/#arrows-circuitbreaker, and added 2 lines to print the state of the CircuitBreaker.

Github link: https://github.com/hoc081098/kotlin_playground/blob/master/src/main/kotlin/com/hoc081098/kotlin_playground/arrowkt/circuit_breaker.kt

package com.hoc081098.kotlin_playground.arrowkt

import arrow.core.Either
import arrow.resilience.CircuitBreaker
import kotlin.time.Duration.Companion.seconds
import kotlin.time.ExperimentalTime
import kotlinx.coroutines.delay

@ExperimentalTime
suspend fun main(): Unit {
  val circuitBreaker = CircuitBreaker(
    openingStrategy = CircuitBreaker.OpeningStrategy.Count(2),
    resetTimeout = 2.seconds,
    exponentialBackoffFactor = 1.2,
    maxResetTimeout = 60.seconds,
  )

  // normal operation
  circuitBreaker.protectOrThrow { "I am in Closed: ${circuitBreaker.state()}" }.also(::println)

  // simulate service getting overloaded
  Either.catch {
    circuitBreaker.protectOrThrow { throw RuntimeException("Service overloaded") }
  }.also(::println)
  Either.catch {
    circuitBreaker.protectOrThrow { throw RuntimeException("Service overloaded") }
  }.also(::println)
  circuitBreaker.protectEither { }
    .also { println("I am Open and short-circuit with ${it}. ${circuitBreaker.state()}") }

  println(">>> state should be Open: ${circuitBreaker.state()}")
  // simulate reset timeout
  println("Service recovering . . .").also { delay(2500) }
  println(">>> state should be HalfOpen: ${circuitBreaker.state()}")

  // simulate test request success
  circuitBreaker.protectOrThrow {
    "I am running test-request in HalfOpen: ${circuitBreaker.state()}"
  }.also(::println)
  println("I am back to normal state closed ${circuitBreaker.state()}")
}

Console

I am in Closed: arrow.resilience.CircuitBreaker$State$Closed@5ccd43c2
Either.Left(java.lang.RuntimeException: Service overloaded)
Either.Left(java.lang.RuntimeException: Service overloaded)
I am Open and short-circuit with Either.Right(kotlin.Unit). arrow.resilience.CircuitBreaker$State$Closed@27ddd392
>>> state should be Open: arrow.resilience.CircuitBreaker$State$Closed@27ddd392
Service recovering . . .
>>> state should be HalfOpen: arrow.resilience.CircuitBreaker$State$Closed@27ddd392
I am running test-request in HalfOpen: arrow.resilience.CircuitBreaker$State$Closed@27ddd392
I am back to normal state closed arrow.resilience.CircuitBreaker$State$Closed@27ddd392

Process finished with exit code 0

We can see the line >>> state should be Open: arrow.resilience.CircuitBreaker$State$Closed@27ddd392
I think that CircuitBreaker.state() returns the wrong state.

@hoc081098
Copy link
Contributor Author

I have just noticed that the example in docs seems to be wrong compared to the actual library source code.

Docs

🔀Closed
This is the state in which the circuit breaker starts.
Requests are made normally in this state:
When an exception occurs, it increments the failure counter.
When the failure counter reaches the given maxFailures threshold, the breaker moves to the Open state.
A successful request will reset the failure counter to zero.

Actual library source code

override fun shouldOpen(): Boolean = failuresCount > maxFailures

Should it be override fun shouldOpen(): Boolean = failuresCount >= maxFailures?

@hoc081098
Copy link
Contributor Author

hoc081098 commented Jan 18, 2025

circuitBreaker.protectEither { }
    .also { println("I am Open and short-circuit with ${it}. ${circuitBreaker.state()}") }

gives output

I am Open and short-circuit with Either.Right(kotlin.Unit). arrow.resilience.CircuitBreaker$State$Closed@71ebc7d4

That means the CircuitBreaker does not reject the execution of the lambda (Either.Right(kotlin.Unit))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant