Skip to content

Commit 1e633d7

Browse files
authored
feat(spanner): add connection property for enabling/disabling grpc-gcp (#12898)
Adds a connection property for enabling/disabling the grpc-gcp channel pool. This channel pool is the default, but can be disabled by setting this property to false. The connection will use Gax for channel pooling if grpc-gcp is disabled.
1 parent 27feef5 commit 1e633d7

5 files changed

Lines changed: 164 additions & 1 deletion

File tree

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_DYNAMIC_CHANNEL_POOL;
3636
import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_END_TO_END_TRACING;
3737
import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_EXTENDED_TRACING;
38+
import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_GRPC_GCP;
3839
import static com.google.cloud.spanner.connection.ConnectionProperties.ENCODED_CREDENTIALS;
3940
import static com.google.cloud.spanner.connection.ConnectionProperties.ENDPOINT;
4041
import static com.google.cloud.spanner.connection.ConnectionProperties.GRPC_INTERCEPTOR_PROVIDER;
@@ -160,6 +161,7 @@ public class ConnectionOptions {
160161
static final Integer DEFAULT_MAX_SESSIONS = null;
161162
static final Integer DEFAULT_NUM_CHANNELS = null;
162163
static final Boolean DEFAULT_ENABLE_DYNAMIC_CHANNEL_POOL = null;
164+
static final Boolean DEFAULT_ENABLE_GRPC_GCP = null;
163165
static final Integer DEFAULT_DCP_MIN_CHANNELS = null;
164166
static final Integer DEFAULT_DCP_MAX_CHANNELS = null;
165167
static final Integer DEFAULT_DCP_INITIAL_CHANNELS = null;
@@ -263,6 +265,9 @@ public class ConnectionOptions {
263265
/** Name of the 'enableDynamicChannelPool' connection property. */
264266
public static final String ENABLE_DYNAMIC_CHANNEL_POOL_PROPERTY_NAME = "enableDynamicChannelPool";
265267

268+
/** Name of the 'enableGrpcGcp' connection property. */
269+
public static final String ENABLE_GRPC_GCP_PROPERTY_NAME = "enableGrpcGcp";
270+
266271
/** Name of the 'dcpMinChannels' connection property. */
267272
public static final String DCP_MIN_CHANNELS_PROPERTY_NAME = "dcpMinChannels";
268273

@@ -1016,6 +1021,11 @@ public Boolean isEnableDynamicChannelPool() {
10161021
return getInitialConnectionPropertyValue(ENABLE_DYNAMIC_CHANNEL_POOL);
10171022
}
10181023

1024+
/** Whether grpc-gcp is enabled for this connection. */
1025+
public Boolean isEnableGrpcGcp() {
1026+
return getInitialConnectionPropertyValue(ENABLE_GRPC_GCP);
1027+
}
1028+
10191029
/** The minimum number of channels in the dynamic channel pool. */
10201030
public Integer getDcpMinChannels() {
10211031
return getInitialConnectionPropertyValue(DCP_MIN_CHANNELS);

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_DYNAMIC_CHANNEL_POOL;
5656
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_END_TO_END_TRACING;
5757
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_EXTENDED_TRACING;
58+
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_GRPC_GCP;
5859
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENDPOINT;
5960
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_IS_EXPERIMENTAL_HOST;
6061
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_KEEP_TRANSACTION_ALIVE;
@@ -85,6 +86,7 @@
8586
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_DYNAMIC_CHANNEL_POOL_PROPERTY_NAME;
8687
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_END_TO_END_TRACING_PROPERTY_NAME;
8788
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_EXTENDED_TRACING_PROPERTY_NAME;
89+
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_GRPC_GCP_PROPERTY_NAME;
8890
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_GRPC_INTERCEPTOR_PROVIDER_SYSTEM_PROPERTY;
8991
import static com.google.cloud.spanner.connection.ConnectionOptions.ENCODED_CREDENTIALS_PROPERTY_NAME;
9092
import static com.google.cloud.spanner.connection.ConnectionOptions.ENDPOINT_PROPERTY_NAME;
@@ -463,6 +465,18 @@ public class ConnectionProperties {
463465
BOOLEANS,
464466
BooleanConverter.INSTANCE,
465467
Context.STARTUP);
468+
static final ConnectionProperty<Boolean> ENABLE_GRPC_GCP =
469+
create(
470+
ENABLE_GRPC_GCP_PROPERTY_NAME,
471+
"Enable or disable grpc-gcp channel pool (true/false). "
472+
+ "Setting this to false will disable grpc-gcp and use the Gax gRPC channel pool. "
473+
+ "Disabling grpc-gcp also automatically disables dynamic channel pooling, regardless "
474+
+ "of the value of enableDynamicChannelPool, as Spanner only supports dynamic channel "
475+
+ "pooling in combination with grpc-gcp.",
476+
DEFAULT_ENABLE_GRPC_GCP,
477+
BOOLEANS,
478+
BooleanConverter.INSTANCE,
479+
Context.STARTUP);
466480
static final ConnectionProperty<Integer> DCP_MIN_CHANNELS =
467481
create(
468482
DCP_MIN_CHANNELS_PROPERTY_NAME,

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ static class SpannerPoolKey {
155155
private final SessionPoolOptions sessionPoolOptions;
156156
private final Integer numChannels;
157157
private final Boolean enableDynamicChannelPool;
158+
private final Boolean enableGrpcGcp;
158159
private final Integer dcpMinChannels;
159160
private final Integer dcpMaxChannels;
160161
private final Integer dcpInitialChannels;
@@ -197,6 +198,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException {
197198
: options.getSessionPoolOptions();
198199
this.numChannels = options.getNumChannels();
199200
this.enableDynamicChannelPool = options.isEnableDynamicChannelPool();
201+
this.enableGrpcGcp = options.isEnableGrpcGcp();
200202
this.dcpMinChannels = options.getDcpMinChannels();
201203
this.dcpMaxChannels = options.getDcpMaxChannels();
202204
this.dcpInitialChannels = options.getDcpInitialChannels();
@@ -228,6 +230,7 @@ public boolean equals(Object o) {
228230
&& Objects.equals(this.sessionPoolOptions, other.sessionPoolOptions)
229231
&& Objects.equals(this.numChannels, other.numChannels)
230232
&& Objects.equals(this.enableDynamicChannelPool, other.enableDynamicChannelPool)
233+
&& Objects.equals(this.enableGrpcGcp, other.enableGrpcGcp)
231234
&& Objects.equals(this.dcpMinChannels, other.dcpMinChannels)
232235
&& Objects.equals(this.dcpMaxChannels, other.dcpMaxChannels)
233236
&& Objects.equals(this.dcpInitialChannels, other.dcpInitialChannels)
@@ -258,6 +261,7 @@ public int hashCode() {
258261
this.sessionPoolOptions,
259262
this.numChannels,
260263
this.enableDynamicChannelPool,
264+
this.enableGrpcGcp,
261265
this.dcpMinChannels,
262266
this.dcpMaxChannels,
263267
this.dcpInitialChannels,
@@ -421,9 +425,18 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) {
421425
if (key.numChannels != null) {
422426
builder.setNumChannels(key.numChannels);
423427
}
428+
if (key.enableGrpcGcp != null) {
429+
if (Boolean.TRUE.equals(key.enableGrpcGcp)) {
430+
builder.enableGrpcGcpExtension();
431+
} else {
432+
builder.disableGrpcGcpExtension();
433+
}
434+
}
424435
// Configure Dynamic Channel Pooling (DCP) based on explicit user setting.
425436
// Note: Setting numChannels disables DCP even if enableDynamicChannelPool is true.
426-
if (key.enableDynamicChannelPool != null && key.numChannels == null) {
437+
if (key.enableDynamicChannelPool != null
438+
&& key.numChannels == null
439+
&& !Boolean.FALSE.equals(key.enableGrpcGcp)) {
427440
if (Boolean.TRUE.equals(key.enableDynamicChannelPool)) {
428441
builder.enableDynamicChannelPool();
429442
// Build custom GcpChannelPoolOptions if any DCP-specific options are set.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner.connection;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertTrue;
22+
23+
import com.google.cloud.spanner.ResultSet;
24+
import com.google.cloud.spanner.Spanner;
25+
import com.google.cloud.spanner.Statement;
26+
import com.google.cloud.spanner.connection.StatementResult.ResultType;
27+
import org.junit.After;
28+
import org.junit.Before;
29+
import org.junit.Test;
30+
import org.junit.runner.RunWith;
31+
import org.junit.runners.JUnit4;
32+
33+
@RunWith(JUnit4.class)
34+
public class GrpcGcpMockServerTest extends AbstractMockServerTest {
35+
36+
@Before
37+
public void setup() {
38+
SpannerPool.closeSpannerPool();
39+
}
40+
41+
@After
42+
public void teardown() {
43+
SpannerPool.closeSpannerPool();
44+
}
45+
46+
private void verifyConnectionWorks(Connection connection) {
47+
StatementResult result = connection.execute(Statement.of("SELECT 1"));
48+
assertEquals(ResultType.RESULT_SET, result.getResultType());
49+
try (ResultSet rs = result.getResultSet()) {
50+
assertTrue(rs.next());
51+
assertEquals(1L, rs.getLong(0));
52+
assertFalse(rs.next());
53+
}
54+
}
55+
56+
@Test
57+
public void testDisableGrpcGcp() {
58+
try (Connection connection = createConnection(";enableGrpcGcp=false")) {
59+
Spanner spanner = ((ConnectionImpl) connection).getSpanner();
60+
assertFalse(spanner.getOptions().isGrpcGcpExtensionEnabled());
61+
verifyConnectionWorks(connection);
62+
}
63+
}
64+
65+
@Test
66+
public void testEnableGrpcGcp() {
67+
try (Connection connection = createConnection(";enableGrpcGcp=true")) {
68+
Spanner spanner = ((ConnectionImpl) connection).getSpanner();
69+
assertTrue(spanner.getOptions().isGrpcGcpExtensionEnabled());
70+
verifyConnectionWorks(connection);
71+
}
72+
}
73+
74+
@Test
75+
public void testDefaultGrpcGcp() {
76+
try (Connection connection = createConnection()) {
77+
Spanner spanner = ((ConnectionImpl) connection).getSpanner();
78+
// Default should be true in SpannerOptions
79+
assertTrue(spanner.getOptions().isGrpcGcpExtensionEnabled());
80+
verifyConnectionWorks(connection);
81+
}
82+
}
83+
}

java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SpannerPoolTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,4 +771,47 @@ public void testExplicitlyDisabledDynamicChannelPool() {
771771
.setCredentials(NoCredentials.getInstance())
772772
.build()));
773773
}
774+
775+
@Test
776+
public void testGrpcGcpSettings() {
777+
SpannerPoolKey keyWithoutGrpcGcp =
778+
SpannerPoolKey.of(
779+
ConnectionOptions.newBuilder()
780+
.setUri("cloudspanner:/projects/p/instances/i/databases/d")
781+
.setCredentials(NoCredentials.getInstance())
782+
.build());
783+
SpannerPoolKey keyWithGrpcGcpEnabled =
784+
SpannerPoolKey.of(
785+
ConnectionOptions.newBuilder()
786+
.setUri("cloudspanner:/projects/p/instances/i/databases/d?enableGrpcGcp=true")
787+
.setCredentials(NoCredentials.getInstance())
788+
.build());
789+
SpannerPoolKey keyWithGrpcGcpDisabled =
790+
SpannerPoolKey.of(
791+
ConnectionOptions.newBuilder()
792+
.setUri("cloudspanner:/projects/p/instances/i/databases/d?enableGrpcGcp=false")
793+
.setCredentials(NoCredentials.getInstance())
794+
.build());
795+
796+
// grpcGcp settings should affect the SpannerPoolKey
797+
assertNotEquals(keyWithoutGrpcGcp, keyWithGrpcGcpEnabled);
798+
assertNotEquals(keyWithoutGrpcGcp, keyWithGrpcGcpDisabled);
799+
assertNotEquals(keyWithGrpcGcpEnabled, keyWithGrpcGcpDisabled);
800+
801+
// Same configuration should create equal keys
802+
assertEquals(
803+
keyWithGrpcGcpEnabled,
804+
SpannerPoolKey.of(
805+
ConnectionOptions.newBuilder()
806+
.setUri("cloudspanner:/projects/p/instances/i/databases/d?enableGrpcGcp=true")
807+
.setCredentials(NoCredentials.getInstance())
808+
.build()));
809+
assertEquals(
810+
keyWithGrpcGcpDisabled,
811+
SpannerPoolKey.of(
812+
ConnectionOptions.newBuilder()
813+
.setUri("cloudspanner:/projects/p/instances/i/databases/d?enableGrpcGcp=false")
814+
.setCredentials(NoCredentials.getInstance())
815+
.build()));
816+
}
774817
}

0 commit comments

Comments
 (0)