1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*******************************************************************************
*
* Copyright (c) 2025 - 2026 Haixing Hu.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0.
*
******************************************************************************/
//! Mutable state shared by retry execution loops.
use std::time::{
Duration,
Instant,
};
use crate::event::RetryContextParts;
use crate::options::EffectiveAttemptTimeout;
use crate::{
AttemptFailure,
RetryContext,
RetryError,
RetryOptions,
};
/// Mutable retry-flow state shared by sync, async, and worker execution loops.
pub(in crate::executor) struct RetryFlowState<E> {
/// Monotonic instant when the retry flow started.
started_at: Instant,
/// Cumulative user operation time consumed by attempts.
operation_elapsed: Duration,
/// Attempts executed or currently being prepared.
attempts: u32,
/// Last failure retained for elapsed-budget errors raised before another attempt.
last_failure: Option<AttemptFailure<E>>,
}
impl<E> RetryFlowState<E> {
/// Creates an empty retry-flow state.
///
/// # Returns
/// A state with zero attempts and no accumulated operation elapsed time.
pub(in crate::executor) fn new() -> Self {
Self {
started_at: Instant::now(),
operation_elapsed: Duration::ZERO,
attempts: 0,
last_failure: None,
}
}
/// Returns total monotonic retry-flow elapsed time.
///
/// # Returns
/// Elapsed time since this retry flow started.
#[inline]
pub(in crate::executor) fn total_elapsed(&self) -> Duration {
self.started_at.elapsed()
}
/// Returns cumulative user operation time consumed by attempts.
///
/// # Returns
/// Accumulated user operation duration.
#[inline]
pub(in crate::executor) fn operation_elapsed(&self) -> Duration {
self.operation_elapsed
}
/// Returns attempts executed or currently being prepared.
///
/// # Returns
/// One-based attempt count for attempt contexts, or zero before attempts.
#[inline]
pub(in crate::executor) fn attempts(&self) -> u32 {
self.attempts
}
/// Marks the next attempt as started.
///
/// # Returns
/// The one-based attempt number after incrementing.
#[inline]
pub(in crate::executor) fn start_next_attempt(&mut self) -> u32 {
self.attempts += 1;
self.attempts
}
/// Adds elapsed user operation time.
///
/// # Parameters
/// - `attempt_elapsed`: Duration consumed by the latest attempt.
#[inline]
pub(in crate::executor) fn add_operation_elapsed(&mut self, attempt_elapsed: Duration) {
self.operation_elapsed = self.operation_elapsed.saturating_add(attempt_elapsed);
}
/// Builds a context snapshot from this retry-flow state.
///
/// # Parameters
/// - `options`: Retry options that define configured limits.
/// - `attempt_elapsed`: Elapsed time in the current attempt.
/// - `attempt_timeout`: Effective timeout configured for the current attempt.
///
/// # Returns
/// A retry context using this state's latest values.
pub(in crate::executor) fn context(
&self,
options: &RetryOptions,
attempt_elapsed: Duration,
attempt_timeout: EffectiveAttemptTimeout,
) -> RetryContext {
RetryContext::from_parts(RetryContextParts {
attempt: self.attempts,
max_attempts: options.max_attempts(),
max_operation_elapsed: options.max_operation_elapsed(),
max_total_elapsed: options.max_total_elapsed(),
operation_elapsed: self.operation_elapsed,
total_elapsed: self.total_elapsed(),
attempt_elapsed,
attempt_timeout: attempt_timeout.duration(),
})
.with_attempt_timeout_source(attempt_timeout.source())
}
/// Takes an elapsed-budget terminal error when a budget is exhausted.
///
/// # Parameters
/// - `options`: Retry options that define elapsed budgets.
/// - `attempt_timeout`: Effective timeout visible in the terminal context.
///
/// # Returns
/// `Some(RetryError)` when the max-operation-elapsed or max-total-elapsed
/// budget is exhausted; otherwise `None`. The retained last failure is moved
/// into the error when present.
pub(in crate::executor) fn take_elapsed_error(
&mut self,
options: &RetryOptions,
attempt_timeout: EffectiveAttemptTimeout,
) -> Option<RetryError<E>> {
let total_elapsed = self.total_elapsed();
let reason = options.elapsed_error_reason(self.operation_elapsed, total_elapsed)?;
Some(RetryError::new(
reason,
self.take_last_failure(),
RetryContext::from_parts(RetryContextParts {
attempt: self.attempts,
max_attempts: options.max_attempts(),
max_operation_elapsed: options.max_operation_elapsed(),
max_total_elapsed: options.max_total_elapsed(),
operation_elapsed: self.operation_elapsed,
total_elapsed,
attempt_elapsed: Duration::ZERO,
attempt_timeout: attempt_timeout.duration(),
})
.with_attempt_timeout_source(attempt_timeout.source()),
))
}
/// Stores the last failure observed before a retry sleep.
///
/// # Parameters
/// - `failure`: Failure from the latest attempt.
#[inline]
pub(in crate::executor) fn record_last_failure(&mut self, failure: AttemptFailure<E>) {
self.last_failure = Some(failure);
}
/// Takes the retained last failure.
///
/// # Returns
/// The retained last failure, if one exists.
#[inline]
pub(in crate::executor) fn take_last_failure(&mut self) -> Option<AttemptFailure<E>> {
self.last_failure.take()
}
}