Skip to content

Commit 5772cf4

Browse files
committed
feat(csharp): refactor retry logic into RetryHelper for improved code organization
1 parent 1e77d31 commit 5772cf4

File tree

3 files changed

+69
-83
lines changed

3 files changed

+69
-83
lines changed

clients/algoliasearch-client-csharp/algoliasearch/Utils/IngestionClientExtensions.cs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,6 @@ List<WatchResponse> ChunkedPush<T>(
4848

4949
public partial class IngestionClient : IIngestionClient
5050
{
51-
/// <summary>
52-
/// The default maximum number of retries for search operations.
53-
/// </summary>
54-
public const int DefaultMaxRetries = 50;
55-
5651
/// <summary>
5752
/// Helper: Chunks the given `objects` list in subset of 1000 elements max in order to make it fit
5853
/// in `push` requests by leveraging the Transformation pipeline setup in the Push connector
@@ -126,7 +121,7 @@ public async Task<List<WatchResponse>> ChunkedPushAsync<T>(
126121
);
127122
}
128123

129-
await RetryUntil(
124+
await RetryHelper.RetryUntil(
130125
async () =>
131126
{
132127
try
@@ -182,37 +177,4 @@ public List<WatchResponse> ChunkedPush<T>(
182177
cancellationToken
183178
)
184179
);
185-
186-
private static async Task<T> RetryUntil<T>(
187-
Func<Task<T>> func,
188-
Func<T, bool> validate,
189-
int maxRetries = DefaultMaxRetries,
190-
Func<int, int> timeout = null,
191-
CancellationToken ct = default
192-
)
193-
{
194-
timeout ??= NextDelay;
195-
196-
var retryCount = 0;
197-
while (retryCount < maxRetries)
198-
{
199-
var resp = await func().ConfigureAwait(false);
200-
if (validate(resp))
201-
{
202-
return resp;
203-
}
204-
205-
await Task.Delay(timeout(retryCount), ct).ConfigureAwait(false);
206-
retryCount++;
207-
}
208-
209-
throw new AlgoliaException(
210-
"The maximum number of retries exceeded. (" + (retryCount + 1) + "/" + maxRetries + ")"
211-
);
212-
}
213-
214-
private static int NextDelay(int retryCount)
215-
{
216-
return Math.Min(retryCount * 200, 5000);
217-
}
218180
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Algolia.Search.Exceptions;
5+
6+
namespace Algolia.Search.Utils;
7+
8+
/// <summary>
9+
/// A helper class to retry operations
10+
/// </summary>
11+
public static class RetryHelper
12+
{
13+
/// <summary>
14+
/// The default maximum number of retries
15+
/// </summary>
16+
public const int DefaultMaxRetries = 50;
17+
18+
/// <summary>
19+
/// Retry the given function until the validation function returns true or the maximum number of retries is reached
20+
/// </summary>
21+
/// <typeparam name="T">The type of the function's return value</typeparam>
22+
/// <param name="func">The function to retry</param>
23+
/// <param name="validate">The validation function</param>
24+
/// <param name="maxRetries">The maximum number of retries</param>
25+
/// <param name="timeout">A function that takes the retry count and returns the timeout in milliseconds before the next retry</param>
26+
/// <param name="ct">A cancellation token to cancel the operation</param>
27+
/// <returns>The result of the function if the validation function returns true</returns>
28+
/// <exception cref="AlgoliaException">Thrown if the maximum number of retries is reached</exception>
29+
public static async Task<T> RetryUntil<T>(
30+
Func<Task<T>> func,
31+
Func<T, bool> validate,
32+
int maxRetries = DefaultMaxRetries,
33+
Func<int, int> timeout = null,
34+
CancellationToken ct = default
35+
)
36+
{
37+
timeout ??= NextDelay;
38+
39+
var retryCount = 0;
40+
while (retryCount < maxRetries)
41+
{
42+
var resp = await func().ConfigureAwait(false);
43+
if (validate(resp))
44+
{
45+
return resp;
46+
}
47+
48+
await Task.Delay(timeout(retryCount), ct).ConfigureAwait(false);
49+
retryCount++;
50+
}
51+
52+
throw new AlgoliaException(
53+
"The maximum number of retries exceeded. (" + (retryCount + 1) + "/" + maxRetries + ")"
54+
);
55+
}
56+
57+
private static int NextDelay(int retryCount)
58+
{
59+
return Math.Min(retryCount * 200, 5000);
60+
}
61+
}

clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ public partial class SearchClient : ISearchClient
531531
/// <summary>
532532
/// The default maximum number of retries for search operations.
533533
/// </summary>
534-
public const int DefaultMaxRetries = 50;
534+
public const int DefaultMaxRetries = RetryHelper.DefaultMaxRetries;
535535

536536
/// <inheritdoc/>
537537
public async Task<GetTaskResponse> WaitForTaskAsync(
@@ -541,17 +541,15 @@ public async Task<GetTaskResponse> WaitForTaskAsync(
541541
Func<int, int> timeout = null,
542542
RequestOptions requestOptions = null,
543543
CancellationToken ct = default
544-
)
545-
{
546-
return await RetryUntil(
544+
) =>
545+
await RetryHelper.RetryUntil(
547546
async () => await GetTaskAsync(indexName, taskId, requestOptions, ct),
548547
resp => resp.Status == Models.Search.TaskStatus.Published,
549548
maxRetries,
550549
timeout,
551550
ct
552551
)
553552
.ConfigureAwait(false);
554-
}
555553

556554
/// <inheritdoc/>
557555
public GetTaskResponse WaitForTask(
@@ -573,17 +571,15 @@ public async Task<GetTaskResponse> WaitForAppTaskAsync(
573571
Func<int, int> timeout = null,
574572
RequestOptions requestOptions = null,
575573
CancellationToken ct = default
576-
)
577-
{
578-
return await RetryUntil(
574+
) =>
575+
await RetryHelper.RetryUntil(
579576
async () => await GetAppTaskAsync(taskId, requestOptions, ct),
580577
resp => resp.Status == Models.Search.TaskStatus.Published,
581578
maxRetries,
582579
timeout,
583580
ct
584581
)
585582
.ConfigureAwait(false);
586-
}
587583

588584
/// <inheritdoc/>
589585
public GetTaskResponse WaitForAppTask(
@@ -613,7 +609,7 @@ public async Task<GetApiKeyResponse> WaitForApiKeyAsync(
613609
throw new AlgoliaException("`ApiKey` is required when waiting for an `update` operation.");
614610
}
615611

616-
return await RetryUntil(
612+
return await RetryHelper.RetryUntil(
617613
() => GetApiKeyAsync(key, requestOptions, ct),
618614
resp =>
619615
{
@@ -637,7 +633,7 @@ public async Task<GetApiKeyResponse> WaitForApiKeyAsync(
637633
.ConfigureAwait(false);
638634
}
639635

640-
return await RetryUntil(
636+
return await RetryHelper.RetryUntil(
641637
async () =>
642638
{
643639
try
@@ -879,39 +875,6 @@ public List<SearchForFacetValuesResponse> SearchForFacets(
879875
SearchForFacetsAsync(requests, searchStrategy, options, cancellationToken)
880876
);
881877

882-
private static async Task<T> RetryUntil<T>(
883-
Func<Task<T>> func,
884-
Func<T, bool> validate,
885-
int maxRetries = DefaultMaxRetries,
886-
Func<int, int> timeout = null,
887-
CancellationToken ct = default
888-
)
889-
{
890-
timeout ??= NextDelay;
891-
892-
var retryCount = 0;
893-
while (retryCount < maxRetries)
894-
{
895-
var resp = await func().ConfigureAwait(false);
896-
if (validate(resp))
897-
{
898-
return resp;
899-
}
900-
901-
await Task.Delay(timeout(retryCount), ct).ConfigureAwait(false);
902-
retryCount++;
903-
}
904-
905-
throw new AlgoliaException(
906-
"The maximum number of retries exceeded. (" + (retryCount + 1) + "/" + maxRetries + ")"
907-
);
908-
}
909-
910-
private static int NextDelay(int retryCount)
911-
{
912-
return Math.Min(retryCount * 200, 5000);
913-
}
914-
915878
/// <inheritdoc/>
916879
public async Task<ReplaceAllObjectsResponse> ReplaceAllObjectsAsync<T>(
917880
string indexName,

0 commit comments

Comments
 (0)