#![forbid(unsafe_code)]
use std::time::Duration;
use nix::errno::Errno;
use retry::{delay::Exponential, retry, OperationResult};
use crate::config::{
EAGAIN_BACKOFF_FACTOR, EAGAIN_INITIAL_DELAY, EAGAIN_MAX_DELAY, EAGAIN_MAX_RETRY,
};
pub fn retry_on_intr<F, T>(mut f: F) -> Result<T, Errno>
where
F: FnMut() -> Result<T, Errno>,
{
let strategy =
Exponential::from_millis_with_factor(EAGAIN_INITIAL_DELAY, EAGAIN_BACKOFF_FACTOR)
.map(|d| Duration::from_millis(EAGAIN_MAX_DELAY).min(d))
.take(EAGAIN_MAX_RETRY);
retry(strategy, || match retry_on_eintr(&mut f) {
Ok(v) => OperationResult::Ok(v),
Err(Errno::EAGAIN) => OperationResult::Retry(Errno::EAGAIN),
Err(errno) => OperationResult::Err(errno),
})
.map_err(|e| e.error)
}
pub fn retry_on_eintr<F, T>(mut f: F) -> Result<T, Errno>
where
F: FnMut() -> Result<T, Errno>,
{
loop {
match f() {
Err(Errno::EINTR) => continue,
result => return result,
}
}
}
pub fn retry_on_eagain<F, T>(mut f: F) -> Result<T, Errno>
where
F: FnMut() -> Result<T, Errno>,
{
let strategy =
Exponential::from_millis_with_factor(EAGAIN_INITIAL_DELAY, EAGAIN_BACKOFF_FACTOR)
.map(|d| Duration::from_millis(EAGAIN_MAX_DELAY).min(d))
.take(EAGAIN_MAX_RETRY);
retry(strategy, || match f() {
Ok(v) => OperationResult::Ok(v),
Err(Errno::EAGAIN) => OperationResult::Retry(Errno::EAGAIN),
Err(errno) => OperationResult::Err(errno),
})
.map_err(|e| e.error)
}
#[macro_export]
macro_rules! rwrite {
($dst:expr, $($arg:tt)*) => {{
$crate::retry::retry_on_intr(|| {
$dst.write_fmt(format_args!($($arg)*))
.map_err(|err| $crate::err::err2no(&err))
})
}};
}
#[macro_export]
macro_rules! rwriteln {
($dst:expr $(, $($arg:tt)*)?) => {{
$crate::retry::retry_on_intr(|| {
let () = $dst
.write_fmt(format_args!($($($arg)*)?))
.map_err(|err| $crate::err::err2no(&err))?;
$dst
.write_all(b"\n")
.map_err(|err| $crate::err::err2no(&err))
})
}};
}
#[cfg(test)]
mod tests {
use std::time::Instant;
use super::*;
#[test]
fn test_retry_1() {
let start = Instant::now();
let mut attempts = 3;
let result = retry_on_intr(move || {
if attempts > 0 {
attempts -= 1;
Err(Errno::EAGAIN) } else {
Ok(42) }
});
assert_eq!(result, Ok(42));
let elapsed = start.elapsed();
let expected_duration = Duration::from_millis(EAGAIN_INITIAL_DELAY as u64 * 7);
assert!(
elapsed >= expected_duration,
"Expected delay due to exponential backoff"
);
}
#[test]
fn test_retry_2() {
let start = Instant::now();
let mut attempts = EAGAIN_MAX_RETRY;
let result = retry_on_intr(move || {
if attempts > 0 {
attempts -= 1;
Err(Errno::EAGAIN) } else {
Ok(42) }
});
assert_eq!(result, Ok(42));
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(EAGAIN_MAX_DELAY as u64),
"Expected delay to exceed max backoff duration"
);
}
#[test]
fn test_retry_3() {
let start = Instant::now();
let result: Result<(), Errno> = retry_on_intr(|| Err(Errno::EINVAL));
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(10),
"Expected immediate termination without delay"
);
assert_eq!(result, Err(Errno::EINVAL));
}
#[test]
fn test_retry_4() {
let result = retry_on_eintr(|| Ok(42));
assert_eq!(result, Ok(42));
}
#[test]
fn test_retry_5() {
let mut attempts = 0;
let result = retry_on_eintr(|| {
attempts += 1;
if attempts < 5 {
Err(Errno::EINTR)
} else {
Ok(99)
}
});
assert_eq!(result, Ok(99));
assert_eq!(attempts, 5);
}
#[test]
fn test_retry_6() {
let mut attempts = 0;
let result: Result<(), Errno> = retry_on_eintr(|| {
attempts += 1;
Err(Errno::EPERM)
});
assert_eq!(result, Err(Errno::EPERM));
assert_eq!(attempts, 1);
}
#[test]
fn test_retry_7() {
let mut attempts = 0;
let result: Result<(), Errno> = retry_on_eintr(|| {
attempts += 1;
if attempts < 3 {
Err(Errno::EINTR)
} else {
Err(Errno::ENOENT)
}
});
assert_eq!(result, Err(Errno::ENOENT));
assert_eq!(attempts, 3);
}
#[test]
fn test_retry_8() {
let result = retry_on_eagain(|| Ok(42));
assert_eq!(result, Ok(42));
}
#[test]
fn test_retry_9() {
let mut attempts = 0;
let result = retry_on_eagain(move || {
attempts += 1;
if attempts < 3 {
Err(Errno::EAGAIN)
} else {
Ok(77)
}
});
assert_eq!(result, Ok(77));
}
#[test]
fn test_retry_10() {
let mut attempts = 0;
let result: Result<(), Errno> = retry_on_eagain(|| {
attempts += 1;
Err(Errno::EACCES)
});
assert_eq!(result, Err(Errno::EACCES));
assert_eq!(attempts, 1);
}
#[test]
fn test_retry_11() {
let result: Result<(), Errno> = retry_on_eagain(|| Err(Errno::EAGAIN));
assert_eq!(result, Err(Errno::EAGAIN));
}
#[test]
fn test_retry_12() {
let result = retry_on_intr(|| Ok(42));
assert_eq!(result, Ok(42));
}
#[test]
fn test_retry_13() {
let mut attempts = 0;
let result = retry_on_intr(move || {
attempts += 1;
if attempts < 3 {
Err(Errno::EINTR)
} else {
Ok(55)
}
});
assert_eq!(result, Ok(55));
}
#[test]
fn test_retry_14() {
let result: Result<(), Errno> = retry_on_intr(|| Err(Errno::ENOENT));
assert_eq!(result, Err(Errno::ENOENT));
}
}