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

Commit 392308d

Browse files
committed
incorporate suggestions
1 parent 33a2af2 commit 392308d

3 files changed

Lines changed: 22 additions & 74 deletions

File tree

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,16 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
163163

164164
/**
165165
* Default affinity key lifetime for dynamic channel pool. This is how long to keep an affinity
166-
* key after its last use. Zero means keeping keys forever. Default is 1 hour.
166+
* key after its last use. Zero means keeping keys forever. Default is 10 minutes, which is
167+
* sufficient to ensure that requests within a single transaction use the same channel.
167168
*/
168-
public static final Duration DEFAULT_DYNAMIC_POOL_AFFINITY_KEY_LIFETIME = Duration.ofHours(1);
169+
public static final Duration DEFAULT_DYNAMIC_POOL_AFFINITY_KEY_LIFETIME = Duration.ofMinutes(10);
169170

170171
/**
171172
* Default cleanup interval for dynamic channel pool affinity keys. This is how frequently the
172-
* affinity key cleanup process runs. Default is 6 minutes (1/10 of default affinity key
173-
* lifetime).
173+
* affinity key cleanup process runs. Default is 1 minute (1/10 of default affinity key lifetime).
174174
*/
175-
public static final Duration DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL = Duration.ofMinutes(6);
175+
public static final Duration DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL = Duration.ofMinutes(1);
176176

177177
private final TransportChannelProvider channelProvider;
178178

@@ -915,6 +915,14 @@ protected SpannerOptions(Builder builder) {
915915
dynamicPoolCleanupInterval = DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL;
916916
}
917917

