Skip to content

Commit e54eab5

Browse files
author
shaitan
committed
Add ExponentialBackoffRecoveryStrategy to Core fail-handling for robust recovery logic.
1 parent 2216cf8 commit e54eab5

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/* *********************************************************************
2+
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3+
* Copyright 2023 51 Degrees Mobile Experts Limited, Davidson House,
4+
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
5+
*
6+
* This Original Work is licensed under the European Union Public Licence
7+
* (EUPL) v.1.2 and is subject to its terms as set out below.
8+
*
9+
* If a copy of the EUPL was not distributed with this file, You can obtain
10+
* one at https://opensource.org/licenses/EUPL-1.2.
11+
*
12+
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
13+
* amended by the European Commission) shall be deemed incompatible for
14+
* the purposes of the Work and the provisions of the compatibility
15+
* clause in Article 5 of the EUPL shall not apply.
16+
*
17+
* If using the Work as, or as part of, a network application, by
18+
* including the attribution notice(s) required under Article 5 of the EUPL
19+
* in the end user terms of the application under an appropriate heading,
20+
* such notice(s) shall fulfill the requirements of that article.
21+
* ********************************************************************* */
22+
23+
using FiftyOne.Pipeline.Core.FailHandling.ExceptionCaching;
24+
using System;
25+
26+
namespace FiftyOne.Pipeline.Core.FailHandling.Recovery
27+
{
28+
/// <summary>
29+
/// Implements exponential backoff where delay doubles after each consecutive failure.
30+
/// First failure: wait initialDelay seconds
31+
/// Second failure: wait initialDelay * multiplier seconds
32+
/// Third failure: wait initialDelay * multiplier^2 seconds
33+
/// And so on, up to maxDelaySeconds.
34+
/// </summary>
35+
public class ExponentialBackoffRecoveryStrategy : IRecoveryStrategy
36+
{
37+
/// <summary>
38+
/// Initial delay in seconds for the first failure.
39+
/// </summary>
40+
public readonly double InitialDelaySeconds;
41+
42+
/// <summary>
43+
/// Maximum delay in seconds to cap the exponential growth.
44+
/// </summary>
45+
public readonly double MaxDelaySeconds;
46+
47+
/// <summary>
48+
/// Multiplier for exponential backoff (typically 2.0 for doubling).
49+
/// </summary>
50+
public readonly double Multiplier;
51+
52+
/// <summary>
53+
/// Current delay in seconds based on consecutive failures.
54+
/// </summary>
55+
public double CurrentDelaySeconds { get; private set; }
56+
57+
private CachedException _exception = null;
58+
private DateTime _recoveryDateTime = DateTime.MinValue;
59+
private int _consecutiveFailures = 0;
60+
private readonly object _lock = new object();
61+
62+
/// <summary>
63+
/// Constructor with default exponential backoff parameters.
64+
/// </summary>
65+
/// <param name="initialDelaySeconds">
66+
/// Initial delay in seconds (default: 2.0).
67+
/// </param>
68+
/// <param name="maxDelaySeconds">
69+
/// Maximum delay in seconds to cap growth (default: 300.0).
70+
/// </param>
71+
/// <param name="multiplier">
72+
/// Exponential multiplier (default: 2.0 for doubling).
73+
/// </param>
74+
public ExponentialBackoffRecoveryStrategy(
75+
double initialDelaySeconds = 2.0,
76+
double maxDelaySeconds = 300.0,
77+
double multiplier = 2.0)
78+
{
79+
if (initialDelaySeconds <= 0)
80+
throw new ArgumentException("Initial delay must be positive", nameof(initialDelaySeconds));
81+
if (maxDelaySeconds <= 0)
82+
throw new ArgumentException("Max delay must be positive", nameof(maxDelaySeconds));
83+
if (multiplier <= 1.0)
84+
throw new ArgumentException("Multiplier must be greater than 1.0", nameof(multiplier));
85+
86+
InitialDelaySeconds = initialDelaySeconds;
87+
MaxDelaySeconds = maxDelaySeconds;
88+
Multiplier = multiplier;
89+
CurrentDelaySeconds = initialDelaySeconds;
90+
}
91+
92+
/// <summary>
93+
/// Called when querying the server failed.
94+
/// Calculates the next delay using exponential backoff.
95+
/// </summary>
96+
/// <param name="cachedException">
97+
/// Timestamped exception.
98+
/// </param>
99+
public void RecordFailure(CachedException cachedException)
100+
{
101+
lock (_lock)
102+
{
103+
_consecutiveFailures++;
104+
105+
// Calculate new delay: initialDelay * multiplier^(failures-1)
106+
// For failures=1: initialDelay * multiplier^0 = initialDelay
107+
// For failures=2: initialDelay * multiplier^1 = initialDelay * multiplier
108+
// For failures=3: initialDelay * multiplier^2, etc.
109+
CurrentDelaySeconds = Math.Min(
110+
InitialDelaySeconds * Math.Pow(Multiplier, _consecutiveFailures - 1),
111+
MaxDelaySeconds);
112+
113+
var newRecoveryTime = cachedException.DateTime.AddSeconds(CurrentDelaySeconds);
114+
115+
_exception = cachedException;
116+
_recoveryDateTime = newRecoveryTime;
117+
}
118+
}
119+
120+
/// <summary>
121+
/// Whether the new request may be sent already.
122+
/// </summary>
123+
/// <param name="cachedException">
124+
/// Timestamped exception that prevents new requests.
125+
/// </param>
126+
/// <returns>true -- send, false -- skip</returns>
127+
public bool MayTryNow(out CachedException cachedException)
128+
{
129+
DateTime recoveryDateTime;
130+
CachedException lastCachedException;
131+
132+
lock (_lock)
133+
{
134+
recoveryDateTime = _recoveryDateTime;
135+
lastCachedException = _exception;
136+
}
137+
138+
if (recoveryDateTime < DateTime.Now)
139+
{
140+
cachedException = null;
141+
return true;
142+
}
143+
else
144+
{
145+
cachedException = lastCachedException;
146+
return false;
147+
}
148+
}
149+
150+
/// <summary>
151+
/// Called once the request succeeds (after recovery).
152+
/// Resets consecutive failures and delay back to initial value.
153+
/// </summary>
154+
public void Reset()
155+
{
156+
lock (_lock)
157+
{
158+
_consecutiveFailures = 0;
159+
CurrentDelaySeconds = InitialDelaySeconds;
160+
_exception = null;
161+
_recoveryDateTime = DateTime.MinValue;
162+
}
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)