ydb 0.17.1

Crate contains generated low-level grpc code from YDB API protobuf, used as base for ydb crate
Documentation
use std::time::Duration;
use tracing::instrument;

const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(5);
const BACKOFF_RETRY_MAX_WAIT_DURATION: Duration = Duration::from_secs(10);
const BACKOFF_RETRY_MAX_WAIT_DURATION_MILLISECONDS: u64 =
    BACKOFF_RETRY_MAX_WAIT_DURATION.as_millis() as u64;

#[derive(Debug)]
pub(crate) struct RetryParams {
    pub(crate) attempt: usize,
    pub(crate) time_from_start: Duration,
}

// May be extended in the future
#[derive(Default, Debug)]
pub(crate) struct RetryDecision {
    pub(crate) allow_retry: bool,
    pub(crate) wait_timeout: Duration,
}

impl RetryDecision {
    /// Waits for decided duration and returns `true`
    /// if retry is allowed, otherwise immediately returns `false`.
    pub async fn wait(&self) -> bool {
        if self.allow_retry {
            tokio::time::sleep(self.wait_timeout).await;
        }

        self.allow_retry
    }
}

pub(crate) trait Retry: Send + Sync {
    fn retry_decision(&self, params: RetryParams) -> RetryDecision;
}

fn exponential_backoff_retry_wait_duration(attempt: usize) -> Duration {
    if attempt == 0 {
        return Duration::default();
    }

    let attempt = attempt.min(63);
    let duration_milliseconds = 2u64
        .pow(attempt as u32)
        .min(BACKOFF_RETRY_MAX_WAIT_DURATION_MILLISECONDS);

    Duration::from_millis(duration_milliseconds)
}

#[derive(Debug)]
pub(crate) struct TimeoutRetrier {
    pub(crate) timeout: Duration,
}

impl Default for TimeoutRetrier {
    fn default() -> Self {
        Self {
            timeout: DEFAULT_RETRY_TIMEOUT,
        }
    }
}

impl Retry for TimeoutRetrier {
    #[instrument(ret)]
    fn retry_decision(&self, params: RetryParams) -> RetryDecision {
        let mut res = RetryDecision::default();
        if params.time_from_start < self.timeout {
            res.wait_timeout = exponential_backoff_retry_wait_duration(params.attempt);
            res.allow_retry = (params.time_from_start + res.wait_timeout) < self.timeout;
        };

        res
    }
}

pub(crate) struct IndefiniteRetrier;

impl Retry for IndefiniteRetrier {
    #[instrument(skip_all)]
    fn retry_decision(&self, params: RetryParams) -> RetryDecision {
        RetryDecision {
            allow_retry: true,
            wait_timeout: exponential_backoff_retry_wait_duration(params.attempt),
        }
    }
}

pub(crate) struct NoRetrier {}

impl Retry for NoRetrier {
    fn retry_decision(&self, _params: RetryParams) -> RetryDecision {
        RetryDecision {
            allow_retry: false,
            wait_timeout: Duration::ZERO,
        }
    }
}