918+
// Validate that if affinity key lifetime is set (non-zero), cleanup interval must be positive.
919+
// A zero cleanup interval with a non-zero affinity key lifetime would disable cleanup of stale
920+
// affinity keys, potentially leading to a memory leak.
921+
Preconditions.checkArgument(
922+
dynamicPoolAffinityKeyLifetime.isZero() || !dynamicPoolCleanupInterval.isZero(),
923+
"Cleanup interval must be positive when affinity key lifetime is set, got cleanup interval: %s",
924+
dynamicPoolCleanupInterval);
925+
918926
autoThrottleAdministrativeRequests = builder.autoThrottleAdministrativeRequests;
919927
retryAdministrativeRequestsSettings = builder.retryAdministrativeRequestsSettings;
920928
trackTransactionStarter = builder.trackTransactionStarter;
@@ -1838,7 +1846,7 @@ public Builder setDynamicPoolAffinityKeyLifetime(Duration lifetime) {
18381846

18391847
/**
18401848
* Sets the cleanup interval for the dynamic channel pool affinity keys. This determines how
1841-
* frequently the affinity key cleanup process runs. Default is 6 minutes. Must be positive if
1849+
* frequently the affinity key cleanup process runs. Default is 1 minute. Must be positive if
18421850
* affinity key lifetime is set.
18431851
*
18441852
* <p>This setting is only effective when dynamic channel pooling is enabled.
@@ -2310,13 +2318,13 @@ public int getDynamicPoolMinChannels() {
23102318

23112319
/**
23122320
* Returns the affinity key lifetime for the dynamic pool. This is how long to keep an affinity
2313-
* key after its last use. Default is 1 hour.
2321+
* key after its last use. Default is 10 minutes.
23142322
*/
23152323
public Duration getDynamicPoolAffinityKeyLifetime() {
23162324
return dynamicPoolAffinityKeyLifetime;
23172325
}
23182326

2319-
/** Returns the cleanup interval for dynamic pool affinity keys. Default is 6 minutes. */
2327+
/** Returns the cleanup interval for dynamic pool affinity keys. Default is 1 minute. */
23202328
public Duration getDynamicPoolCleanupInterval() {
23212329
return dynamicPoolCleanupInterval;
23222330
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,9 @@ private static GcpManagedChannelOptions grpcGcpOptionsWithMetricsAndDcp(SpannerO
597597

598598
// Configure dynamic channel pool options if enabled
599599
if (options.isDynamicChannelPoolEnabled()) {
600-
GcpChannelPoolOptions existingPoolOptions = grpcGcpOptions.getChannelPoolOptions();
600+
GcpChannelPoolOptions existingPoolOptions =
601+
MoreObjects.firstNonNull(
602+
grpcGcpOptions.getChannelPoolOptions(), GcpChannelPoolOptions.newBuilder().build());
601603
GcpChannelPoolOptions.Builder poolOptionsBuilder =
602604
GcpChannelPoolOptions.newBuilder(existingPoolOptions);
603605

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

Lines changed: 3 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.google.cloud.spanner;
1818

19-
import static io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR;
2019
import static org.junit.Assert.assertEquals;
2120
import static org.junit.Assert.assertFalse;
2221
import static org.junit.Assert.assertNotEquals;
@@ -33,23 +32,15 @@
3332
import com.google.spanner.v1.BatchCreateSessionsRequest;
3433
import com.google.spanner.v1.BeginTransactionRequest;
3534
import com.google.spanner.v1.ExecuteSqlRequest;
36-
import io.grpc.Attributes;
3735
import io.grpc.CallOptions;
3836
import io.grpc.Channel;
3937
import io.grpc.ClientCall;
4038
import io.grpc.ClientInterceptor;
4139
import io.grpc.Context;
4240
import io.grpc.Deadline;
4341
import io.grpc.ManagedChannelBuilder;
44-
import io.grpc.Metadata;
4542
import io.grpc.MethodDescriptor;
46-
import io.grpc.ServerCall;
47-
import io.grpc.ServerCall.Listener;
48-
import io.grpc.ServerCallHandler;
49-
import io.grpc.ServerInterceptor;
5043
import io.grpc.Status;
51-
import java.io.IOException;
52-
import java.net.InetSocketAddress;
5344
import java.time.Duration;
5445
import java.util.HashMap;
5546
import java.util.HashSet;
@@ -70,18 +61,14 @@
7061

7162
@RunWith(JUnit4.class)
7263
public class RetryOnDifferentGrpcChannelMockServerTest extends AbstractMockServerTest {
73-
private static final Map<String, Set<InetSocketAddress>> SERVER_ADDRESSES = new HashMap<>();
74-
75-
/** Tracks the physical channel IDs from request ID headers (set by grpc-gcp). */
76-
private static final Map<String, Set<Long>> CHANNEL_HINTS = new HashMap<>();
77-
7864
/** Tracks the logical affinity keys before grpc-gcp routes the request. */
7965
private static final Map<String, Set<String>> LOGICAL_AFFINITY_KEYS = new HashMap<>();
8066

8167
@BeforeClass
82-
public static void startStaticServer() throws IOException {
68+
public static void setupAndStartServer() throws Exception {
8369
System.setProperty("spanner.retry_deadline_exceeded_on_different_channel", "true");
84-
startStaticServer(createServerInterceptor());
70+
// Call the parent's startStaticServer to set up the mock server
71+
AbstractMockServerTest.startStaticServer();
8572
}
8673

8774
@AfterClass
@@ -91,8 +78,6 @@ public static void removeSystemProperty() {
9178

9279
@After
9380
public void clearRequests() {
94-
SERVER_ADDRESSES.clear();
95-
CHANNEL_HINTS.clear();
9681
LOGICAL_AFFINITY_KEYS.clear();
9782
mockSpanner.clearRequests();
9883
mockSpanner.removeAllExecutionTimes();
@@ -125,53 +110,6 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
125110
});
126111
}
127112

128-
static ServerInterceptor createServerInterceptor() {
129-
return new ServerInterceptor() {
130-
@Override
131-
public <ReqT, RespT> Listener<ReqT> interceptCall(
132-
ServerCall<ReqT, RespT> serverCall,
133-
Metadata metadata,
134-
ServerCallHandler<ReqT, RespT> serverCallHandler) {
135-
Attributes attributes = serverCall.getAttributes();
136-
String methodName = serverCall.getMethodDescriptor().getFullMethodName();
137-
//noinspection 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-
InetSocketAddress address = attributes.get(key);
146-
synchronized (SERVER_ADDRESSES) {
147-
Set<InetSocketAddress> addresses =
148-
SERVER_ADDRESSES.getOrDefault(methodName, new HashSet<>());
149-
addresses.add(address);
150-
SERVER_ADDRESSES.putIfAbsent(methodName, addresses);
151-
}
152-
}
153-
String requestId = metadata.get(XGoogSpannerRequestId.REQUEST_ID_HEADER_KEY);
154-
if (requestId != null) {
155-
// REQUEST_ID format: version.randProcessId.nthClientId.nthChannelId.nthRequest.attempt
156-
String[] parts = requestId.split("\\.");
157-
if (parts.length >= 6) {
158-
try {
159-
long channelHint = Long.parseLong(parts[3]);
160-
synchronized (CHANNEL_HINTS) {
161-
Set<Long> hints = CHANNEL_HINTS.getOrDefault(methodName, new HashSet<>());
162-
hints.add(channelHint);
163-
CHANNEL_HINTS.putIfAbsent(methodName, hints);
164-
}
165-
} catch (NumberFormatException ignore) {
166-
// Ignore malformed header values in tests.
167-
}
168-
}
169-
}
170-
return serverCallHandler.startCall(serverCall, metadata);
171-
}
172-
};
173-
}
174-
175113
SpannerOptions.Builder createSpannerOptionsBuilder() {
176114
return SpannerOptions.newBuilder()
177115
.setProjectId("my-project")

0 commit comments

Comments
 (0)