dex_sdk/retry_policy.rs
1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Sustainable Use License 1.0.
4// You may not use this file except in compliance with the License.
5// See the LICENSE file in the repository root.
6//
7// SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
8
9use std::time::Duration;
10
11#[derive(Clone, Debug, Default, PartialEq)]
12/// Overrides Dex retry timing for a Flow, `wait_for`, or `execute` operation.
13///
14/// Unset fields use server defaults. Intervals and total duration use [`Duration`]; maximum
15/// attempts counts the initial attempt. A zero maximum attempts value uses the server behavior.
16///
17/// Step retries default to a one-second initial interval, a multiplier of 2, a 100-second
18/// maximum interval, unlimited attempts, and four hours of total duration. Zero values select
19/// those defaults. A maximum of one disables retries. Non-retryable handler failures do not retry.
20///
21/// Changed in 1.35.0: a Step's total duration starts at its first hand-out to a Worker.
22/// It only gates scheduling another retry after failure; it never interrupts a running attempt.
23/// Method and heartbeat timeouts still apply to that attempt. Initial queue and lock waits do
24/// not consume the budget. Later backoff and queue waits do. A scheduled retry can start after
25/// the budget expires. Gracefully returning attempt one resets the budget's start only when no
26/// execution of that attempt may have run.
27///
28/// Use this policy for Step methods. The Server does not support whole-Flow retry. It rejects
29/// [`crate::StartFlowOptions::retry_policy`] with INVALID_ARGUMENT and rejects a SubFlow that
30/// sets [`crate::SubFlowOptions::retry_policy`] as invalid input. Use Step retry policies instead.
31/// Duration values must fit signed 32-bit whole seconds when submitted. Builders take ownership
32/// and return the updated policy; validation occurs when the options are mapped for a request.
33///
34/// # Examples
35///
36/// ```
37/// use dex_sdk::RetryPolicy;
38/// use std::time::Duration;
39///
40/// let retry = RetryPolicy::new()
41/// .initial_interval(Duration::from_secs(1))
42/// .backoff_coefficient(2.0)
43/// .maximum_interval(Duration::from_secs(30))
44/// .maximum_attempts(5);
45/// ```
46pub struct RetryPolicy {
47 pub(crate) initial_interval: Option<Duration>,
48 pub(crate) backoff_coefficient: Option<f64>,
49 pub(crate) maximum_interval: Option<Duration>,
50 pub(crate) maximum_attempts: Option<u32>,
51 pub(crate) total_duration: Option<Duration>,
52}
53
54impl RetryPolicy {
55 /// Creates an empty policy that preserves every server default.
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 /// Sets the delay before the first retry.
61 ///
62 /// Zero uses one second. The value must fit signed 32-bit whole seconds.
63 pub fn initial_interval(mut self, value: Duration) -> Self {
64 self.initial_interval = Some(value);
65 self
66 }
67
68 /// Sets the multiplier applied to each successive retry interval.
69 ///
70 /// Zero uses 2. The retry delay grows by this multiplier until the maximum interval.
71 pub fn backoff_coefficient(mut self, value: f64) -> Self {
72 self.backoff_coefficient = Some(value);
73 self
74 }
75
76 /// Caps the delay between retries.
77 ///
78 /// Zero uses 100 seconds. The value must fit signed 32-bit whole seconds.
79 pub fn maximum_interval(mut self, value: Duration) -> Self {
80 self.maximum_interval = Some(value);
81 self
82 }
83
84 /// Sets the total attempt limit, including the initial attempt.
85 ///
86 /// Zero allows unlimited attempts within the total-duration budget for Steps. One disables retries.
87 /// Values above the signed 32-bit maximum fail when the policy is submitted.
88 pub fn maximum_attempts(mut self, value: u32) -> Self {
89 self.maximum_attempts = Some(value);
90 self
91 }
92
93 /// Sets the elapsed-time budget used to decide whether to retry a failed Step attempt.
94 ///
95 /// For Steps, time starts when Dex first hands the Step to a Worker. Dex checks this budget
96 /// only after an attempt fails; expiration never ends a running attempt. Unset uses the Server
97 /// default of four hours. Zero also selects that default. `value` uses [`Duration`] and is
98 /// serialized in seconds; invalid durations fail when the policy is submitted. Returns this
99 /// policy with the budget applied.
100 ///
101 /// Changed in 1.35.0: queueing before the first hand-out is excluded, and this budget no
102 /// longer shortens a running attempt's method timeout. See [`RetryPolicy`] for retry queueing.
103 ///
104 /// ```
105 /// use dex_sdk::RetryPolicy;
106 /// use std::time::Duration;
107 /// let retry = RetryPolicy::new().total_duration(Duration::from_secs(60));
108 /// ```
109 pub fn total_duration(mut self, value: Duration) -> Self {
110 self.total_duration = Some(value);
111 self
112 }
113}