Skip to content

Commit 670e96e

Browse files
heuveaCopilot
andauthored
FUND-2327 nrc_listener rollback Polly retry with AddRetryOnHttpStatusCodes and DBG retry-logging (#142)
* AddRetryOnHttpStatusCodes restored with Polly DBG logging * Update src/OneGround.ZGW.Notificaties.Messaging/ServiceConfiguration.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/OneGround.ZGW.Notificaties.Messaging/ServiceConfiguration.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * copilot fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 9e571f6 commit 670e96e

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

src/OneGround.ZGW.Common.ServiceAgent/Configuration/HttpResiliencePipelineOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ public class HttpResiliencePipelineOptions
77
public HttpRetryStrategyOptions Retry { get; set; } = HttpResiliencePipelineDefaults.RetryStrategyOptions;
88
public HttpTimeoutStrategyOptions Timeout { get; set; } = HttpResiliencePipelineDefaults.TimeoutStrategyOptions;
99

10+
public string AddRetryOnHttpStatusCodes { get; set; } = "";
11+
1012
public static string GetKey(string serviceName) => $"PollyConfig:{serviceName}";
1113
}

src/OneGround.ZGW.Notificaties.Messaging/ServiceConfiguration.cs

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
using System.Linq;
2+
using System.Net;
13
using Hangfire;
24
using Hangfire.Console;
35
using Hangfire.PostgreSql;
46
using MassTransit;
57
using Microsoft.Extensions.Configuration;
68
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Http.Resilience;
10+
using Microsoft.Extensions.Logging;
711
using OneGround.ZGW.Common.Batching;
812
using OneGround.ZGW.Common.CorrelationId;
913
using OneGround.ZGW.Common.Extensions;
@@ -21,6 +25,7 @@
2125
using OneGround.ZGW.Notificaties.Messaging.Jobs.Notificatie;
2226
using OneGround.ZGW.Notificaties.Messaging.Services;
2327
using Polly;
28+
using Polly.Retry;
2429

2530
namespace OneGround.ZGW.Notificaties.Messaging;
2631

@@ -64,10 +69,44 @@ public void ConfigureServices(IServiceCollection services)
6469
serviceName,
6570
(builder, context) =>
6671
{
67-
context.EnableReloads<HttpResiliencePipelineOptions>(optionsKey);
68-
var options = context.GetOptions<HttpResiliencePipelineOptions>(optionsKey);
72+
// Enable dynamic reloads of this pipeline whenever the named ResiliencePipelineNotificaties change
73+
context.EnableReloads<HttpResiliencePipelineOptions>("PollyConfig:NotificatiesSender");
74+
75+
// Retrieve the named options
76+
var options = context.GetOptions<HttpResiliencePipelineOptions>("PollyConfig:NotificatiesSender");
77+
78+
builder.AddRetry(
79+
new RetryStrategyOptions<HttpResponseMessage>
80+
{
81+
MaxRetryAttempts = options.Retry.MaxRetryAttempts,
82+
BackoffType = options.Retry.BackoffType,
83+
UseJitter = options.Retry.UseJitter,
84+
Delay = options.Retry.Delay,
85+
ShouldHandle = arg =>
86+
{
87+
if (arg.Outcome.Result == null) // This flow is when service did not respond at all (gives no HTTP statuscode)
88+
{
89+
// Always retry on this flow
90+
return ValueTask.FromResult(true);
91+
}
92+
93+
// Retry depending on the HTTP status code
94+
var shouldRetry = DefaultRetryOnHttpStatusCodes
95+
.Concat(HttpStatusCodesStringToEnumerable(options.AddRetryOnHttpStatusCodes))
96+
.Any(statuscode => arg.Outcome.Result.StatusCode == statuscode);
97+
98+
return ValueTask.FromResult(shouldRetry);
99+
},
100+
OnRetry = arg =>
101+
{
102+
LogRetry(context, arg);
103+
104+
// Event handlers can be asynchronous; here, we return an empty ValueTask.
105+
return default;
106+
},
107+
}
108+
);
69109

70-
builder.AddRetry(options.Retry);
71110
builder.AddTimeout(options.Timeout);
72111
}
73112
);
@@ -166,6 +205,24 @@ public void ConfigureServices(IServiceCollection services)
166205
);
167206
}
168207

