From 4fe9c359c70400337a115c647721b43148a5b56a Mon Sep 17 00:00:00 2001 From: Kunal Vaswani Date: Fri, 24 Apr 2026 10:42:47 +0530 Subject: [PATCH 1/3] ASP-2043: Add serviceConfig support to GrpcChannelConfig for gRPC built-in retry - Add optional serviceConfig field to GrpcChannelConfig - Wire defaultServiceConfig() + enableRetry() in GrpcChannelRegistry when serviceConfig is set - Add test verifying channels with different service configs are cached independently Co-Authored-By: Claude Sonnet 4.6 AI-Session-Id: 75f3864c-dc2c-4e44-b4f3-b23c980f2ea6 AI-Tool: claude-code AI-Model: unknown --- .../core/grpcutils/client/GrpcChannelConfig.java | 3 +++ .../grpcutils/client/GrpcChannelRegistry.java | 3 +++ .../client/GrpcChannelRegistryTest.java | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java index e3b866c..9c90930 100644 --- a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java @@ -2,6 +2,7 @@ import io.grpc.ClientInterceptor; import java.util.List; +import java.util.Map; import lombok.Builder; import lombok.Singular; import lombok.Value; @@ -12,4 +13,6 @@ public class GrpcChannelConfig { Integer maxInboundMessageSize; @Singular List clientInterceptors; + + Map serviceConfig; } diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java index 618a104..86cda90 100644 --- a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java @@ -90,6 +90,9 @@ protected ManagedChannel configureAndBuildChannel( if (config.getMaxInboundMessageSize() != null) { builder.maxInboundMessageSize(config.getMaxInboundMessageSize()); } + if (config.getServiceConfig() != null) { + builder.defaultServiceConfig(config.getServiceConfig()).enableRetry(); + } this.registryConfig.getDefaultInterceptors().forEach(builder::intercept); return builder.intercept(config.getClientInterceptors()).build(); } diff --git a/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java index 7a2d7b9..0b82688 100644 --- a/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java +++ b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java @@ -23,6 +23,7 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -145,6 +146,21 @@ void copyConstructorReusesExistingChannels() { assertSame(firstChannel, new GrpcChannelRegistry(firstRegistry).forSecureAddress("foo", 1000)); } + @Test + void createsDistinctChannelsForDifferentServiceConfigs() { + Map serviceConfig = Map.of("methodConfig", List.of()); + Channel channelWithServiceConfig = + this.channelRegistry.forSecureAddress( + "foo", 1000, GrpcChannelConfig.builder().serviceConfig(serviceConfig).build()); + + assertNotNull(channelWithServiceConfig); + assertNotSame(channelWithServiceConfig, this.channelRegistry.forSecureAddress("foo", 1000)); + assertSame( + channelWithServiceConfig, + this.channelRegistry.forSecureAddress( + "foo", 1000, GrpcChannelConfig.builder().serviceConfig(serviceConfig).build())); + } + @Test void registersRegistryInterceptors() { try (MockedStatic mockedBuilderStatic = From 299a909c92b10f19c2e988ee171d2ebfc01059f1 Mon Sep 17 00:00:00 2001 From: Kunal Vaswani Date: Fri, 24 Apr 2026 18:40:28 +0530 Subject: [PATCH 2/3] ASP-2043: Type serviceConfig in GrpcChannelConfig using GrpcServiceConfig and GrpcRetryPolicy Co-Authored-By: Claude Sonnet 4.6 AI-Session-Id: 75f3864c-dc2c-4e44-b4f3-b23c980f2ea6 AI-Tool: claude-code AI-Model: unknown --- .../grpcutils/client/GrpcChannelConfig.java | 3 +- .../grpcutils/client/GrpcChannelRegistry.java | 2 +- .../grpcutils/client/GrpcRetryPolicy.java | 41 +++++++++++++++++++ .../grpcutils/client/GrpcServiceConfig.java | 21 ++++++++++ .../client/GrpcChannelRegistryTest.java | 15 ++++++- 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java create mode 100644 grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcServiceConfig.java diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java index 9c90930..17f9091 100644 --- a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java @@ -2,7 +2,6 @@ import io.grpc.ClientInterceptor; import java.util.List; -import java.util.Map; import lombok.Builder; import lombok.Singular; import lombok.Value; @@ -14,5 +13,5 @@ public class GrpcChannelConfig { @Singular List clientInterceptors; - Map serviceConfig; + GrpcServiceConfig serviceConfig; } diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java index 86cda90..2a97564 100644 --- a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java @@ -91,7 +91,7 @@ protected ManagedChannel configureAndBuildChannel( builder.maxInboundMessageSize(config.getMaxInboundMessageSize()); } if (config.getServiceConfig() != null) { - builder.defaultServiceConfig(config.getServiceConfig()).enableRetry(); + builder.defaultServiceConfig(config.getServiceConfig().toMap()).enableRetry(); } this.registryConfig.getDefaultInterceptors().forEach(builder::intercept); return builder.intercept(config.getClientInterceptors()).build(); diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java new file mode 100644 index 0000000..754f787 --- /dev/null +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java @@ -0,0 +1,41 @@ +package org.hypertrace.core.grpcutils.client; + +import static java.util.stream.Collectors.toUnmodifiableList; + +import io.grpc.Status; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import lombok.Builder; +import lombok.Singular; +import lombok.Value; + +@Value +@Builder +public class GrpcRetryPolicy { + private static final String MAX_ATTEMPTS = "maxAttempts"; + private static final String INITIAL_BACKOFF = "initialBackoff"; + private static final String MAX_BACKOFF = "maxBackoff"; + private static final String BACKOFF_MULTIPLIER = "backoffMultiplier"; + private static final String RETRYABLE_STATUS_CODES = "retryableStatusCodes"; + + int maxAttempts; + Duration initialBackoff; + Duration maxBackoff; + double backoffMultiplier; + @Singular List retryableStatusCodes; + + Map toMap() { + return Map.of( + MAX_ATTEMPTS, + (double) maxAttempts, + INITIAL_BACKOFF, + initialBackoff.toMillis() / 1000.0 + "s", + MAX_BACKOFF, + maxBackoff.toMillis() / 1000.0 + "s", + BACKOFF_MULTIPLIER, + backoffMultiplier, + RETRYABLE_STATUS_CODES, + retryableStatusCodes.stream().map(Enum::name).collect(toUnmodifiableList())); + } +} diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcServiceConfig.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcServiceConfig.java new file mode 100644 index 0000000..fbb1b9b --- /dev/null +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcServiceConfig.java @@ -0,0 +1,21 @@ +package org.hypertrace.core.grpcutils.client; + +import java.util.List; +import java.util.Map; +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class GrpcServiceConfig { + private static final String METHOD_CONFIG = "methodConfig"; + private static final String NAME = "name"; + private static final String RETRY_POLICY = "retryPolicy"; + + GrpcRetryPolicy retryPolicy; + + Map toMap() { + return Map.of( + METHOD_CONFIG, List.of(Map.of(NAME, List.of(Map.of()), RETRY_POLICY, retryPolicy.toMap()))); + } +} diff --git a/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java index 0b82688..d5ee418 100644 --- a/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java +++ b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java @@ -22,8 +22,9 @@ import io.grpc.Deadline.Ticker; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; +import io.grpc.Status; +import java.time.Duration; import java.util.List; -import java.util.Map; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -148,7 +149,17 @@ void copyConstructorReusesExistingChannels() { @Test void createsDistinctChannelsForDifferentServiceConfigs() { - Map serviceConfig = Map.of("methodConfig", List.of()); + GrpcServiceConfig serviceConfig = + GrpcServiceConfig.builder() + .retryPolicy( + GrpcRetryPolicy.builder() + .maxAttempts(3) + .initialBackoff(Duration.ofMillis(100)) + .maxBackoff(Duration.ofSeconds(1)) + .backoffMultiplier(2.0) + .retryableStatusCode(Status.Code.UNAVAILABLE) + .build()) + .build(); Channel channelWithServiceConfig = this.channelRegistry.forSecureAddress( "foo", 1000, GrpcChannelConfig.builder().serviceConfig(serviceConfig).build()); From 9aa00a11d377df62f5c7ee0d010cbecdb8951566 Mon Sep 17 00:00:00 2001 From: Kunal Vaswani Date: Fri, 24 Apr 2026 19:22:20 +0530 Subject: [PATCH 3/3] ASP-2043: Address PR review comments - typed Duration, enableRetry opt-in, in-process retry test Co-Authored-By: Claude Sonnet 4.6 AI-Session-Id: 75f3864c-dc2c-4e44-b4f3-b23c980f2ea6 AI-Tool: claude-code AI-Model: unknown --- grpc-circuitbreaker-utils/gradle.lockfile | 4 +- grpc-client-rx-utils/gradle.lockfile | 4 +- grpc-client-utils/build.gradle.kts | 2 + grpc-client-utils/gradle.lockfile | 51 +++++----- .../grpcutils/client/GrpcChannelConfig.java | 2 + .../grpcutils/client/GrpcChannelRegistry.java | 5 +- .../grpcutils/client/GrpcRetryPolicy.java | 15 ++- .../client/GrpcChannelRegistryRetryTest.java | 94 +++++++++++++++++++ .../client/GrpcChannelRegistryTest.java | 27 ------ grpc-context-utils/gradle.lockfile | 4 +- grpc-server-rx-utils/gradle.lockfile | 4 +- grpc-server-utils/gradle.lockfile | 4 +- grpc-validation-utils/gradle.lockfile | 4 +- 13 files changed, 149 insertions(+), 71 deletions(-) create mode 100644 grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryRetryTest.java diff --git a/grpc-circuitbreaker-utils/gradle.lockfile b/grpc-circuitbreaker-utils/gradle.lockfile index 89e611d..7c54541 100644 --- a/grpc-circuitbreaker-utils/gradle.lockfile +++ b/grpc-circuitbreaker-utils/gradle.lockfile @@ -27,7 +27,7 @@ io.grpc:grpc-api:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,t io.grpc:grpc-bom:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-context:1.75.0=runtimeClasspath,testRuntimeClasspath io.grpc:grpc-core:1.75.0=runtimeClasspath,testRuntimeClasspath -io.netty:netty-bom:4.1.130.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-bom:4.1.132.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.perfmark:perfmark-api:0.27.0=runtimeClasspath,testRuntimeClasspath io.vavr:vavr-match:0.10.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.vavr:vavr:0.10.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath @@ -39,7 +39,7 @@ org.checkerframework:checker-qual:3.33.0=compileClasspath,testCompileClasspath org.checkerframework:checker-qual:3.43.0=runtimeClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.24=runtimeClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.26=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hypertrace.bom:hypertrace-bom:0.3.73=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hypertrace.bom:hypertrace-bom:0.3.76=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hypertrace.core.kafkastreams.framework:kafka-bom:0.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath org.junit.jupiter:junit-jupiter-api:5.10.1=testRuntimeClasspath diff --git a/grpc-client-rx-utils/gradle.lockfile b/grpc-client-rx-utils/gradle.lockfile index fc6dbdd..6e3769e 100644 --- a/grpc-client-rx-utils/gradle.lockfile +++ b/grpc-client-rx-utils/gradle.lockfile @@ -21,7 +21,7 @@ io.grpc:grpc-bom:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,t io.grpc:grpc-context:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-core:1.75.0=runtimeClasspath,testRuntimeClasspath io.grpc:grpc-stub:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-bom:4.1.130.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-bom:4.1.132.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.perfmark:perfmark-api:0.27.0=runtimeClasspath,testRuntimeClasspath io.reactivex.rxjava3:rxjava:3.1.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy-agent:1.14.10=testCompileClasspath,testRuntimeClasspath @@ -30,7 +30,7 @@ org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath org.checkerframework:checker-qual:3.43.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.24=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.26=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hypertrace.bom:hypertrace-bom:0.3.73=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hypertrace.bom:hypertrace-bom:0.3.76=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hypertrace.core.kafkastreams.framework:kafka-bom:0.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath org.junit.jupiter:junit-jupiter-api:5.10.1=testRuntimeClasspath diff --git a/grpc-client-utils/build.gradle.kts b/grpc-client-utils/build.gradle.kts index 95364a5..530a934 100644 --- a/grpc-client-utils/build.gradle.kts +++ b/grpc-client-utils/build.gradle.kts @@ -14,12 +14,14 @@ dependencies { implementation(projects.grpcContextUtils) implementation(commonLibs.slf4j2.api) implementation(commonLibs.grpc.core) + implementation(commonLibs.protobuf.javautil) annotationProcessor(commonLibs.lombok) compileOnly(commonLibs.lombok) testImplementation(commonLibs.junit.jupiter) testImplementation(commonLibs.mockito.core) + testImplementation(commonLibs.grpc.stub) testRuntimeOnly(commonLibs.grpc.netty) } diff --git a/grpc-client-utils/gradle.lockfile b/grpc-client-utils/gradle.lockfile index 616609a..beeec55 100644 --- a/grpc-client-utils/gradle.lockfile +++ b/grpc-client-utils/gradle.lockfile @@ -9,40 +9,47 @@ com.fasterxml.jackson.core:jackson-databind:2.21.1=runtimeClasspath,testRuntimeC com.fasterxml.jackson:jackson-bom:2.21.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath com.google.android:annotations:4.1.1.4=runtimeClasspath,testRuntimeClasspath com.google.code.findbugs:jsr305:3.0.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -com.google.code.gson:gson:2.13.1=runtimeClasspath,testRuntimeClasspath -com.google.errorprone:error_prone_annotations:2.30.0=compileClasspath,testCompileClasspath -com.google.errorprone:error_prone_annotations:2.38.0=runtimeClasspath,testRuntimeClasspath -com.google.guava:failureaccess:1.0.2=runtimeClasspath,testRuntimeClasspath -com.google.guava:guava:33.3.1-android=runtimeClasspath,testRuntimeClasspath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=runtimeClasspath,testRuntimeClasspath -com.google.j2objc:j2objc-annotations:3.0.0=runtimeClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:failureaccess:1.0.1=compileClasspath +com.google.guava:failureaccess:1.0.2=runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:guava-parent:32.1.2-jre=compileClasspath +com.google.guava:guava:32.1.2-jre=compileClasspath +com.google.guava:guava:33.3.1-android=runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.j2objc:j2objc-annotations:2.8=compileClasspath +com.google.j2objc:j2objc-annotations:3.0.0=runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.protobuf:protobuf-java-util:3.25.8=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.google.protobuf:protobuf-java:3.25.8=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-api:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-bom:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-context:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-core:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-inprocess:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-netty:1.75.0=testRuntimeClasspath +io.grpc:grpc-stub:1.75.0=testCompileClasspath,testRuntimeClasspath io.grpc:grpc-util:1.75.0=testRuntimeClasspath -io.netty:netty-bom:4.1.130.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-buffer:4.1.130.Final=testRuntimeClasspath -io.netty:netty-codec-http2:4.1.130.Final=testRuntimeClasspath -io.netty:netty-codec-http:4.1.130.Final=testRuntimeClasspath -io.netty:netty-codec-socks:4.1.130.Final=testRuntimeClasspath -io.netty:netty-codec:4.1.130.Final=testRuntimeClasspath -io.netty:netty-common:4.1.130.Final=testRuntimeClasspath -io.netty:netty-handler-proxy:4.1.130.Final=testRuntimeClasspath -io.netty:netty-handler:4.1.130.Final=testRuntimeClasspath -io.netty:netty-resolver:4.1.130.Final=testRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.1.130.Final=testRuntimeClasspath -io.netty:netty-transport:4.1.130.Final=testRuntimeClasspath +io.netty:netty-bom:4.1.132.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-buffer:4.1.132.Final=testRuntimeClasspath +io.netty:netty-codec-http2:4.1.132.Final=testRuntimeClasspath +io.netty:netty-codec-http:4.1.132.Final=testRuntimeClasspath +io.netty:netty-codec-socks:4.1.132.Final=testRuntimeClasspath +io.netty:netty-codec:4.1.132.Final=testRuntimeClasspath +io.netty:netty-common:4.1.132.Final=testRuntimeClasspath +io.netty:netty-handler-proxy:4.1.132.Final=testRuntimeClasspath +io.netty:netty-handler:4.1.132.Final=testRuntimeClasspath +io.netty:netty-resolver:4.1.132.Final=testRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.1.132.Final=testRuntimeClasspath +io.netty:netty-transport:4.1.132.Final=testRuntimeClasspath io.perfmark:perfmark-api:0.27.0=runtimeClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy-agent:1.14.10=testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.14.10=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath -org.checkerframework:checker-qual:3.43.0=runtimeClasspath,testRuntimeClasspath -org.codehaus.mojo:animal-sniffer-annotations:1.24=runtimeClasspath,testRuntimeClasspath +org.checkerframework:checker-qual:3.33.0=compileClasspath +org.checkerframework:checker-qual:3.43.0=runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.24=runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.26=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hypertrace.bom:hypertrace-bom:0.3.73=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hypertrace.bom:hypertrace-bom:0.3.76=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hypertrace.core.kafkastreams.framework:kafka-bom:0.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.10.0=testRuntimeClasspath diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java index 17f9091..b7cf7c1 100644 --- a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java @@ -14,4 +14,6 @@ public class GrpcChannelConfig { @Singular List clientInterceptors; GrpcServiceConfig serviceConfig; + + boolean enableRetry; } diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java index 2a97564..c2fdd1e 100644 --- a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java @@ -91,7 +91,10 @@ protected ManagedChannel configureAndBuildChannel( builder.maxInboundMessageSize(config.getMaxInboundMessageSize()); } if (config.getServiceConfig() != null) { - builder.defaultServiceConfig(config.getServiceConfig().toMap()).enableRetry(); + builder.defaultServiceConfig(config.getServiceConfig().toMap()); + } + if (config.isEnableRetry()) { + builder.enableRetry(); } this.registryConfig.getDefaultInterceptors().forEach(builder::intercept); return builder.intercept(config.getClientInterceptors()).build(); diff --git a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java index 754f787..2c1e7b2 100644 --- a/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java +++ b/grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcRetryPolicy.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toUnmodifiableList; +import com.google.protobuf.util.Durations; import io.grpc.Status; import java.time.Duration; import java.util.List; @@ -27,15 +28,11 @@ public class GrpcRetryPolicy { Map toMap() { return Map.of( - MAX_ATTEMPTS, - (double) maxAttempts, - INITIAL_BACKOFF, - initialBackoff.toMillis() / 1000.0 + "s", - MAX_BACKOFF, - maxBackoff.toMillis() / 1000.0 + "s", - BACKOFF_MULTIPLIER, - backoffMultiplier, + MAX_ATTEMPTS, (double) maxAttempts, + INITIAL_BACKOFF, Durations.toString(Durations.fromMillis(initialBackoff.toMillis())), + MAX_BACKOFF, Durations.toString(Durations.fromMillis(maxBackoff.toMillis())), + BACKOFF_MULTIPLIER, backoffMultiplier, RETRYABLE_STATUS_CODES, - retryableStatusCodes.stream().map(Enum::name).collect(toUnmodifiableList())); + retryableStatusCodes.stream().map(Enum::name).collect(toUnmodifiableList())); } } diff --git a/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryRetryTest.java b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryRetryTest.java new file mode 100644 index 0000000..dacb4ec --- /dev/null +++ b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryRetryTest.java @@ -0,0 +1,94 @@ +package org.hypertrace.core.grpcutils.client; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.grpc.CallOptions; +import io.grpc.ManagedChannel; +import io.grpc.MethodDescriptor; +import io.grpc.MethodDescriptor.MethodType; +import io.grpc.Server; +import io.grpc.ServerServiceDefinition; +import io.grpc.Status; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.stub.ClientCalls; +import io.grpc.stub.ServerCalls; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class GrpcChannelRegistryRetryTest { + + @Test + void retriesOnUnavailableWhenRetryPolicyConfigured() throws Exception { + String serverName = UUID.randomUUID().toString(); + AtomicInteger callCount = new AtomicInteger(0); + MethodDescriptor method = + MethodDescriptor.newBuilder() + .setType(MethodType.UNARY) + .setFullMethodName("test.Service/Call") + .setRequestMarshaller(inputStreamMarshaller()) + .setResponseMarshaller(inputStreamMarshaller()) + .build(); + + // Fail first 2 attempts with UNAVAILABLE, succeed on 3rd + Server server = + InProcessServerBuilder.forName(serverName) + .addService( + ServerServiceDefinition.builder("test.Service") + .addMethod( + method, + ServerCalls.asyncUnaryCall( + (request, responseObserver) -> { + if (callCount.incrementAndGet() < 3) { + responseObserver.onError(Status.UNAVAILABLE.asRuntimeException()); + } else { + responseObserver.onNext(new byte[0]); + responseObserver.onCompleted(); + } + })) + .build()) + .build() + .start(); + + GrpcServiceConfig serviceConfig = + GrpcServiceConfig.builder() + .retryPolicy( + GrpcRetryPolicy.builder() + .maxAttempts(3) + .initialBackoff(Duration.ofMillis(10)) + .maxBackoff(Duration.ofMillis(100)) + .backoffMultiplier(2.0) + .retryableStatusCode(Status.Code.UNAVAILABLE) + .build()) + .build(); + + InProcessGrpcChannelRegistry registry = new InProcessGrpcChannelRegistry(); + ManagedChannel channel = + registry.forName( + serverName, + GrpcChannelConfig.builder().serviceConfig(serviceConfig).enableRetry(true).build()); + + ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, new byte[0]); + + assertEquals(3, callCount.get()); + server.shutdown(); + registry.shutdown(); + } + + private static MethodDescriptor.Marshaller inputStreamMarshaller() { + return new MethodDescriptor.Marshaller<>() { + @Override + public InputStream stream(byte[] value) { + return new ByteArrayInputStream(value); + } + + @Override + public byte[] parse(InputStream stream) { + return new byte[0]; + } + }; + } +} diff --git a/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java index d5ee418..7a2d7b9 100644 --- a/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java +++ b/grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java @@ -22,8 +22,6 @@ import io.grpc.Deadline.Ticker; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; -import io.grpc.Status; -import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; @@ -147,31 +145,6 @@ void copyConstructorReusesExistingChannels() { assertSame(firstChannel, new GrpcChannelRegistry(firstRegistry).forSecureAddress("foo", 1000)); } - @Test - void createsDistinctChannelsForDifferentServiceConfigs() { - GrpcServiceConfig serviceConfig = - GrpcServiceConfig.builder() - .retryPolicy( - GrpcRetryPolicy.builder() - .maxAttempts(3) - .initialBackoff(Duration.ofMillis(100)) - .maxBackoff(Duration.ofSeconds(1)) - .backoffMultiplier(2.0) - .retryableStatusCode(Status.Code.UNAVAILABLE) - .build()) - .build(); - Channel channelWithServiceConfig = - this.channelRegistry.forSecureAddress( - "foo", 1000, GrpcChannelConfig.builder().serviceConfig(serviceConfig).build()); - - assertNotNull(channelWithServiceConfig); - assertNotSame(channelWithServiceConfig, this.channelRegistry.forSecureAddress("foo", 1000)); - assertSame( - channelWithServiceConfig, - this.channelRegistry.forSecureAddress( - "foo", 1000, GrpcChannelConfig.builder().serviceConfig(serviceConfig).build())); - } - @Test void registersRegistryInterceptors() { try (MockedStatic mockedBuilderStatic = diff --git a/grpc-context-utils/gradle.lockfile b/grpc-context-utils/gradle.lockfile index 6086a48..34205b5 100644 --- a/grpc-context-utils/gradle.lockfile +++ b/grpc-context-utils/gradle.lockfile @@ -24,7 +24,7 @@ io.grpc:grpc-api:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,t io.grpc:grpc-bom:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-context:1.75.0=runtimeClasspath,testRuntimeClasspath io.grpc:grpc-core:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-bom:4.1.130.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-bom:4.1.132.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.perfmark:perfmark-api:0.27.0=runtimeClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy-agent:1.14.10=testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.14.10=testCompileClasspath,testRuntimeClasspath @@ -33,7 +33,7 @@ org.checkerframework:checker-qual:3.33.0=compileClasspath,testCompileClasspath org.checkerframework:checker-qual:3.43.0=runtimeClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.24=runtimeClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.26=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hypertrace.bom:hypertrace-bom:0.3.73=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hypertrace.bom:hypertrace-bom:0.3.76=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hypertrace.core.kafkastreams.framework:kafka-bom:0.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath org.junit.jupiter:junit-jupiter-api:5.10.1=testRuntimeClasspath diff --git a/grpc-server-rx-utils/gradle.lockfile b/grpc-server-rx-utils/gradle.lockfile index edb02b6..45d2b53 100644 --- a/grpc-server-rx-utils/gradle.lockfile +++ b/grpc-server-rx-utils/gradle.lockfile @@ -11,7 +11,7 @@ com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,runtimeClasspath,tes io.grpc:grpc-api:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-bom:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-stub:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-bom:4.1.130.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-bom:4.1.132.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.reactivex.rxjava3:rxjava:3.1.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy-agent:1.14.10=testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.14.10=testCompileClasspath,testRuntimeClasspath @@ -19,7 +19,7 @@ org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath org.checkerframework:checker-qual:3.43.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.24=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.26=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hypertrace.bom:hypertrace-bom:0.3.73=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hypertrace.bom:hypertrace-bom:0.3.76=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hypertrace.core.kafkastreams.framework:kafka-bom:0.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath org.junit.jupiter:junit-jupiter-api:5.10.1=testRuntimeClasspath diff --git a/grpc-server-utils/gradle.lockfile b/grpc-server-utils/gradle.lockfile index cf3c7fa..08a5e50 100644 --- a/grpc-server-utils/gradle.lockfile +++ b/grpc-server-utils/gradle.lockfile @@ -20,7 +20,7 @@ io.grpc:grpc-api:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,t io.grpc:grpc-bom:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-context:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-core:1.75.0=runtimeClasspath,testRuntimeClasspath -io.netty:netty-bom:4.1.130.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-bom:4.1.132.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.perfmark:perfmark-api:0.27.0=runtimeClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy-agent:1.14.10=testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.14.10=testCompileClasspath,testRuntimeClasspath @@ -28,7 +28,7 @@ org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath org.checkerframework:checker-qual:3.43.0=runtimeClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.24=runtimeClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.26=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hypertrace.bom:hypertrace-bom:0.3.73=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hypertrace.bom:hypertrace-bom:0.3.76=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hypertrace.core.kafkastreams.framework:kafka-bom:0.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath org.junit.jupiter:junit-jupiter-api:5.10.1=testRuntimeClasspath diff --git a/grpc-validation-utils/gradle.lockfile b/grpc-validation-utils/gradle.lockfile index 6f6f052..c98989d 100644 --- a/grpc-validation-utils/gradle.lockfile +++ b/grpc-validation-utils/gradle.lockfile @@ -25,13 +25,13 @@ io.grpc:grpc-api:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,t io.grpc:grpc-bom:1.75.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.grpc:grpc-context:1.75.0=runtimeClasspath,testRuntimeClasspath io.grpc:grpc-core:1.75.0=runtimeClasspath,testRuntimeClasspath -io.netty:netty-bom:4.1.130.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-bom:4.1.132.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.perfmark:perfmark-api:0.27.0=runtimeClasspath,testRuntimeClasspath org.checkerframework:checker-qual:3.33.0=compileClasspath,testCompileClasspath org.checkerframework:checker-qual:3.43.0=runtimeClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.24=runtimeClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-bom:11.0.26=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hypertrace.bom:hypertrace-bom:0.3.73=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hypertrace.bom:hypertrace-bom:0.3.76=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hypertrace.core.kafkastreams.framework:kafka-bom:0.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.slf4j:slf4j-api:2.0.7=runtimeClasspath,testRuntimeClasspath empty=annotationProcessor