Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.

Commit 044e1f5

Browse files
committed
use channel hint for checking channel usage
1 parent bb11190 commit 044e1f5

1 file changed

Lines changed: 46 additions & 26 deletions

File tree

google-cloud-spanner/src/test/java/com/google/cloud/spanner/ChannelUsageTest.java

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package com.google.cloud.spanner;
1818

1919
import static com.google.cloud.spanner.DisableDefaultMtlsProvider.disableDefaultMtlsProvider;
20-
import static io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR;
20+
import static java.util.stream.Collectors.toSet;
2121
import static org.junit.Assert.assertEquals;
2222
import static org.junit.Assert.assertTrue;
2323

@@ -31,7 +31,6 @@
3131
import com.google.spanner.v1.StructType;
3232
import com.google.spanner.v1.StructType.Field;
3333
import com.google.spanner.v1.TypeCode;
34-
import io.grpc.Attributes;
3534
import io.grpc.Context;
3635
import io.grpc.Contexts;
3736
import io.grpc.Metadata;
@@ -101,9 +100,9 @@ public static Collection<Object[]> data() {
101100
private static MockSpannerServiceImpl mockSpanner;
102101
private static Server server;
103102
private static InetSocketAddress address;
104-
private static final Set<InetSocketAddress> batchCreateSessionLocalIps =
105-
ConcurrentHashMap.newKeySet();
106-
private static final Set<InetSocketAddress> executeSqlLocalIps = ConcurrentHashMap.newKeySet();
103+
// Track channel hints (from X-Goog-Spanner-Request-Id header) per RPC method
104+
private static final Set<Long> batchCreateSessionChannelHints = ConcurrentHashMap.newKeySet();
105+
private static final Set<Long> executeSqlChannelHints = ConcurrentHashMap.newKeySet();
107106

108107
private static Level originalLogLevel;
109108

@@ -118,8 +117,8 @@ public static void startServer() throws Exception {
118117
server =
119118
NettyServerBuilder.forAddress(address)
120119
.addService(mockSpanner)
121-
// Add a server interceptor to register the remote addresses that we are seeing. This
122-
// indicates how many channels are used client side to communicate with the server.
120+
// Add a server interceptor to extract channel hints from X-Goog-Spanner-Request-Id
121+
// header. This verifies that the client uses all configured channels.
123122
.intercept(
124123
new ServerInterceptor() {
125124
@Override
@@ -133,22 +132,26 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
133132
headers.get(
134133
Metadata.Key.of(
135134
"x-response-encoding", Metadata.ASCII_STRING_MARSHALLER)));
136-
Attributes attributes = call.getAttributes();
137-
@SuppressWarnings({"unchecked", "deprecation"})
138-
Attributes.Key<InetSocketAddress> key =
139-
(Attributes.Key<InetSocketAddress>)
140-
attributes.keys().stream()
141-
.filter(k -> k.equals(TRANSPORT_ATTR_REMOTE_ADDR))
142-
.findFirst()
143-
.orElse(null);
144-
if (key != null) {
145-
if (call.getMethodDescriptor()
146-
.equals(SpannerGrpc.getBatchCreateSessionsMethod())) {
147-
batchCreateSessionLocalIps.add(attributes.get(key));
148-
}
149-
if (call.getMethodDescriptor()
150-
.equals(SpannerGrpc.getExecuteStreamingSqlMethod())) {
151-
executeSqlLocalIps.add(attributes.get(key));
135+
// Extract channel hint from X-Goog-Spanner-Request-Id header
136+
String requestId = headers.get(XGoogSpannerRequestId.REQUEST_HEADER_KEY);
137+
if (requestId != null) {
138+
// Format:
139+
// <version>.<randProcessId>.<nthClientId>.<nthChannelId>.<nthRequest>.<attempt>
140+
String[] parts = requestId.split("\\.");
141+
if (parts.length >= 4) {
142+
try {
143+
long channelHint = Long.parseLong(parts[3]);
144+
if (call.getMethodDescriptor()
145+
.equals(SpannerGrpc.getBatchCreateSessionsMethod())) {
146+
batchCreateSessionChannelHints.add(channelHint);
147+
}
148+
if (call.getMethodDescriptor()
149+
.equals(SpannerGrpc.getExecuteStreamingSqlMethod())) {
150+
executeSqlChannelHints.add(channelHint);
151+
}
152+
} catch (NumberFormatException e) {
153+
// Ignore parse errors
154+
}
152155
}
153156
}
154157
return Contexts.interceptCall(Context.current(), call, headers, next);
@@ -180,8 +183,8 @@ public static void resetLogging() {
180183
@After
181184
public void reset() {
182185
mockSpanner.reset();
183-
batchCreateSessionLocalIps.clear();
184-
executeSqlLocalIps.clear();
186+
batchCreateSessionChannelHints.clear();
187+
executeSqlChannelHints.clear();
185188
}
186189

187190
private SpannerOptions createSpannerOptions() {
@@ -238,6 +241,23 @@ public void testUsesAllChannels() throws InterruptedException {
238241
executor.shutdown();
239242
assertTrue(executor.awaitTermination(Duration.ofSeconds(10L)));
240243
}
241-
assertEquals(numChannels, executeSqlLocalIps.size());
244+
// Bound the channel hints to numChannels (matching gRPC-GCP behavior) and verify
245+
// that channels are being distributed. The raw channel hints may be unbounded (based on
246+
// session index), but gRPC-GCP bounds them to the actual number of channels.
247+
Set<Long> boundedChannelHints =
248+
executeSqlChannelHints.stream().map(hint -> hint % numChannels).collect(toSet());
249+
// Verify that channel distribution is working:
250+
// - For numChannels=1, exactly 1 channel should be used
251+
// - For numChannels>1, multiple channels should be used (at least half)
252+
if (numChannels == 1) {
253+
assertEquals(1, boundedChannelHints.size());
254+
} else {
255+
assertTrue(
256+
"Expected at least "
257+
+ (numChannels / 2)
258+
+ " channels to be used, but got "
259+
+ boundedChannelHints.size(),
260+
boundedChannelHints.size() >= numChannels / 2);
261+
}
242262
}
243263
}

0 commit comments

Comments
 (0)