use crate::error::{Error, Result};
use crate::instrumentation::{self, timing::MutationTimer};
use std::future::Future;
use std::time::Duration;
const MAX_ATTEMPTS: u32 = 12;
const BASE_DELAY_MS: u64 = 4;
pub async fn retry_on_database_tx_conflict<F, Fut, T>(
operation: &'static str,
mut f: F,
) -> Result<T>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T>>,
{
let timer = MutationTimer::start(operation);
let mut attempt: u32 = 0;
loop {
attempt += 1;
match f().await {
Ok(v) => {
timer.finish();
return Ok(v);
}
Err(e) => {
if e.is_retryable_transaction_contention() && attempt < MAX_ATTEMPTS {
let pow = attempt.min(8);
let ms = (BASE_DELAY_MS.saturating_mul(1u64 << pow)).min(400);
tokio::time::sleep(Duration::from_millis(ms)).await;
continue;
}
if operation == "Model::__assert_unique_field_value" {
if let Error::Validation(ref msg) = e {
if let Some((table, field)) = parse_unique_violation(msg) {
instrumentation::record_unique_violation(table, field);
return Err(e);
}
}
}
if e.is_retryable_transaction_contention() {
instrumentation::record_retry_error(operation, "unknown", &e.to_string());
}
return Err(e);
}
}
}
}
fn parse_unique_violation(msg: &str) -> Option<(&str, &str)> {
let rest = msg.strip_prefix("Unique constraint violation on ")?;
let (table, field) = rest.split_once('.')?;
Some((table, field))
}