diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c213392..088e1b0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.4.12 ## +* Core: treat gRPC ResourceExhausted "message larger than max" as non-retryable + ## 2.4.11 ## * Table: fixed StackOverflow problem for the immediately released sessions diff --git a/core/src/main/java/tech/ydb/core/grpc/GrpcStatuses.java b/core/src/main/java/tech/ydb/core/grpc/GrpcStatuses.java index 4dbef235d..bd11bb045 100644 --- a/core/src/main/java/tech/ydb/core/grpc/GrpcStatuses.java +++ b/core/src/main/java/tech/ydb/core/grpc/GrpcStatuses.java @@ -26,7 +26,7 @@ public static tech.ydb.core.Status toStatus(io.grpc.Status status, String endpoi return Status.SUCCESS; } Issue message = Issue.of(getMessage(status, endpoint), Issue.Severity.ERROR); - StatusCode code = getStatusCode(status.getCode()); + StatusCode code = getStatusCode(status); Throwable cause = status.getCause(); if (cause == null) { @@ -49,16 +49,29 @@ private static String getMessage(io.grpc.Status status, String endpoint) { return message; } - private static StatusCode getStatusCode(io.grpc.Status.Code code) { - switch (code) { + private static StatusCode getStatusCode(io.grpc.Status status) { + switch (status.getCode()) { case UNAVAILABLE: return StatusCode.TRANSPORT_UNAVAILABLE; case UNAUTHENTICATED: return StatusCode.CLIENT_UNAUTHENTICATED; case CANCELLED: return StatusCode.CLIENT_CANCELLED; case UNIMPLEMENTED: return StatusCode.CLIENT_CALL_UNIMPLEMENTED; case DEADLINE_EXCEEDED: return StatusCode.CLIENT_DEADLINE_EXCEEDED; - case RESOURCE_EXHAUSTED: return StatusCode.CLIENT_RESOURCE_EXHAUSTED; + case RESOURCE_EXHAUSTED: + // A message that exceeds the gRPC size limit is a deterministic client-side error: + // retrying it with the same payload always fails identically. Map it to a non-retryable + // status so the caller fails fast instead of spinning through the retry budget. + // Other ResourceExhausted errors (e.g. quota or rate limiting) stay retryable. + if (isMessageLargerThanMax(status.getDescription())) { + return StatusCode.BAD_REQUEST; + } + return StatusCode.CLIENT_RESOURCE_EXHAUSTED; default: return StatusCode.CLIENT_GRPC_ERROR; } } + + private static boolean isMessageLargerThanMax(String description) { + return description != null && (description.contains("trying to send message larger than max") + || description.contains("received message larger than max")); + } } diff --git a/core/src/test/java/tech/ydb/core/grpc/GrpcStatusesTest.java b/core/src/test/java/tech/ydb/core/grpc/GrpcStatusesTest.java index cea07f59c..b95c46dfc 100644 --- a/core/src/test/java/tech/ydb/core/grpc/GrpcStatusesTest.java +++ b/core/src/test/java/tech/ydb/core/grpc/GrpcStatusesTest.java @@ -117,6 +117,34 @@ public void statusResourceExhausted() { assertEquals(Status.of(StatusCode.CLIENT_RESOURCE_EXHAUSTED).withIssues(issue), status); } + @Test + public void statusResourceExhaustedMessageTooLargeSend() { + Status status = GrpcStatuses.toStatus( + io.grpc.Status.RESOURCE_EXHAUSTED.withDescription("trying to send message larger than max"), "test"); + Issue issue = Issue.of( + "gRPC error: (RESOURCE_EXHAUSTED) on test, trying to send message larger than max", + Issue.Severity.ERROR); + assertEquals(Status.of(StatusCode.BAD_REQUEST).withIssues(issue), status); + } + + @Test + public void statusResourceExhaustedMessageTooLargeReceive() { + Status status = GrpcStatuses.toStatus( + io.grpc.Status.RESOURCE_EXHAUSTED.withDescription("received message larger than max"), "test"); + Issue issue = Issue.of( + "gRPC error: (RESOURCE_EXHAUSTED) on test, received message larger than max", + Issue.Severity.ERROR); + assertEquals(Status.of(StatusCode.BAD_REQUEST).withIssues(issue), status); + } + + @Test + public void statusResourceExhaustedOtherDescription() { + Status status = GrpcStatuses.toStatus( + io.grpc.Status.RESOURCE_EXHAUSTED.withDescription("quota exceeded"), "test"); + Issue issue = Issue.of("gRPC error: (RESOURCE_EXHAUSTED) on test, quota exceeded", Issue.Severity.ERROR); + assertEquals(Status.of(StatusCode.CLIENT_RESOURCE_EXHAUSTED).withIssues(issue), status); + } + @Test public void statusThrowable() { Throwable th = new RuntimeException("Hello");