From 3eacd5a730f351ae6bb429b6dd5aa57e95f55ea5 Mon Sep 17 00:00:00 2001 From: Albert Peschar Date: Mon, 12 Oct 2026 13:26:27 +0000 Subject: [PATCH] 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 f67e716642f8..bdbce0ea41a8 100644 --- a/generated/src/aws-cpp-sdk-s3/source/S3Errors.cpp +++ b/generated/src/aws-cpp-sdk-s3/source/S3Errors.cpp @@ -39,6 +39,8 @@ static const int NO_SUCH_KEY_HASH = HashingUtils::HashString("NoSuchKey"); 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"); // MinIO compatibility error hashes (all MinIO errors handled here for S3 compatibility) static const int MINIO_INTERNAL_ERROR_HASH = HashingUtils::HashString("InternalError"); static const int MINIO_SERVER_NOT_INITIALIZED_HASH = HashingUtils::HashString("XMinioServerNotInitialized"); @@ -138,6 +140,10 @@ AWSError GetErrorForName(const char* errorName) { return AWSError(static_cast(S3Errors::REQUEST_TIMEOUT), RetryableType::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 80bc663d3ede..8b4ac5da34f7 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 @@ -300,6 +301,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); + }); + 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); + 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);