Say why the stable route proof failed #6
1 changed files with 139 additions and 6 deletions
|
|
@ -6730,10 +6730,23 @@ impl Engine {
|
|||
}
|
||||
|
||||
fn clear_fault(&self, transaction_id: &str) {
|
||||
self.fault_reports
|
||||
.lock()
|
||||
.expect("fault report mutex")
|
||||
.remove(transaction_id);
|
||||
let mut reports = self.fault_reports.lock().expect("fault report mutex");
|
||||
reports.remove(transaction_id);
|
||||
reports.remove(&route_proof_report_key(transaction_id));
|
||||
}
|
||||
|
||||
/// Say why the stable route's proof failed. The Routing phase retries the
|
||||
/// proof until its deadline, and the deadline's own cause names only the
|
||||
/// clock, so this line is where an operator learns what the listener
|
||||
/// actually did. Said once per distinct reason while the fault lasts.
|
||||
fn report_route_proof_failure(&self, transaction_id: &str, error: &anyhow::Error) {
|
||||
self.report_once(
|
||||
&route_proof_report_key(transaction_id),
|
||||
format!(
|
||||
"Idunn route proof for {transaction_id} failed: {}",
|
||||
route_proof_reason(error)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Write `detail` as the record's `last_error`, touching nothing else and
|
||||
|
|
@ -8871,11 +8884,13 @@ impl Engine {
|
|||
) {
|
||||
Ok(observation) => observation,
|
||||
Err(proof_error) if !rollback_allowed => {
|
||||
self.report_route_proof_failure(&admitted.value.transaction_id, &proof_error);
|
||||
return Err(proof_error).context(
|
||||
"incumbent authority was fenced; candidate route remains installed for fail-closed retry",
|
||||
);
|
||||
}
|
||||
Err(proof_error) => {
|
||||
self.report_route_proof_failure(&admitted.value.transaction_id, &proof_error);
|
||||
return match driver.rollback(
|
||||
expected,
|
||||
&activation.runtime_instance_id,
|
||||
|
|
@ -10560,8 +10575,15 @@ impl Engine {
|
|||
.iter()
|
||||
.any(|request| request.value.command_id == transaction.command_id);
|
||||
let cause = if now >= deadline.deadline_at_unix_millis {
|
||||
// The step's latest failure is the reason the phase never
|
||||
// finished, and the abort that ends it must not drop it.
|
||||
let last_attempt = transaction
|
||||
.last_error
|
||||
.as_deref()
|
||||
.map(|error| format!("; its last attempt failed: {error}"))
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
"the {:?} phase passed its deadline at unix ms {}",
|
||||
"the {:?} phase passed its deadline at unix ms {}{last_attempt}",
|
||||
transaction.phase, deadline.deadline_at_unix_millis
|
||||
)
|
||||
} else if requested {
|
||||
|
|
@ -11426,6 +11448,22 @@ fn runner_failure_journal_line(
|
|||
}
|
||||
}
|
||||
|
||||
fn route_proof_report_key(transaction_id: &str) -> String {
|
||||
format!("route-proof:{transaction_id}")
|
||||
}
|
||||
|
||||
/// The stable route proof's failure in words: whether the listener said
|
||||
/// nothing or answered wrongly, then the specific refusal (HTTP status line,
|
||||
/// presence not active, challenge or authority disagreement). The texts it
|
||||
/// carries name states and codes, never keys or signature bytes.
|
||||
fn route_proof_reason(error: &anyhow::Error) -> String {
|
||||
match error.downcast_ref::<ChallengeFailure>() {
|
||||
Some(ChallengeFailure::Silent(_)) => format!("silent, nothing answered: {error:#}"),
|
||||
Some(ChallengeFailure::Refused(_)) => format!("refused: {error:#}"),
|
||||
None => format!("{error:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn bounded_detail(value: &str) -> String {
|
||||
let value = value.replace(' | ||||