1+ using System ;
2+ using System . Net ;
3+ using System . Net . Http ;
4+ using System . Threading . Tasks ;
5+
6+ using Polly ;
7+ using Polly . Extensions . Http ;
8+
9+ namespace Route4MeSDKLibrary . Resilience
10+ {
11+ internal static class HttpResiliencePolicy
12+ {
13+ private static readonly Lazy < IAsyncPolicy < HttpResponseMessage > > _asyncPolicy
14+ = new Lazy < IAsyncPolicy < HttpResponseMessage > > ( CreateAsyncPolicy ) ;
15+
16+ private static readonly Lazy < ISyncPolicy < HttpResponseMessage > > _syncPolicy
17+ = new Lazy < ISyncPolicy < HttpResponseMessage > > ( CreateSyncPolicy ) ;
18+
19+ private static readonly Random _jitterer = new Random ( ) ;
20+
21+ public static IAsyncPolicy < HttpResponseMessage > GetAsyncPolicy ( )
22+ {
23+ return Route4MeConfig . RetryCount > 0
24+ ? _asyncPolicy . Value
25+ : Policy . NoOpAsync < HttpResponseMessage > ( ) ;
26+ }
27+
28+ public static ISyncPolicy < HttpResponseMessage > GetSyncPolicy ( )
29+ {
30+ return Route4MeConfig . RetryCount > 0
31+ ? _syncPolicy . Value
32+ : Policy . NoOp < HttpResponseMessage > ( ) ;
33+ }
34+
35+ private static IAsyncPolicy < HttpResponseMessage > CreateAsyncPolicy ( )
36+ {
37+ var retryPolicy = CreateAsyncRetryPolicy ( ) ;
38+
39+ if ( Route4MeConfig . EnableCircuitBreaker )
40+ {
41+ var circuitBreakerPolicy = CreateAsyncCircuitBreakerPolicy ( ) ;
42+ return Policy . WrapAsync ( retryPolicy , circuitBreakerPolicy ) ;
43+ }
44+
45+ return retryPolicy ;
46+ }
47+
48+ private static ISyncPolicy < HttpResponseMessage > CreateSyncPolicy ( )
49+ {
50+ var retryPolicy = CreateSyncRetryPolicy ( ) ;
51+
52+ if ( Route4MeConfig . EnableCircuitBreaker )
53+ {
54+ var circuitBreakerPolicy = CreateSyncCircuitBreakerPolicy ( ) ;
55+ return Policy . Wrap ( retryPolicy , circuitBreakerPolicy ) ;
56+ }
57+
58+ return retryPolicy ;
59+ }
60+
61+ private static IAsyncPolicy < HttpResponseMessage > CreateAsyncRetryPolicy ( )
62+ {
63+ return HttpPolicyExtensions
64+ . HandleTransientHttpError ( )
65+ . Or < TaskCanceledException > ( )
66+ . OrResult ( r => r . StatusCode == ( HttpStatusCode ) 429 )
67+ . WaitAndRetryAsync (
68+ retryCount : Route4MeConfig . RetryCount ,
69+ sleepDurationProvider : ( retryAttempt ) =>
70+ {
71+ var exponentialDelay = Route4MeConfig . RetryInitialDelay
72+ . TotalMilliseconds * Math . Pow ( 2 , retryAttempt - 1 ) ;
73+
74+ var jitter = ( _jitterer . NextDouble ( ) * 0.4 ) - 0.2 ;
75+ var delayWithJitter = exponentialDelay * ( 1 + jitter ) ;
76+
77+ return TimeSpan . FromMilliseconds ( delayWithJitter ) ;
78+ } ,
79+ onRetry : ( outcome , timespan , retryCount , context ) =>
80+ {
81+ Route4MeConfig . OnRetry ? . Invoke (
82+ outcome . Exception ,
83+ timespan ,
84+ retryCount ,
85+ context
86+ ) ;
87+ }
88+ ) ;
89+ }
90+
91+ private static ISyncPolicy < HttpResponseMessage > CreateSyncRetryPolicy ( )
92+ {
93+ return HttpPolicyExtensions
94+ . HandleTransientHttpError ( )
95+ . Or < TaskCanceledException > ( )
96+ . OrResult ( r => r . StatusCode == ( HttpStatusCode ) 429 )
97+ . WaitAndRetry (
98+ retryCount : Route4MeConfig . RetryCount ,
99+ sleepDurationProvider : ( retryAttempt ) =>
100+ {
101+ var exponentialDelay = Route4MeConfig . RetryInitialDelay
102+ . TotalMilliseconds * Math . Pow ( 2 , retryAttempt - 1 ) ;
103+ var jitter = ( _jitterer . NextDouble ( ) * 0.4 ) - 0.2 ;
104+ var delayWithJitter = exponentialDelay * ( 1 + jitter ) ;
105+ return TimeSpan . FromMilliseconds ( delayWithJitter ) ;
106+ } ,
107+ onRetry : ( outcome , timespan , retryCount , context ) =>
108+ {
109+ Route4MeConfig . OnRetry ? . Invoke (
110+ outcome . Exception ,
111+ timespan ,
112+ retryCount ,
113+ context
114+ ) ;
115+ }
116+ ) ;
117+ }
118+
119+ private static IAsyncPolicy < HttpResponseMessage > CreateAsyncCircuitBreakerPolicy ( )
120+ {
121+ return HttpPolicyExtensions
122+ . HandleTransientHttpError ( )
123+ . Or < TaskCanceledException > ( )
124+ . OrResult ( r => r . StatusCode == ( HttpStatusCode ) 429 )
125+ . CircuitBreakerAsync (
126+ handledEventsAllowedBeforeBreaking : Route4MeConfig . CircuitBreakerFailureThreshold ,
127+ durationOfBreak : Route4MeConfig . CircuitBreakerDuration ,
128+ onBreak : ( outcome , duration ) =>
129+ {
130+ Route4MeConfig . OnCircuitBreakerOpen ? . Invoke (
131+ outcome . Exception ,
132+ duration
133+ ) ;
134+ } ,
135+ onReset : ( ) => { } ,
136+ onHalfOpen : ( ) => { }
137+ ) ;
138+ }
139+
140+ private static ISyncPolicy < HttpResponseMessage > CreateSyncCircuitBreakerPolicy ( )
141+ {
142+ return HttpPolicyExtensions
143+ . HandleTransientHttpError ( )
144+ . Or < TaskCanceledException > ( )
145+ . OrResult ( r => r . StatusCode == ( HttpStatusCode ) 429 )
146+ . CircuitBreaker (
147+ handledEventsAllowedBeforeBreaking : Route4MeConfig . CircuitBreakerFailureThreshold ,
148+ durationOfBreak : Route4MeConfig . CircuitBreakerDuration ,
149+ onBreak : ( outcome , duration ) =>
150+ {
151+ Route4MeConfig . OnCircuitBreakerOpen ? . Invoke (
152+ outcome . Exception ,
153+ duration
154+ ) ;
155+ } ,
156+ onReset : ( ) => { } ,
157+ onHalfOpen : ( ) => { }
158+ ) ;
159+ }
160+
161+ internal static bool IsTransientError ( HttpStatusCode statusCode )
162+ {
163+ return statusCode == HttpStatusCode . RequestTimeout
164+ || statusCode == ( HttpStatusCode ) 429
165+ || statusCode == HttpStatusCode . ServiceUnavailable
166+ || statusCode == HttpStatusCode . GatewayTimeout
167+ || ( ( int ) statusCode >= 500 && ( int ) statusCode < 600 ) ;
168+ }
169+ }
170+ }
0 commit comments