@@ -18,6 +18,41 @@ using namespace Aws::Http;
1818
1919static const char ALLOCATION_TAG [] = " RetryBehaviorTest" ;
2020
21+ class TestThrottleBasedQuotaContainer : public RetryQuotaContainer
22+ {
23+ public:
24+ TestThrottleBasedQuotaContainer () : m_retryQuota(500 ) {}
25+
26+ bool AcquireRetryQuota (int capacityAmount) override
27+ {
28+ if (capacityAmount > m_retryQuota) return false ;
29+ m_retryQuota -= capacityAmount;
30+ return true ;
31+ }
32+
33+ bool AcquireRetryQuota (const AWSError<CoreErrors>& error) override
34+ {
35+ int capacityAmount = error.ShouldThrottle () ? 5 : 14 ;
36+ return AcquireRetryQuota (capacityAmount);
37+ }
38+
39+ void ReleaseRetryQuota (int capacityAmount) override
40+ {
41+ m_retryQuota = std::min (m_retryQuota + capacityAmount, 500 );
42+ }
43+
44+ void ReleaseRetryQuota (const AWSError<CoreErrors>& error) override
45+ {
46+ int capacityAmount = error.ShouldThrottle () ? 5 : 14 ;
47+ ReleaseRetryQuota (capacityAmount);
48+ }
49+
50+ int GetRetryQuota () const override { return m_retryQuota; }
51+
52+ private:
53+ int m_retryQuota;
54+ };
55+
2156class RetryBehaviorTest : public Aws ::Testing::AwsCppSdkGTestSuite
2257{
2358};
@@ -49,8 +84,8 @@ static AWSError<CoreErrors> MakeTransientErrorWithRetryAfter(const Aws::String&
4984// SEP Test 1: Retry eventually succeeds, quota restored
5085TEST_F (RetryBehaviorTest, RetryEventuallySucceeds)
5186{
52- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
53- StandardRetryStrategy strategy (quota, 3 , 0.05 );
87+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
88+ StandardRetryStrategy strategy (quota, 3 );
5489
5590 ASSERT_EQ (500 , quota->GetRetryQuota ());
5691
@@ -66,8 +101,8 @@ TEST_F(RetryBehaviorTest, RetryEventuallySucceeds)
66101// SEP Test 2: Max attempts reached
67102TEST_F (RetryBehaviorTest, MaxAttemptsReached)
68103{
69- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
70- StandardRetryStrategy strategy (quota, 3 , 0.05 );
104+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
105+ StandardRetryStrategy strategy (quota, 3 );
71106
72107 ASSERT_TRUE (strategy.ShouldRetry (MakeTransientError (), 0 ));
73108 ASSERT_TRUE (strategy.ShouldRetry (MakeTransientError (), 1 ));
@@ -78,8 +113,8 @@ TEST_F(RetryBehaviorTest, MaxAttemptsReached)
78113// SEP Test 3: Quota reached after 1 retry
79114TEST_F (RetryBehaviorTest, QuotaReachedAfterRetry)
80115{
81- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
82- StandardRetryStrategy strategy (quota, 10 , 0.05 );
116+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
117+ StandardRetryStrategy strategy (quota, 10 );
83118
84119 // Drain quota to 10
85120 ASSERT_TRUE (quota->AcquireRetryQuota (490 ));
@@ -92,8 +127,8 @@ TEST_F(RetryBehaviorTest, QuotaReachedAfterRetry)
92127// SEP Test 4: Zero quota, no retries
93128TEST_F (RetryBehaviorTest, ZeroQuotaNoRetries)
94129{
95- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
96- StandardRetryStrategy strategy (quota, 10 , 0.05 );
130+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
131+ StandardRetryStrategy strategy (quota, 10 );
97132
98133 ASSERT_TRUE (quota->AcquireRetryQuota (500 ));
99134 ASSERT_EQ (0 , quota->GetRetryQuota ());
@@ -105,8 +140,8 @@ TEST_F(RetryBehaviorTest, ZeroQuotaNoRetries)
105140// SEP Test 5: Exponential timing (transient, 50ms base)
106141TEST_F (RetryBehaviorTest, ExponentialBackoffTransient)
107142{
108- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
109- StandardRetryStrategy strategy (quota, 10 , 0.05 );
143+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
144+ StandardRetryStrategy strategy (quota, 10 );
110145
111146 auto error = MakeTransientError ();
112147 // Backoff is randomized, but must be within [0, x * 2^i * 1000]ms
@@ -123,8 +158,8 @@ TEST_F(RetryBehaviorTest, ExponentialBackoffTransient)
123158// SEP Test 6: Max backoff cap at 20s
124159TEST_F (RetryBehaviorTest, MaxBackoffCap)
125160{
126- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
127- StandardRetryStrategy strategy (quota, 100 , 0.05 );
161+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
162+ StandardRetryStrategy strategy (quota, 100 );
128163
129164 auto error = MakeTransientError ();
130165 // At i=30, 0.05 * 2^30 = 53687091.2s which exceeds 20s cap
@@ -135,8 +170,8 @@ TEST_F(RetryBehaviorTest, MaxBackoffCap)
135170// SEP Test 7: Quota exhaustion mid-sequence
136171TEST_F (RetryBehaviorTest, QuotaExhaustionMidSequence)
137172{
138- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
139- StandardRetryStrategy strategy (quota, 100 , 0.05 );
173+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
174+ StandardRetryStrategy strategy (quota, 100 );
140175
141176 // Drain to 20 tokens
142177 ASSERT_TRUE (quota->AcquireRetryQuota (480 ));
@@ -154,8 +189,8 @@ TEST_F(RetryBehaviorTest, QuotaExhaustionMidSequence)
154189// SEP Test 8: Quota recovery (stateful multi-request sequence)
155190TEST_F (RetryBehaviorTest, QuotaRecoveryStateful)
156191{
157- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
158- StandardRetryStrategy strategy (quota, 10 , 0.05 );
192+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
193+ StandardRetryStrategy strategy (quota, 10 );
159194
160195 std::shared_ptr<HttpRequest> httpRequest = CreateHttpRequest (URI (" http://www.uri.com" ), HttpMethod::HTTP_GET , Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
161196 std::shared_ptr<HttpResponse> httpResponse = Aws::MakeShared<Standard::StandardHttpResponse>(ALLOCATION_TAG , httpRequest);
@@ -189,8 +224,8 @@ TEST_F(RetryBehaviorTest, QuotaRecoveryStateful)
189224// SEP Test 9: Multi-threaded quota sharing (verify shared state)
190225TEST_F (RetryBehaviorTest, SharedQuotaAcrossRequests)
191226{
192- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
193- StandardRetryStrategy strategy (quota, 10 , 0.05 );
227+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
228+ StandardRetryStrategy strategy (quota, 10 );
194229
195230 // Simulate two concurrent requests both acquiring from same quota
196231 // Request A: transient retry, costs 14, quota = 486
@@ -212,8 +247,8 @@ TEST_F(RetryBehaviorTest, SharedQuotaAcrossRequests)
212247// SEP Test 10: Throttling costs (5 tokens) and backoff (1000ms base)
213248TEST_F (RetryBehaviorTest, ThrottlingCostsAndBackoff)
214249{
215- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
216- StandardRetryStrategy strategy (quota, 10 , 0.05 );
250+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
251+ StandardRetryStrategy strategy (quota, 10 );
217252
218253 // Throttling error costs 5 tokens
219254 ASSERT_TRUE (strategy.ShouldRetry (MakeThrottlingError (), 0 ));
@@ -231,31 +266,31 @@ TEST_F(RetryBehaviorTest, ThrottlingCostsAndBackoff)
231266 ASSERT_LE (delay, 2000 );
232267}
233268
234- // SEP Test 11: DynamoDB tuning (25ms base, 4 max attempts )
269+ // SEP Test 11: DynamoDB tuning (maxAttempts=4, 25ms base deferred to follow-up )
235270TEST_F (RetryBehaviorTest, DynamoDBTuning)
236271{
237- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
238- StandardRetryStrategy strategy (quota, 4 , 0.025 );
272+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
273+ StandardRetryStrategy strategy (quota, 4 );
239274
240275 ASSERT_EQ (4 , strategy.GetMaxAttempts ());
241276
242277 auto error = MakeTransientError ();
243- // i=0: [0, 25ms]
278+ // i=0: [0, 50ms] (25ms base deferred)
244279 long delay = strategy.CalculateDelayBeforeNextRetry (error, 0 );
245280 ASSERT_GE (delay, 0 );
246- ASSERT_LE (delay, 25 );
281+ ASSERT_LE (delay, 50 );
247282
248- // i=1: [0, 50ms ]
283+ // i=1: [0, 100ms ]
249284 delay = strategy.CalculateDelayBeforeNextRetry (error, 1 );
250285 ASSERT_GE (delay, 0 );
251- ASSERT_LE (delay, 50 );
286+ ASSERT_LE (delay, 100 );
252287}
253288
254289// SEP Test 12: Long-polling transient + empty quota (backoff applied)
255290TEST_F (RetryBehaviorTest, LongPollingTransientEmptyQuota)
256291{
257- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
258- StandardRetryStrategy strategy (quota, 10 , 0.05 );
292+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
293+ StandardRetryStrategy strategy (quota, 10 );
259294
260295 // Drain quota
261296 ASSERT_TRUE (quota->AcquireRetryQuota (500 ));
@@ -275,8 +310,8 @@ TEST_F(RetryBehaviorTest, LongPollingTransientEmptyQuota)
275310// SEP Test 13: Long-polling throttling + empty quota (backoff applied)
276311TEST_F (RetryBehaviorTest, LongPollingThrottlingEmptyQuota)
277312{
278- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
279- StandardRetryStrategy strategy (quota, 10 , 0.05 );
313+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
314+ StandardRetryStrategy strategy (quota, 10 );
280315
281316 // Drain quota
282317 ASSERT_TRUE (quota->AcquireRetryQuota (500 ));
@@ -296,8 +331,8 @@ TEST_F(RetryBehaviorTest, LongPollingThrottlingEmptyQuota)
296331// SEP Test 14: Long-polling max attempts exceeded (no delay)
297332TEST_F (RetryBehaviorTest, LongPollingMaxAttemptsExceeded)
298333{
299- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
300- StandardRetryStrategy strategy (quota, 3 , 0.05 );
334+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
335+ StandardRetryStrategy strategy (quota, 3 );
301336
302337 // At retries=2, max attempts (3) is reached
303338 ASSERT_FALSE (strategy.ShouldRetry (MakeTransientError (), 2 ));
@@ -307,8 +342,8 @@ TEST_F(RetryBehaviorTest, LongPollingMaxAttemptsExceeded)
307342// SEP Test 15: Long-polling success (no delay)
308343TEST_F (RetryBehaviorTest, LongPollingSuccess)
309344{
310- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
311- StandardRetryStrategy strategy (quota, 10 , 0.05 );
345+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
346+ StandardRetryStrategy strategy (quota, 10 );
312347
313348 std::shared_ptr<HttpRequest> httpRequest = CreateHttpRequest (URI (" http://www.uri.com" ), HttpMethod::HTTP_GET , Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
314349 std::shared_ptr<HttpResponse> httpResponse = Aws::MakeShared<Standard::StandardHttpResponse>(ALLOCATION_TAG , httpRequest);
@@ -322,8 +357,8 @@ TEST_F(RetryBehaviorTest, LongPollingSuccess)
322357// SEP Test 16: Long-polling non-retryable error (no delay)
323358TEST_F (RetryBehaviorTest, LongPollingNonRetryableError)
324359{
325- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
326- StandardRetryStrategy strategy (quota, 10 , 0.05 );
360+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
361+ StandardRetryStrategy strategy (quota, 10 );
327362
328363 // Non-retryable error: ShouldRetry returns false
329364 ASSERT_FALSE (strategy.ShouldRetry (MakeNonRetryableError (), 0 ));
@@ -334,8 +369,8 @@ TEST_F(RetryBehaviorTest, LongPollingNonRetryableError)
334369// SEP Test 17: retry-after header honored
335370TEST_F (RetryBehaviorTest, RetryAfterHeaderHonored)
336371{
337- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
338- StandardRetryStrategy strategy (quota, 10 , 0.05 );
372+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
373+ StandardRetryStrategy strategy (quota, 10 );
339374
340375 // Header value 1500ms, at i=0 t_i is in [0, 50ms]
341376 // clamped to max(t_i, min(1.5, 5 + t_i)) = 1.5s = 1500ms
@@ -349,8 +384,8 @@ TEST_F(RetryBehaviorTest, RetryAfterHeaderHonored)
349384// SEP Test 18: retry-after floor clamped (value 0 clamped up to t_i)
350385TEST_F (RetryBehaviorTest, RetryAfterFloorClamped)
351386{
352- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
353- StandardRetryStrategy strategy (quota, 10 , 0.05 );
387+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
388+ StandardRetryStrategy strategy (quota, 10 );
354389
355390 auto error = MakeTransientErrorWithRetryAfter (" 0" );
356391 // Header is 0ms, gets clamped up to t_i
@@ -363,8 +398,8 @@ TEST_F(RetryBehaviorTest, RetryAfterFloorClamped)
363398// SEP Test 19: retry-after ceiling clamped (value 10000ms clamped to 5+t_i)
364399TEST_F (RetryBehaviorTest, RetryAfterCeilingClamped)
365400{
366- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
367- StandardRetryStrategy strategy (quota, 10 , 0.05 );
401+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
402+ StandardRetryStrategy strategy (quota, 10 );
368403
369404 auto error = MakeTransientErrorWithRetryAfter (" 10000" );
370405 // Header is 10000ms = 10s, exceeds 5 + t_i (max ~5.05s at i=0)
@@ -378,8 +413,8 @@ TEST_F(RetryBehaviorTest, RetryAfterCeilingClamped)
378413// SEP Test 20: Invalid retry-after falls back to exponential backoff
379414TEST_F (RetryBehaviorTest, InvalidRetryAfterFallsBack)
380415{
381- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
382- StandardRetryStrategy strategy (quota, 10 , 0.05 );
416+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
417+ StandardRetryStrategy strategy (quota, 10 );
383418
384419 // "abc" parses to 0 via atoll, which gets clamped to t_i
385420 auto error = MakeTransientErrorWithRetryAfter (" abc" );
@@ -411,8 +446,8 @@ TEST_F(RetryBehaviorTest, LegacyBehaviorUnchanged)
411446// Verify throttle-based classification: throttling costs 5, transient costs 14
412447TEST_F (RetryBehaviorTest, ThrottleBasedClassification)
413448{
414- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
415- StandardRetryStrategy strategy (quota, 10 , 0.05 );
449+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
450+ StandardRetryStrategy strategy (quota, 10 );
416451
417452 // Transient costs 14
418453 ASSERT_TRUE (strategy.ShouldRetry (MakeTransientError (), 0 ));
@@ -443,8 +478,8 @@ TEST_F(RetryBehaviorTest, LegacyClassification)
443478// Non-retryable errors are not retried regardless of gate
444479TEST_F (RetryBehaviorTest, NonRetryableNotRetried)
445480{
446- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
447- StandardRetryStrategy strategy (quota, 10 , 0.05 );
481+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
482+ StandardRetryStrategy strategy (quota, 10 );
448483
449484 ASSERT_FALSE (strategy.ShouldRetry (MakeNonRetryableError (), 0 ));
450485 ASSERT_EQ (500 , quota->GetRetryQuota ());
@@ -453,8 +488,8 @@ TEST_F(RetryBehaviorTest, NonRetryableNotRetried)
453488// Verify RequestBookkeeping releases correct tokens on success
454489TEST_F (RetryBehaviorTest, RequestBookkeepingReleasesTokens)
455490{
456- auto quota = Aws::MakeShared<DefaultRetryQuotaContainer >(ALLOCATION_TAG , 14 , 5 , RetryCostClassification:: THROTTLE_BASED );
457- StandardRetryStrategy strategy (quota, 10 , 0.05 );
491+ auto quota = Aws::MakeShared<TestThrottleBasedQuotaContainer >(ALLOCATION_TAG );
492+ StandardRetryStrategy strategy (quota, 10 );
458493
459494 std::shared_ptr<HttpRequest> httpRequest = CreateHttpRequest (URI (" http://www.uri.com" ), HttpMethod::HTTP_GET , Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
460495 std::shared_ptr<HttpResponse> httpResponse = Aws::MakeShared<Standard::StandardHttpResponse>(ALLOCATION_TAG , httpRequest);
0 commit comments