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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! Retrying an operation that failed for a reason worth retrying.
//!
//! The crate has carried a complete retryability *classification* with no
//! *mechanism*: [`DataflowError::retryable`] sorts every variant, and
//! [`ErrorInfo`](crate::ErrorInfo) has `retry_attempted` / `retry_count` fields,
//! but no engine code path acts on any of it. Every host has written the same
//! loop.
//!
//! This module supplies the loop. It is deliberately **not** engine-level
//! automatic retry: the engine cannot know which handlers are idempotent — an
//! SMTP send that times out after `DATA` is indistinguishable from one that
//! succeeded, and retrying duplicates the mail. Whether to retry stays a
//! per-handler, per-call-site decision; the crate just supplies the correct
//! loop for those that opt in.
//!
//! # Not available on wasm32
//!
//! Backoff needs a timer, and tokio's time driver does not run on
//! `wasm32-unknown-unknown`. The whole module is `cfg`-gated off that target.
use crate;
use Future;
use Duration;
use Instant;
/// The longest a single backoff sleep may grow to, however many attempts have
/// failed. Without a ceiling, doubling reaches minutes and a caller waiting on
/// the result has no idea why.
const MAX_BACKOFF: Duration = from_secs;
/// How hard to retry, and for how long overall.
///
/// ```
/// use dataflow_rs::RetryPolicy;
/// use std::time::Duration;
///
/// // Three retries, 100ms doubling, but never more than 5s in total.
/// let policy = RetryPolicy {
/// max_retries: 3,
/// retry_delay_ms: 100,
/// deadline: Some(Duration::from_secs(5)),
/// };
/// assert_eq!(policy.max_retries, 3);
/// ```
/// Run `operation`, retrying while it fails retryably and budget remains.
///
/// Retries only when [`DataflowError::retryable`] says so — a validation error
/// fails once and returns immediately, because trying it again cannot help.
///
/// ```no_run
/// use dataflow_rs::{RetryPolicy, retry_with_policy, DataflowError, Result};
/// # async fn demo() -> Result<String> {
/// # async fn call_the_service() -> Result<String> { Ok(String::new()) }
/// let body = retry_with_policy(RetryPolicy::default(), "user_service", || async {
/// call_the_service().await
/// })
/// .await?;
/// # Ok(body)
/// # }
/// ```
///
/// # Deadline
///
/// The deadline covers the whole loop, sleeps included. A backoff that would
/// cross it is **skipped** rather than slept: sleeping only to fail afterwards
/// spends latency the caller is already waiting on. The loop then ends with the
/// last error.
///
/// Timing uses [`tokio::time::Instant`], so the deadline stays coherent with
/// the sleeps under `tokio::time::pause()`.
pub async
/// As [`retry_with_policy`], also reporting how many attempts were made.
///
/// The count is what fills [`ErrorInfo::retry_attempted`](crate::ErrorInfo) and
/// `retry_count` — the two fields the crate has always carried with nothing to
/// populate them:
///
/// ```no_run
/// use dataflow_rs::{ErrorInfo, RetryPolicy, retry_with_attempts};
/// # async fn demo() {
/// # async fn call() -> dataflow_rs::Result<()> { Ok(()) }
/// let (result, attempts) =
/// retry_with_attempts(RetryPolicy::default(), "svc", || async { call().await }).await;
///
/// if let Err(err) = result {
/// let mut info = ErrorInfo::simple_ref("SVC_FAILED", &err.to_string(), None);
/// info.retry_attempted = Some(attempts > 1);
/// info.retry_count = Some(attempts.saturating_sub(1));
/// }
/// # }
/// ```
pub async