use nodedb_cluster::calvin::{AttemptOutcome, CalvinCompletionRegistry, TxnId};
use crate::Error;
use crate::control::cluster::calvin::executor::ollp::error::OllpError;
use crate::control::cluster::calvin::executor::ollp::orchestrator::OllpOrchestrator;
use crate::control::planner::calvin::submit::RoutedAssignment;
pub struct DependentRetryArgs<'a, P, SF, RF> {
pub registry: &'a CalvinCompletionRegistry,
pub orchestrator: &'a OllpOrchestrator,
pub predicate_class_hash: u64,
pub timeout: std::time::Duration,
pub ollp_max_retries: u32,
pub initial_predicted: P,
pub submit: SF,
pub rescan: RF,
}
pub async fn run_dependent_with_retry<P, SF, SFut, RF, RFut>(
args: DependentRetryArgs<'_, P, SF, RF>,
) -> crate::Result<TxnId>
where
SF: FnMut(&P) -> SFut,
SFut: std::future::Future<Output = Result<RoutedAssignment, OllpError>>,
RF: FnMut() -> RFut,
RFut: std::future::Future<Output = crate::Result<P>>,
{
let DependentRetryArgs {
registry,
orchestrator,
predicate_class_hash,
timeout,
ollp_max_retries,
initial_predicted,
mut submit,
mut rescan,
} = args;
let mut predicted = initial_predicted;
let mut retry: u32 = 0;
loop {
let assignment = match submit(&predicted).await {
Ok(assignment) => assignment,
Err(_ollp_err) => {
if retry >= ollp_max_retries {
return Err(Error::OllpExhausted {
retries: ollp_max_retries.min(u8::MAX as u32) as u8,
});
}
orchestrator
.on_retry_required(predicate_class_hash, retry)
.await;
retry += 1;
continue;
}
};
let txn_id = TxnId::new(assignment.epoch, assignment.position);
let completion_rx = registry.register_completion(txn_id, assignment.participants);
let outcome = tokio::time::timeout(timeout, completion_rx)
.await
.map_err(|_| Error::Internal {
detail: "timed out waiting for Calvin completion".into(),
})?
.map_err(|_| Error::Internal {
detail: "Calvin completion channel closed".into(),
})?;
match outcome {
AttemptOutcome::Completed => return Ok(txn_id),
AttemptOutcome::Aborted => {
return Err(Error::CalvinSerializationConflict);
}
AttemptOutcome::Failed { detail } => {
return Err(Error::Internal {
detail: format!("calvin transaction routing failed: {detail}"),
});
}
AttemptOutcome::Mismatch => {
if retry >= ollp_max_retries {
return Err(Error::OllpExhausted {
retries: ollp_max_retries.min(u8::MAX as u32) as u8,
});
}
orchestrator
.on_retry_required(predicate_class_hash, retry)
.await;
retry += 1;
predicted = rescan().await?;
}
}
}
}
#[cfg(test)]
#[path = "retry_loop_tests.rs"]
mod tests;