#指数退避 #重试 #退避 #tokio #异步

retry-if

用于装饰方法及函数以实现指数退避的tokio兼容属性宏

3个不稳定版本

0.2.2 2024年7月16日
0.2.1 2024年6月30日
0.2.0 2024年5月21日
0.1.0 2024年4月18日

异步 中排名第257

Download history 135/week @ 2024-05-16 41/week @ 2024-05-23 85/week @ 2024-05-30 132/week @ 2024-06-06 92/week @ 2024-06-13 95/week @ 2024-06-20 230/week @ 2024-06-27 77/week @ 2024-07-04 275/week @ 2024-07-11 94/week @ 2024-07-18 136/week @ 2024-07-25 110/week @ 2024-08-01 24/week @ 2024-08-08

每月下载402

MIT授权MIT

14KB

Retry-If

badgeLicense: MIT

基于谓词的retry装饰器,用于使用指数退避策略重试任意函数。

此库旨在使装饰代码使用重试策略尽可能简单,所需的所有内容只是一个定义的重试策略和一个确定代码输出是否需要重试的函数。

示例:在Err(...)上重试Result生产函数

以下示例设置了一个基本的重试配置,将最多重试五次,第一次等待1秒,然后2秒,4秒等。没有配置所有尝试的最大重试时间(t_wait_max),也没有配置每个退避的最大等待时间(backoff_max)。

use retry_if::{retry, ExponentialBackoffConfig};
use std::num::TryFromIntError;
use std::time::Duration;
use tokio::time::{pause, Instant};

const BACKOFF_CONFIG: ExponentialBackoffConfig = ExponentialBackoffConfig {
    max_retries: 5,
    t_wait: Duration::from_secs(1),
    backoff: 2.0,
    t_wait_max: None,
    backoff_max: None,
};

// this takes an address of the same type as the output of the decorated function.
//  It returns true if the function should be retried based on the result
fn retry_if(result: &Result<i64, TryFromIntError>) -> bool {
    result.is_err()
}

#[retry(BACKOFF_CONFIG, retry_if)]
async fn fallible_call() -> Result<i64, TryFromIntError> {
    // this will always produce a TryFromIntError, triggering a retry
    i64::try_from(i128::MAX)
}

#[tokio::main]
async fn main() {
    let start = Instant::now();

    let _ = fallible_call().await;

    let end = Instant::now();

    let elapsed = end - start;

    // expected waits are 1s, 2s, 4s, 8s, 16s = 31s
    assert!(elapsed > Duration::from_secs(31));
    assert!(elapsed < Duration::from_millis(31100));

    println!("Total test time: {elapsed:?}");
}

跟踪

该crate将tracing作为功能暴露出来,以启用使用tokio tracing库的tracing::info!进行日志记录,为每次重试尝试。这些目前的形式为:Sleeping {Duration:?} on attempt {i32}。输出跟踪将采用父函数提供的任何仪器范围。

限制

#[retry(...)]可以装饰几乎所有的异步函数。这包括

  • 自由函数,如async fn do_thing() -> i32
  • 实现块中的方法,例如:async fn fn do_thing(&mut self)
  • impl Trait for Struct 块中的特例实现,例如:async fn do_thing(&self) -> String

以下是一些无法使用 retry-if 的用例:

  • 接收并消耗 self 的函数,因为所有权已传递,且在第一次调用后 self 可能不再存在。
  • 依赖于在 Option 上使用 try (?) 操作符的函数。

示例:消耗 Self 的非工作函数

以下是一个非工作示例,其中 to_thing() 消耗 self,导致无法进行第二次调用。

struct Thing {}

struct Other {}

impl Thing {
    #[retry(...)]
    async fn to_thing(self) -> Other {
        self.into()
    }
}

示例:使用 Try 在 Option 上的非工作函数

以下是一个非工作示例,其中函数使用 try 操作符提前退出并返回一个 Option。这无法工作,因为代码主要是用 Result 扩展的,且在查看宏的 TokenStreams 时无法确定 ? 是否适用于 ResultOption,因此在解析时无法同时扩展两者。

struct Thing {}

struct Other {}

impl Thing {
    #[retry(...)]
    async fn do_thing(self) -> Option<i32> {
        // compilation fails here because this is expanded to match on `get_data()` and the match arms are Ok & Err
        //  instead of Some & None
        let data = get_data()?;
        data * 2
    }
}

贡献

如果您遇到边缘情况,有任何改进 API 的建议,或有任何可以澄清的地方,请与我们联系。

依赖关系

~2.6–9.5MB
~75K SLoC