From fc1ff50f4c6ad2678bf70c602190c2cae51504a9 Mon Sep 17 00:00:00 2001 From: Albert Peschar Date: Mon, 12 Oct 2026 13:26:57 +0000 Subject: [PATCH 1/2] Retry Wasabi TemporarilyUnavailable S3 errors --- .../src/aws-cpp-sdk-s3/source/S3Errors.cpp | 6 +++++ .../aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/generated/src/aws-cpp-sdk-s3/source/S3Errors.cpp b/generated/src/aws-cpp-sdk-s3/source/S3Errors.cpp index eb8a02f47233..951d6a35f60c 100644 --- a/generated/src/aws-cpp-sdk-s3/source/S3Errors.cpp +++ b/generated/src/aws-cpp-sdk-s3/source/S3Errors.cpp @@ -42,6 +42,8 @@ static const int ANNOTATION_NAME_TOO_LONG_HASH = HashingUtils::HashString("Annot static const int OBJECT_NOT_IN_ACTIVE_TIER_HASH = HashingUtils::HashString("ObjectNotInActiveTierError"); static const int BUCKET_ALREADY_EXISTS_HASH = HashingUtils::HashString("BucketAlreadyExists"); static const int INVALID_OBJECT_STATE_HASH = HashingUtils::HashString("InvalidObjectState"); +// Wasabi returns this code for transient storage-service failures. +static const int TEMPORARILY_UNAVAILABLE_HASH = HashingUtils::HashString("TemporarilyUnavailable"); AWSError GetErrorForName(const char* errorName) { int hashCode = HashingUtils::HashString(errorName); @@ -85,6 +87,10 @@ AWSError GetErrorForName(const char* errorName) { } else if (hashCode == INVALID_OBJECT_STATE_HASH) { return AWSError(static_cast(S3Errors::INVALID_OBJECT_STATE), RetryableType::NOT_RETRYABLE); } + else if (hashCode == TEMPORARILY_UNAVAILABLE_HASH) + { + return AWSError(CoreErrors::SERVICE_UNAVAILABLE, RetryableType::RETRYABLE); + } return AWSError(CoreErrors::UNKNOWN, false); } diff --git a/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp b/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp index 69c3ab632c67..09f7aab8369d 100644 --- a/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp +++ b/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -363,6 +364,32 @@ class MockRequest : public Aws::AmazonWebServiceRequest }; //Set http error and error in body in a way to hit generic xml error marshaller, which sets the exception name +TEST_F(S3UnitTest, TemporarilyUnavailableIsRetryable) { + auto mockRequest = Aws::MakeShared( + ALLOCATION_TAG, "https://mock.example/bucket/key", HttpMethod::HTTP_PUT); + mockRequest->SetResponseStreamFactory([]() -> IOStream* { + return Aws::New(ALLOCATION_TAG, + "TemporarilyUnavailable" + "Storage service is temporarily not available.", + std::ios_base::in | std::ios_base::binary); + }); + auto mockResponse = Aws::MakeShared(ALLOCATION_TAG, mockRequest); + mockResponse->SetResponseCode(HttpResponseCode::SERVICE_UNAVAILABLE); + + const auto error = S3ErrorMarshaller().BuildAWSError(mockResponse); + EXPECT_EQ(CoreErrors::SERVICE_UNAVAILABLE, error.GetErrorType()); + EXPECT_EQ("TemporarilyUnavailable", error.GetExceptionName()); + EXPECT_TRUE(error.ShouldRetry()); + EXPECT_TRUE(DefaultRetryStrategy().ShouldRetry(error, 0)); +} + +TEST_F(S3UnitTest, UnknownS3ErrorRemainsNonRetryable) { + const auto error = S3ErrorMapper::GetErrorForName("UnknownStorageError"); + EXPECT_EQ(CoreErrors::UNKNOWN, error.GetErrorType()); + EXPECT_FALSE(error.ShouldRetry()); + EXPECT_FALSE(S3ErrorMapper::GetErrorForName("NoSuchKey").ShouldRetry()); +} + TEST_F(S3UnitTest, S3EmbeddedErrorTestNonOKResponse) { auto mockRequest = Aws::MakeShared(ALLOCATION_TAG, "mockuri", HttpMethod::HTTP_GET); From cb38853b778eb50898099a14d55747a49e3cabb9 Mon Sep 17 00:00:00 2001 From: Albert Peschar Date: Mon, 12 Oct 2026 13:35:23 +0000 Subject: [PATCH 2/2] Exercise the XML response body in retry regression test --- tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp b/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp index 09f7aab8369d..6e49d5bc86ae 100644 --- a/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp +++ b/tests/aws-cpp-sdk-s3-unit-tests/S3UnitTests.cpp @@ -368,12 +368,12 @@ TEST_F(S3UnitTest, TemporarilyUnavailableIsRetryable) { auto mockRequest = Aws::MakeShared( ALLOCATION_TAG, "https://mock.example/bucket/key", HttpMethod::HTTP_PUT); mockRequest->SetResponseStreamFactory([]() -> IOStream* { - return Aws::New(ALLOCATION_TAG, - "TemporarilyUnavailable" - "Storage service is temporarily not available.", - std::ios_base::in | std::ios_base::binary); + return Aws::New(ALLOCATION_TAG); }); auto mockResponse = Aws::MakeShared(ALLOCATION_TAG, mockRequest); + mockResponse->GetResponseBody() + << "TemporarilyUnavailable" + "Storage service is temporarily not available."; mockResponse->SetResponseCode(HttpResponseCode::SERVICE_UNAVAILABLE); const auto error = S3ErrorMarshaller().BuildAWSError(mockResponse);