use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
struct FlakyListener {
inner: TcpListener,
remaining_failures: AtomicUsize,
attempts: Mutex<Vec<tokio::time::Instant>>,
}
impl TcpAccept for FlakyListener {
async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)> {
self.attempts
.lock()
.unwrap()
.push(tokio::time::Instant::now());
if self.remaining_failures.fetch_sub(1, Ordering::SeqCst) > 0 {
Err(std::io::Error::other("simulated accept error"))
} else {
TcpAccept::accept(&self.inner).await
}
}
}
#[tokio::test(start_paused = true)]
async fn accept_loop_backs_off_between_repeated_errors_instead_of_busy_spinning() {
let inner = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let addr = inner.local_addr().unwrap();
let flaky = FlakyListener {
inner,
remaining_failures: AtomicUsize::new(5),
attempts: Mutex::new(Vec::new()),
};
tokio::spawn(async move {
let _ = TcpStream::connect(addr).await;
});
let semaphore = Arc::new(Semaphore::new(1));
let mut backoff = ACCEPT_BACKOFF_INITIAL;
let result = accept_and_permit(&flaky, &mut backoff, &semaphore).await;
assert!(
result.is_some(),
"accept should eventually succeed once the flaky listener stops failing"
);
let recorded = flaky.attempts.lock().unwrap();
assert_eq!(recorded.len(), 6, "5 failures then 1 success");
let expected_gaps = [
ACCEPT_BACKOFF_INITIAL,
ACCEPT_BACKOFF_INITIAL * 2,
ACCEPT_BACKOFF_INITIAL * 4,
ACCEPT_BACKOFF_INITIAL * 8,
ACCEPT_BACKOFF_INITIAL * 16,
];
for (i, expected) in expected_gaps.iter().enumerate() {
let gap = recorded[i + 1] - recorded[i];
assert_eq!(
gap,
*expected,
"gap between attempt {i} and {} should reflect the backoff delay, not a busy spin",
i + 1
);
}
let mut capped = ACCEPT_BACKOFF_MAX;
capped = (capped * 2).min(ACCEPT_BACKOFF_MAX);
assert_eq!(capped, ACCEPT_BACKOFF_MAX);
}
#[tokio::test(start_paused = true)]
async fn a_successful_accept_resets_the_backoff() {
let inner = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let addr = inner.local_addr().unwrap();
let flaky = FlakyListener {
inner,
remaining_failures: AtomicUsize::new(3),
attempts: Mutex::new(Vec::new()),
};
tokio::spawn(async move {
let _ = TcpStream::connect(addr).await;
});
let semaphore = Arc::new(Semaphore::new(1));
let mut backoff = ACCEPT_BACKOFF_INITIAL * 32;
accept_and_permit(&flaky, &mut backoff, &semaphore).await;
assert_eq!(
backoff, ACCEPT_BACKOFF_INITIAL,
"the delay must return to its initial value once an accept succeeds"
);
}