chj_unix_util/
retry.rs

1use std::{
2    num::NonZeroU32,
3    sync::atomic::{AtomicBool, Ordering},
4    thread::sleep,
5    time::Duration,
6};
7
8use nix::sys::pthread::pthread_self;
9
10use crate::xorshift::Xorshift128plus;
11
12pub static DEBUG: AtomicBool = AtomicBool::new(false);
13
14/// Rerun `f` until it returns Ok, only sleeps after 100 attempts,
15/// panics after 200 attempts. Mostly useful for atomic
16/// `compare_exchange`. I.e. do not use if `f` expects a value from
17/// another thread; just do retries that should succeed except for bad
18/// luck when another thread is making a change at the same time.
19pub fn retry<R, E>(f: impl Fn() -> Result<R, E>) -> R {
20    let mut tries_left: u32 = 200;
21    let mut random = None;
22    loop {
23        if let Ok(r) = f() {
24            return r;
25        }
26        tries_left -= 1;
27        if tries_left == 0 {
28            panic!("can't seem to get this to succeed")
29        }
30        if tries_left < 100 {
31            if random.is_none() {
32                let tid = pthread_self() as u64;
33                random = Some(Xorshift128plus::new(tid));
34            }
35            let r = random.as_mut().expect("initialized above").get();
36            sleep(Duration::from_micros(r & 16383));
37            if true || DEBUG.load(Ordering::Relaxed) {
38                eprintln!(
39                    "note: retrying with {tries_left} tries left via `retry` at {}:{}",
40                    file!(),
41                    line!()
42                );
43            }
44        }
45    }
46}
47
48/// Rerun `f` until it returns Ok or `max_tries` has run out. Sleeps a
49/// constant time between tries.
50pub fn retry_n<R, E>(
51    max_tries: NonZeroU32,
52    sleep_ms: u64,
53    f: impl Fn() -> Result<R, E>,
54) -> Result<R, E> {
55    let mut tries_left: u32 = max_tries.into();
56    loop {
57        match f() {
58            Ok(r) => return Ok(r),
59            Err(e) => {
60                tries_left -= 1;
61                if tries_left == 0 {
62                    return Err(e);
63                }
64                sleep(Duration::from_millis(sleep_ms));
65            }
66        }
67        if DEBUG.load(Ordering::Relaxed) {
68            eprintln!("note: retrying via `retry_n` at {}:{}", file!(), line!());
69        }
70    }
71}