Skip to content

Commit a545450

Browse files
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 <noreply@anthropic.com> AI-Session-Id: 75f3864c-dc2c-4e44-b4f3-b23c980f2ea6 AI-Tool: claude-code AI-Model: unknown
1 parent f236b97 commit a545450

3 files changed

Lines changed: 58 additions & 0 deletions

File tree

grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.grpc.ClientInterceptor;
44
import java.util.List;
5+
import java.util.Map;
56
import lombok.Builder;
67
import lombok.Singular;
78
import lombok.Value;
@@ -12,4 +13,6 @@ public class GrpcChannelConfig {
1213
Integer maxInboundMessageSize;
1314

1415
@Singular List<ClientInterceptor> clientInterceptors;
16+
17+
Map<String, Object> serviceConfig;
1518
}

grpc-client-utils/src/main/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistry.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ protected ManagedChannel configureAndBuildChannel(
9090
if (config.getMaxInboundMessageSize() != null) {
9191
builder.maxInboundMessageSize(config.getMaxInboundMessageSize());
9292
}
93+
if (config.getServiceConfig() != null) {
94+
builder.defaultServiceConfig(config.getServiceConfig()).enableRetry();
95+
}
9396
this.registryConfig.getDefaultInterceptors().forEach(builder::intercept);
9497
return builder.intercept(config.getClientInterceptors()).build();
9598
}

grpc-client-utils/src/test/java/org/hypertrace/core/grpcutils/client/GrpcChannelRegistryTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static org.mockito.ArgumentMatchers.anyString;
1212
import static org.mockito.Mockito.inOrder;
1313
import static org.mockito.Mockito.mock;
14+
import static org.mockito.Mockito.never;
1415
import static org.mockito.Mockito.times;
1516
import static org.mockito.Mockito.verify;
1617
import static org.mockito.Mockito.verifyNoInteractions;
@@ -23,6 +24,7 @@
2324
import io.grpc.ManagedChannel;
2425
import io.grpc.ManagedChannelBuilder;
2526
import java.util.List;
27+
import java.util.Map;
2628
import java.util.concurrent.TimeUnit;
2729
import org.junit.jupiter.api.BeforeEach;
2830
import org.junit.jupiter.api.Test;
@@ -145,6 +147,56 @@ void copyConstructorReusesExistingChannels() {
145147
assertSame(firstChannel, new GrpcChannelRegistry(firstRegistry).forSecureAddress("foo", 1000));
146148
}
147149

150+
@Test
151+
void appliesServiceConfigAndEnablesRetryWhenServiceConfigIsSet() {
152+
try (MockedStatic<ManagedChannelBuilder> mockedBuilderStatic =
153+
Mockito.mockStatic(ManagedChannelBuilder.class)) {
154+
ManagedChannelBuilder<?> mockBuilder = mock(ManagedChannelBuilder.class);
155+
ManagedChannel mockChannel = mock(ManagedChannel.class);
156+
157+
mockedBuilderStatic
158+
.when(() -> ManagedChannelBuilder.forAddress("foo", 1000))
159+
.thenAnswer(
160+
invocation -> {
161+
when(mockBuilder.intercept(anyList())).then(RETURNS_SELF);
162+
when(mockBuilder.defaultServiceConfig(any())).then(RETURNS_SELF);
163+
when(mockBuilder.enableRetry()).then(RETURNS_SELF);
164+
when(mockBuilder.build()).thenReturn(mockChannel);
165+
return mockBuilder;
166+
});
167+
168+
Map<String, Object> serviceConfig = Map.of("methodConfig", List.of());
169+
this.channelRegistry.forPlaintextAddress(
170+
"foo", 1000, GrpcChannelConfig.builder().serviceConfig(serviceConfig).build());
171+
172+
verify(mockBuilder, times(1)).defaultServiceConfig(serviceConfig);
173+
verify(mockBuilder, times(1)).enableRetry();
174+
}
175+
}
176+
177+
@Test
178+
void doesNotApplyServiceConfigOrEnableRetryWhenServiceConfigIsAbsent() {
179+
try (MockedStatic<ManagedChannelBuilder> mockedBuilderStatic =
180+
Mockito.mockStatic(ManagedChannelBuilder.class)) {
181+
ManagedChannelBuilder<?> mockBuilder = mock(ManagedChannelBuilder.class);
182+
ManagedChannel mockChannel = mock(ManagedChannel.class);
183+
184+
mockedBuilderStatic
185+
.when(() -> ManagedChannelBuilder.forAddress("foo", 1000))
186+
.thenAnswer(
187+
invocation -> {
188+
when(mockBuilder.intercept(anyList())).then(RETURNS_SELF);
189+
when(mockBuilder.build()).thenReturn(mockChannel);
190+
return mockBuilder;
191+
});
192+
193+
this.channelRegistry.forPlaintextAddress("foo", 1000);
194+
195+
verify(mockBuilder, never()).defaultServiceConfig(any());
196+
verify(mockBuilder, never()).enableRetry();
197+
}
198+
}
199+
148200
@Test
149201
void registersRegistryInterceptors() {
150202
try (MockedStatic<ManagedChannelBuilder> mockedBuilderStatic =

0 commit comments

Comments
 (0)