208+
private static IEnumerable<HttpStatusCode> HttpStatusCodesStringToEnumerable(string addRetryOnHttpStatusCodes)
209+
{
210+
var result = addRetryOnHttpStatusCodes
211+
.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
212+
.Select(codeString =>
213+
{
214+
if (Enum.TryParse<HttpStatusCode>(codeString, out var statusCode))
215+
{
216+
return (HttpStatusCode?)statusCode;
217+
}
218+
return null;
219+
})
220+
.Where(code => code.HasValue)
221+
.Select(code => code!.Value);
222+
223+
return result;
224+
}
225+
169226
private bool IsExpireFailedJobsScannerEnabled =>
170227
!string.IsNullOrWhiteSpace(_hangfireConfiguration.ExpireFailedJobsScanAt)
171228
&& !DisabledValues.Contains(_hangfireConfiguration.ExpireFailedJobsScanAt.Trim());
@@ -228,4 +285,57 @@ private AutomaticRetryAttribute GetRetryPolicyFromConfig()
228285
LogEvents = false,
229286
};
230287
}
288+
289+
private static void LogRetry(ResilienceHandlerContext context, OnRetryArguments<HttpResponseMessage> arg)
290+
{
291+
using var scope = context.ServiceProvider.CreateScope();
292+
293+
var logger = scope.ServiceProvider.GetRequiredService<ILogger<ServiceConfiguration>>();
294+
295+
// Get the context in which a retry should be taken and log...
296+
if (
297+
arg.Context.Properties.TryGetValue(
298+
new ResiliencePropertyKey<HttpRequestMessage>("Resilience.Http.RequestMessage"),
299+
out var httpRequestMessage
300+
)
301+
)
302+
{
303+
if (arg.Outcome.Result is { } httpResponseMessage)
304+
{
305+
logger.LogDebug(
306+
"OnRetry, Attempt# {AttemptNumber} on {Method} {RequestUri} => {ReasonPhrase} [{StatusCode}]. Next over {TotalSeconds} second(s).",
307+
arg.AttemptNumber,
308+
httpRequestMessage.Method,
309+
httpRequestMessage.RequestUri,
310+
httpResponseMessage.ReasonPhrase,
311+
(int)httpResponseMessage.StatusCode,
312+
arg.RetryDelay.TotalSeconds
313+
);
314+
}
315+
else
316+
{
317+
logger.LogDebug(
318+
"OnRetry, Attempt# {AttemptNumber} on {Method} {RequestUri} => (no response). Next over {TotalSeconds} second(s).",
319+
arg.AttemptNumber,
320+
httpRequestMessage.Method,
321+
httpRequestMessage.RequestUri,
322+
arg.RetryDelay.TotalSeconds
323+
);
324+
}
325+
}
326+
else
327+
{
328+
logger.LogDebug("OnRetry, Attempt# {AttemptNumber}. Next over {TotalSeconds} second(s).", arg.AttemptNumber, arg.RetryDelay.TotalSeconds);
329+
}
330+
}
331+
332+
private static IEnumerable<HttpStatusCode> DefaultRetryOnHttpStatusCodes =>
333+
[
334+
HttpStatusCode.RequestTimeout,
335+
HttpStatusCode.TooManyRequests,
336+
HttpStatusCode.InternalServerError,
337+
HttpStatusCode.BadGateway,
338+
HttpStatusCode.ServiceUnavailable,
339+
HttpStatusCode.GatewayTimeout,
340+
];
231341
}

0 commit comments

Comments
 (0)