-
Notifications
You must be signed in to change notification settings - Fork 0
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
321 update the circuit breaker retries logic #336
base: main
Are you sure you want to change the base?
Conversation
Still adjusting my test. Should be done by the end of the day |
…e-reliability-features-eg-simulated-network-failures-retries-e3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of this ticket was to adjust the retry logic for the futures
. The future
should initially fail once before the specified number of retries is executed. Currently, the implementation only retries the future the specified number of times, without accounting for the initial failure. For example, if the retry count is set to 3, the future is retried only 3 times instead of the expected 4.
I should look first where the number of retries is checked.
pub fn call<F, Fut, T, E>(&self, mut f: F) -> impl Future<Output = Result<T, Error<E>>> | ||
where | ||
F: FnMut() -> Fut, | ||
Fut: Future<Output = Result<T, E>>, | ||
{ | ||
ResultFuture { | ||
factory: f, | ||
state: State::Initial, | ||
breaker: self.clone(), | ||
let breaker = self.clone(); | ||
|
||
async move { | ||
if !breaker.should_allow_call() { | ||
return Err(Error::CircuitOpen); | ||
} | ||
|
||
let mut attempts = 0; | ||
|
||
loop { | ||
attempts += 1; | ||
let result = f().await; | ||
|
||
match result { | ||
Ok(val) => { | ||
breaker.success(); | ||
return Ok(val); | ||
} | ||
Err(err) => { | ||
breaker.failure(); | ||
|
||
if attempts >= breaker.inner.lock().max_retries { | ||
return Err(Error::Inner(err)); | ||
} | ||
|
||
if !breaker.should_allow_call() { | ||
return Err(Error::CircuitOpen); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't change the implementation logic. That is not the aim of the ticket.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay
let attempts_clone = attempts.clone(); | ||
|
||
let retry_operation = || { | ||
let attempts = attempts_clone.clone(); | ||
async move { | ||
attempts.fetch_add(1, Ordering::AcqRel); | ||
Err::<(), ()>(()) // Simulate a failure | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to change anything here.
// #[tokio::test] | ||
// async fn test_exponential_backoff() { | ||
// let breaker = CircuitBreaker::new() | ||
// .retries(3) | ||
// .exponential_backoff(Duration::from_millis(100)); | ||
|
||
// let start = Instant::now(); | ||
// let _ = breaker.call(|| async { Err::<(), ()>(()) }).await; | ||
// let elapsed = start.elapsed(); | ||
|
||
// Log the elapsed time for debugging | ||
// println!("Elapsed time: {:?}", elapsed); | ||
|
||
// After the first failure, we wait 100ms before retrying | ||
// After the second failure, we wait 200ms before retrying | ||
// After the third failure, we should not retry anymore, and the circuit should open | ||
// assert!(elapsed >= Duration::from_millis(290) && elapsed < Duration::from_millis(600)); // Widen range further | ||
// assert!(!breaker.should_allow_call()); // Circuit should be open after 3 retries | ||
// } | ||
|
||
// #[tokio::test] | ||
// async fn test_half_open_state_failure() { | ||
// let breaker = CircuitBreaker::new() | ||
// .retries(1) // Allow 1 retry | ||
// .reset_timeout(Duration::from_millis(100)); | ||
|
||
// Trigger a failure to open the circuit | ||
// let _ = breaker.call(|| async { Err::<(), ()>(()) }).await; | ||
// assert!(!breaker.should_allow_call()); // Circuit should be open | ||
|
||
// Wait for the reset timeout to allow the circuit to move to half-open state | ||
// sleep(Duration::from_millis(150)).await; | ||
|
||
// Verify the circuit is in the half-open state | ||
// assert!(breaker.should_allow_call()); | ||
// assert_eq!(breaker.inner.lock().state, CircuitState::HalfOpen); | ||
|
||
// Call again and trigger another failure | ||
// let result = breaker.call(|| async { Err::<(), ()>(()) }).await; | ||
|
||
// The circuit should open again after this failure | ||
// assert_eq!(result, Err(Error::CircuitOpen)); // Circuit should be open again after failure | ||
// assert_eq!(breaker.inner.lock().state, CircuitState::Open); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have you commented out the tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No don't worry about that, I have already un-commented the test. I was just testing out something.
I will do a new push with all your suggestions 👍
No description provided.