Skip to content

Commit e8aff2e

Browse files
committed
docs: Improve documentation for retry strategies (box/box-codegen#925)
1 parent 576c121 commit e8aff2e

File tree

2 files changed

+145
-18
lines changed

2 files changed

+145
-18
lines changed

.codegen.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "engineHash": "9dcb945", "specHash": "77eac4b", "version": "6.4.0" }
1+
{ "engineHash": "482939a", "specHash": "77eac4b", "version": "6.4.0" }

docs/BoxSdkGen/Configuration.md

Lines changed: 144 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,173 @@
33
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
44
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
55

6-
- [Configuration](#configuration)
7-
- [Max retry attempts](#max-retry-attempts)
8-
- [Custom retry strategy](#custom-retry-strategy)
6+
- [Retry Strategy](#retry-strategy)
7+
- [Overview](#overview)
8+
- [Default Configuration](#default-configuration)
9+
- [Retry Decision Flow](#retry-decision-flow)
10+
- [Exponential Backoff Algorithm](#exponential-backoff-algorithm)
11+
- [Example Delays (with default settings)](#example-delays-with-default-settings)
12+
- [Retry-After Header](#retry-after-header)
13+
- [Network Exception Handling](#network-exception-handling)
14+
- [Customizing Retry Parameters](#customizing-retry-parameters)
15+
- [Custom Retry Strategy](#custom-retry-strategy)
916

1017
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
1118

12-
## Max retry attempts
19+
## Retry Strategy
1320

14-
The default maximum number of retries in case of failed API call is 5.
15-
To change this number you should initialize `BoxRetryStrategy` with the new value and pass it to `NetworkSession`.
21+
### Overview
22+
23+
The SDK ships with a built-in retry strategy (`BoxRetryStrategy`) that implements the `IRetryStrategy` interface. The `BoxNetworkClient`, which serves as the default network client, uses this strategy to automatically retry failed API requests with exponential backoff.
24+
25+
The retry strategy exposes two methods:
26+
27+
- **`ShouldRetryAsync`** — Determines whether a failed request should be retried based on the HTTP status code, response headers, attempt count, and authentication state.
28+
- **`RetryAfter`** — Computes the delay (in seconds) before the next retry attempt, using either the server-provided `Retry-After` header or an exponential backoff formula.
29+
30+
### Default Configuration
31+
32+
| Parameter | Default | Description |
33+
| -------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
34+
| `MaxAttempts` | `5` | Maximum number of retry attempts for HTTP error responses (status 4xx/5xx). |
35+
| `RetryBaseInterval` | `1` (second) | Base interval used in the exponential backoff calculation. |
36+
| `RetryRandomizationFactor` | `0.5` | Jitter factor applied to the backoff delay. The actual delay is multiplied by a random value between `1 - factor` and `1 + factor`. |
37+
| `MaxRetriesOnException` | `2` | Maximum number of retries for network-level exceptions (connection failures, timeouts). These are tracked by a separate counter from HTTP error retries. |
38+
39+
### Retry Decision Flow
40+
41+
The following diagram shows how `BoxRetryStrategy.ShouldRetryAsync` decides whether to retry a request:
42+
43+
```
44+
ShouldRetryAsync(fetchOptions, fetchResponse, attemptNumber)
45+
|
46+
v
47+
+-----------------------+
48+
| Status == 0 | Yes
49+
| (network exception)? |----------> attemptNumber <= MaxRetriesOnException?
50+
+-----------------------+ | |
51+
| No Yes No
52+
v | |
53+
+-----------------------+ [RETRY] [NO RETRY]
54+
| attemptNumber >= |
55+
| MaxAttempts? |
56+
+-----------------------+
57+
| |
58+
Yes No
59+
| |
60+
[NO RETRY] v
61+
+-----------------------+
62+
| Status == 202 AND | Yes
63+
| Retry-After header? |----------> [RETRY]
64+
+-----------------------+
65+
| No
66+
v
67+
+-----------------------+
68+
| Status >= 500 | Yes
69+
| (server error)? |----------> [RETRY]
70+
+-----------------------+
71+
| No
72+
v
73+
+-----------------------+
74+
| Status == 429 | Yes
75+
| (rate limited)? |----------> [RETRY]
76+
+-----------------------+
77+
| No
78+
v
79+
+-----------------------+
80+
| Status == 401 AND | Yes
81+
| auth available? |----------> Refresh token, then [RETRY]
82+
+-----------------------+
83+
| No
84+
v
85+
[NO RETRY]
86+
```
87+
88+
### Exponential Backoff Algorithm
89+
90+
When the response does not include a `Retry-After` header, the retry delay is computed using exponential backoff with randomized jitter:
91+
92+
```
93+
delay = 2^attemptNumber * RetryBaseInterval * random(1 - factor, 1 + factor)
94+
```
95+
96+
Where:
97+
98+
- `attemptNumber` is the current attempt (1-based)
99+
- `RetryBaseInterval` defaults to `1` second
100+
- `factor` is `RetryRandomizationFactor` (default `0.5`)
101+
- `random(min, max)` returns a uniformly distributed value in `[min, max]`
102+
103+
#### Example Delays (with default settings)
104+
105+
| Attempt | Base Delay | Min Delay (factor=0.5) | Max Delay (factor=0.5) |
106+
| ------- | ---------- | ---------------------- | ---------------------- |
107+
| 1 | 2s | 1.0s | 3.0s |
108+
| 2 | 4s | 2.0s | 6.0s |
109+
| 3 | 8s | 4.0s | 12.0s |
110+
| 4 | 16s | 8.0s | 24.0s |
111+
112+
### Retry-After Header
113+
114+
When the server includes a `Retry-After` header in the response, the SDK uses the header value directly as the delay in seconds instead of computing an exponential backoff delay. This applies to any retryable response that includes the header, including:
115+
116+
- `202 Accepted` with `Retry-After` (long-running operations)
117+
- `429 Too Many Requests` with `Retry-After`
118+
- `5xx` server errors with `Retry-After`
119+
120+
The header value is parsed as a floating-point number representing seconds.
121+
122+
### Network Exception Handling
123+
124+
Network-level failures (connection refused, DNS resolution errors, timeouts, TLS errors) are represented internally as responses with status `0`. These exceptions are tracked by a **separate counter** (`MaxRetriesOnException`, default `2`) from the regular HTTP error retry counter (`MaxAttempts`).
125+
126+
This means:
127+
128+
- Network exception retries are tracked independently from HTTP error retries, each with their own counter and backoff progression.
129+
- A request can fail up to `MaxRetriesOnException` times due to network exceptions, but each exception retry also increments the overall attempt counter, so the total number of retries across both exception and HTTP error types is bounded by `MaxAttempts`.
130+
131+
### Customizing Retry Parameters
132+
133+
You can customize all retry parameters by initializing `BoxRetryStrategy` with the desired values and passing it to `NetworkSession`:
16134

17135
```c#
18136
BoxDeveloperTokenAuth auth = new BoxDeveloperTokenAuth("DEVELOPER_TOKEN");
19-
NetworkSession networkSession = new NetworkSession() { RetryStrategy = new BoxRetryStrategy(5) };
137+
NetworkSession networkSession = new NetworkSession()
138+
{
139+
RetryStrategy = new BoxRetryStrategy(
140+
maxAttempts: 3,
141+
retryBaseInterval: 2,
142+
retryRandomizationFactor: 0.3,
143+
maxRetriesOnException: 1
144+
)
145+
};
20146
BoxClient client = new BoxClient(auth: auth, networkSession: networkSession);
21147
```
22148

23-
## Custom retry strategy
149+
### Custom Retry Strategy
24150

25-
You can also implement your own retry strategy by subclassing `RetryStrategy` and overriding `ShouldRetryAsync` and `RetryAfter` methods.
26-
This example shows how to set custom strategy that retries on 5xx status codes and waits 1 second between retries.
151+
You can implement your own retry strategy by implementing the `IRetryStrategy` interface and providing `ShouldRetryAsync` and `RetryAfter` methods:
27152

28153
```c#
29154
public class CustomRetryStrategy : IRetryStrategy
30155
{
31-
public Task<bool> ShouldRetryAsync(FetchOptions fetchOptions, FetchResponse fetchResponse, int attemptNumber)
156+
public Task<bool> ShouldRetryAsync(
157+
FetchOptions fetchOptions, FetchResponse fetchResponse, int attemptNumber)
32158
{
33-
return Task.FromResult(fetchResponse.Status >= 500);
159+
return Task.FromResult(fetchResponse.Status >= 500 && attemptNumber < 3);
34160
}
35161

36-
public double RetryAfter(FetchOptions fetchOptions, FetchResponse fetchResponse, int attemptNumber)
162+
public double RetryAfter(
163+
FetchOptions fetchOptions, FetchResponse fetchResponse, int attemptNumber)
37164
{
38165
return 1.0;
39166
}
40167
}
41168

42169
BoxDeveloperTokenAuth auth = new BoxDeveloperTokenAuth("DEVELOPER_TOKEN");
43-
NetworkSession networkSession = new NetworkSession() { RetryStrategy = new CustomRetryStrategy() };
170+
NetworkSession networkSession = new NetworkSession()
171+
{
172+
RetryStrategy = new CustomRetryStrategy()
173+
};
44174
BoxClient client = new BoxClient(auth: auth, networkSession: networkSession);
45175
```
46-
47-
As you can see, in this example we based our decision to retry solely on the status code of the response.
48-
However, you can use any information available in `fetchOptions` and `fetchResponse` to make a more informed decision.

0 commit comments

Comments
 (0)