Skip to content

Commit ab90ef6

Browse files
authored
Add a virtual ShouldRetry method to the RetryPolicy for customization. (#5584)
* Add a virtual ShouldTry method to the RetryPolicy for customization. * Make test hooks virtual again. * Add unit test for ShouldRetry. * Fix typo. * Address PR feedback. * Revert making the mock'd functions private for non-test builds. * Add new line.
1 parent 3b9574c commit ab90ef6

File tree

3 files changed

+115
-6
lines changed

3 files changed

+115
-6
lines changed

sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,7 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
363363
/**
364364
* @brief HTTP retry policy.
365365
*/
366-
class RetryPolicy
367-
#if !defined(_azure_TESTING_BUILD)
368-
final
369-
#endif
370-
: public HttpPolicy {
366+
class RetryPolicy : public HttpPolicy {
371367
private:
372368
RetryOptions m_retryOptions;
373369

@@ -415,6 +411,26 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
415411
int32_t attempt,
416412
std::chrono::milliseconds& retryAfter,
417413
double jitterFactor = -1) const;
414+
415+
/**
416+
* @brief Overriding this method customizes the logic of when the RetryPolicy will re-attempt
417+
* a request, based on the returned HTTP response.
418+
*
419+
* @remark A null response pointer means there was no response received from the corresponding
420+
* request. Custom implementations of this method that override the retry behavior, should
421+
* handle that error case, if that needs to be customized.
422+
*
423+
* @remark Unless overriden, the default implementation is to always return true. The
424+
* non-retriable errors, including those specified in the RetryOptions, remain evaluated
425+
* before calling ShouldRetry.
426+
*
427+
* @param response An HTTP response returned corresponding to the request sent by the policy.
428+
* @param retryOptions The set of options provided to the RetryPolicy.
429+
* @return Whether or not the HTTP request should be sent again through the pipeline.
430+
*/
431+
virtual bool ShouldRetry(
432+
std::unique_ptr<RawResponse> const& response,
433+
RetryOptions const& retryOptions) const;
418434
};
419435

420436
/**

sdk/core/azure-core/src/http/retry_policy.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ int32_t RetryPolicy::GetRetryCount(Context const& context)
118118
return *ptr;
119119
}
120120

121+
bool RetryPolicy::ShouldRetry(std::unique_ptr<RawResponse> const&, RetryOptions const&) const
122+
{
123+
return true;
124+
}
125+
121126
std::unique_ptr<RawResponse> RetryPolicy::Send(
122127
Request& request,
123128
NextHttpPolicy nextPolicy,
@@ -141,13 +146,20 @@ std::unique_ptr<RawResponse> RetryPolicy::Send(
141146
auto response = nextPolicy.Send(request, retryContext);
142147

143148
// If we are out of retry attempts, if a response is non-retriable (or simply 200 OK, i.e
144-
// doesn't need to be retried), then ShouldRetry returns false.
149+
// doesn't need to be retried), then ShouldRetryOnResponse returns false.
145150
if (!ShouldRetryOnResponse(*response.get(), m_retryOptions, attempt, retryAfter))
146151
{
147152
// If this is the second attempt and StartTry was called, we need to stop it. Otherwise
148153
// trying to perform same request would use last retry query/headers
149154
return response;
150155
}
156+
157+
// Service SDKs can inject custom logic to define whether the request should be retried,
158+
// based on the response. The default is true.
159+
if (!ShouldRetry(response, m_retryOptions))
160+
{
161+
return response;
162+
}
151163
}
152164
catch (const TransportException& e)
153165
{

sdk/core/azure-core/test/ut/retry_policy_test.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,89 @@ class RetryPolicyTest final : public RetryPolicy {
124124
return m_shouldRetryOnResponse(response, retryOptions, attempt, retryAfter, jitterFactor);
125125
}
126126
};
127+
128+
class RetryPolicyTest_CustomRetry_False final : public RetryPolicy {
129+
public:
130+
RetryPolicyTest_CustomRetry_False(RetryOptions const& retryOptions) : RetryPolicy(retryOptions) {}
131+
132+
std::unique_ptr<HttpPolicy> Clone() const override
133+
{
134+
return std::make_unique<RetryPolicyTest_CustomRetry_False>(*this);
135+
}
136+
137+
protected:
138+
bool ShouldRetry(std::unique_ptr<RawResponse> const& response, RetryOptions const& retryOptions)
139+
const override
140+
{
141+
(void)response;
142+
(void)retryOptions;
143+
return false;
144+
}
145+
};
127146
} // namespace
128147

148+
TEST(RetryPolicy, ShouldRetry)
149+
{
150+
using namespace std::chrono_literals;
151+
RetryOptions const retryOptions{3, 10ms, 60s, {HttpStatusCode::Ok}};
152+
153+
// The default ShouldRetry implementation is to always return true,
154+
// which means we will retry up to the retry count in the options.
155+
{
156+
Azure::Core::Context context = Azure::Core::Context::ApplicationContext;
157+
auto policy = RetryPolicy(retryOptions);
158+
159+
RawResponse const* responsePtrSent = nullptr;
160+
int32_t retryCounter = -1;
161+
162+
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
163+
policies.emplace_back(std::make_unique<RetryPolicy>(policy));
164+
policies.emplace_back(std::make_unique<TestTransportPolicy>([&]() {
165+
auto response = std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Test");
166+
167+
responsePtrSent = response.get();
168+
retryCounter++;
169+
return response;
170+
}));
171+
172+
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
173+
174+
Request request(HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com"));
175+
pipeline.Send(request, context);
176+
177+
EXPECT_NE(responsePtrSent, nullptr);
178+
EXPECT_EQ(retryCounter, 3);
179+
}
180+
181+
// Overriding ShouldRetry to return false will mean we won't retry.
182+
{
183+
Azure::Core::Context context = Azure::Core::Context();
184+
auto policy = RetryPolicyTest_CustomRetry_False(retryOptions);
185+
186+
RawResponse const* responsePtrSent = nullptr;
187+
int32_t retryCounter = -1;
188+
189+
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
190+
policies.emplace_back(std::make_unique<RetryPolicyTest_CustomRetry_False>(policy));
191+
192+
policies.emplace_back(std::make_unique<TestTransportPolicy>([&]() {
193+
auto response = std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Test");
194+
195+
responsePtrSent = response.get();
196+
retryCounter++;
197+
return response;
198+
}));
199+
200+
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
201+
202+
Request request(HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com"));
203+
pipeline.Send(request, context);
204+
205+
EXPECT_NE(responsePtrSent, nullptr);
206+
EXPECT_EQ(retryCounter, 0);
207+
}
208+
}
209+
129210
TEST(RetryPolicy, ShouldRetryOnResponse)
130211
{
131212
using namespace std::chrono_literals;

0 commit comments

Comments
 (0)