重试以可变引用作为参数的异步函数

问题描述 投票:0回答:1

我有一个函数

fetch_stuff
,它需要对 sqlx 连接的可变引用。如果这个函数失败,我想重试N次。我需要对许多类似的函数进行这种重试行为,因此我尝试制作一个通用的
retry_up_to_n_times
函数,它采用异步 fnmut 和允许的重试次数作为输入。

#[derive(Clone)]
pub struct App {
    db_pool: sqlx::PgPool,
}

impl App {
    pub async fn foo(&self) {
        let mut db_conn = self.db_pool.acquire().await.unwrap();
        retry_up_to_n_times(|| fetch_stuff(&mut db_conn), 3).await;
    }
}

pub async fn fetch_stuff(db_conn: &mut sqlx::postgres::PgConnection) -> Result<(), StorageError> {
    //do stuff with db_conn
    Ok(())
}


pub async fn retry_up_to_n_times<F, T, O, E>(mut func: F, max_tries: usize) -> T::Output
where
    F: FnMut() -> T,
    T: std::future::Future<Output = Result<O, E>>,
    E: Error
{
    let mut fail_count = 0;
    loop {
        match func().await {
            Ok(t) => return Ok(t),
            Err(e) => {
                fail_count += 1;
                if fail_count >= max_tries {
                    return Err(e);
                }
            }
        }
    }
}

编译器给了我这个错误

error: captured variable cannot escape `FnMut` closure body
  --> src/app/tag.rs:32:32
   |
31 |         let mut db_conn = self.db_pool.acquire().await.unwrap();
   |             ----------- variable defined here
32 |         retry_up_to_n_times(|| fetch_stuff(&mut db_conn), 3).await;
   |                              - ^^^^^^^^^^^^^^^^^-------^
   |                              | |                |
   |                              | |                variable captured here
   |                              | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
   |                              inferred to be a `FnMut` closure
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

我对 Rust 没有太多经验,所以我不确定我是否完全理解它。我已经阅读了有关该主题的所有内容,并尝试使用 Rc 和 RefCell,但没有成功。 我也尝试过使用 tokio_retry,但最终遇到了同样的问题。

我想做的事情可能吗?

rust async-await borrow-checker rust-sqlx
1个回答
0
投票

最简单的方法是在闭包中获取池连接。无论如何,这可能就是您想要的;如果您正在使用的连接断开并导致错误,您应该在下次重试时获取另一个连接,否则由于连接断开,它注定会反复失败。

pub async fn foo(&self) {
    retry_up_to_n_times(
        || async {
            let mut db_conn = self.db_pool.acquire().await.unwrap();
            fetch_stuff(&mut db_conn).await
        },
        3,
    )
    .await;
}

(理想情况下,您会返回错误而不是

unwrap()
ping 它,但我会将其作为练习留给您。)

为了解释为什么不允许这样做,

FnMut
并不能阻止代码在第一个
Future
被删除之前第二次调用它。这将导致两个 future 实际上具有相同的值,这违反了 Rust 的别名规则。因此,
&mut
可能不会返回包含捕获值的独占借用的值。
一个简化的示例说明了 Rust 试图防止的问题:

FnMut

© www.soinside.com 2019 - 2024. All rights reserved.