From 0444bc6f0d1cc55991cecae8d309acf7c75165a8 Mon Sep 17 00:00:00 2001 From: Deepika Mamidipelly <85735834+Deepika-lab@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:59:26 -0700 Subject: [PATCH 1/2] feat: expose ChannelPoolSettings on GrpcStorageOptions.Builder --- .../cloud/storage/GrpcStorageOptions.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index 1a6726b9c0..661489466b 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -28,6 +28,7 @@ import com.google.api.gax.core.CredentialsProvider; import com.google.api.gax.core.FixedCredentialsProvider; import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.grpc.ChannelPoolSettings; import com.google.api.gax.grpc.GrpcCallSettings; import com.google.api.gax.grpc.GrpcInterceptorProvider; import com.google.api.gax.grpc.GrpcStubCallableFactory; @@ -122,6 +123,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * @since 2.14.0 @@ -148,6 +150,9 @@ public final class GrpcStorageOptions extends StorageOptions private final GrpcInterceptorProvider grpcInterceptorProvider; private final BlobWriteSessionConfig blobWriteSessionConfig; private transient OpenTelemetry openTelemetry; + // ChannelPoolSettings is not Serializable, so this must be transient. A null value (including + // after deserialization) means "unset", in which case the default channel pool is used. + private transient ChannelPoolSettings channelPoolSettings; private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) { super(builder, serviceDefaults); @@ -165,6 +170,7 @@ private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) this.grpcInterceptorProvider = builder.grpcInterceptorProvider; this.blobWriteSessionConfig = builder.blobWriteSessionConfig; this.openTelemetry = builder.openTelemetry; + this.channelPoolSettings = builder.channelPoolSettings; } @Override @@ -192,6 +198,12 @@ GrpcInterceptorProvider getGrpcInterceptorProvider() { return grpcInterceptorProvider; } + @InternalApi + @Nullable + ChannelPoolSettings getChannelPoolSettings() { + return channelPoolSettings; + } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.openTelemetry = HttpStorageOptions.getDefaultInstance().getOpenTelemetry(); @@ -333,6 +345,10 @@ private Tuple> resolveSettingsAndOpts() throw channelProviderBuilder.setInterceptorProvider(grpcInterceptorProvider); } + if (channelPoolSettings != null) { + channelProviderBuilder.setChannelPoolSettings(channelPoolSettings); + } + if (attemptDirectPath) { channelProviderBuilder.setAttemptDirectPathXds(); } @@ -432,6 +448,7 @@ public int hashCode() { grpcInterceptorProvider, blobWriteSessionConfig, openTelemetry, + channelPoolSettings, baseHashCode()); } @@ -451,6 +468,7 @@ public boolean equals(Object o) { && Objects.equals(grpcInterceptorProvider, that.grpcInterceptorProvider) && Objects.equals(blobWriteSessionConfig, that.blobWriteSessionConfig) && Objects.equals(openTelemetry, that.openTelemetry) + && Objects.equals(channelPoolSettings, that.channelPoolSettings) && this.baseEquals(that); } @@ -501,6 +519,7 @@ public static final class Builder extends StorageOptions.Builder { private BlobWriteSessionConfig blobWriteSessionConfig = GrpcStorageDefaults.INSTANCE.getDefaultStorageWriterConfig(); private OpenTelemetry openTelemetry = GrpcStorageDefaults.INSTANCE.getDefaultOpenTelemetry(); + private ChannelPoolSettings channelPoolSettings; private boolean grpcMetricsManuallyEnabled = false; @@ -516,6 +535,7 @@ public static final class Builder extends StorageOptions.Builder { this.grpcInterceptorProvider = gso.grpcInterceptorProvider; this.blobWriteSessionConfig = gso.blobWriteSessionConfig; this.openTelemetry = gso.openTelemetry; + this.channelPoolSettings = gso.channelPoolSettings; } /** @@ -750,6 +770,28 @@ public GrpcStorageOptions.Builder setOpenTelemetry(OpenTelemetry openTelemetry) return this; } + /** + * Set the {@link ChannelPoolSettings} used to configure the pool of gRPC channels underlying + * this client. + * + *

By default a single gRPC channel is used. If you expect a high number of concurrent RPCs + * (for example many concurrent resumable uploads), configuring a larger channel pool can + * improve throughput and tail latency. + * + *

If unset, the default channel pool configuration is used. + * + * @param channelPoolSettings a non-null ChannelPoolSettings to use + * @return the builder + * @since 2.65.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public GrpcStorageOptions.Builder setChannelPoolSettings( + @NonNull ChannelPoolSettings channelPoolSettings) { + this.channelPoolSettings = + requireNonNull(channelPoolSettings, "channelPoolSettings must be non null"); + return this; + } + /** * @since 2.14.0 */ From ca7ce715545d8643920ec4d1d6f915f32696b12b Mon Sep 17 00:00:00 2001 From: Deepika Mamidipelly <85735834+Deepika-lab@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:59:32 -0700 Subject: [PATCH 2/2] test: cover ChannelPoolSettings on GrpcStorageOptions.Builder --- .../cloud/storage/GrpcStorageOptionsTest.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/GrpcStorageOptionsTest.java diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/GrpcStorageOptionsTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/GrpcStorageOptionsTest.java new file mode 100644 index 0000000000..c5ee3a6f3f --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/GrpcStorageOptionsTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.gax.grpc.ChannelPoolSettings; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; +import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.cloud.NoCredentials; +import com.google.storage.v2.StorageSettings; +import org.junit.Test; + +public final class GrpcStorageOptionsTest { + + private static GrpcStorageOptions.Builder baseBuilder() { + return GrpcStorageOptions.grpc() + .setProjectId("test-proj") + .setCredentials(NoCredentials.getInstance()); + } + + private static InstantiatingGrpcChannelProvider resolveChannelProvider(GrpcStorageOptions options) + throws Exception { + StorageSettings settings = options.getStorageSettings(); + TransportChannelProvider provider = settings.getTransportChannelProvider(); + assertThat(provider).isInstanceOf(InstantiatingGrpcChannelProvider.class); + return (InstantiatingGrpcChannelProvider) provider; + } + + @Test + public void setChannelPoolSettings_propagatedToTransportChannelProvider() throws Exception { + ChannelPoolSettings channelPoolSettings = + ChannelPoolSettings.builder() + .setInitialChannelCount(8) + .setMaxChannelCount(16) + .setMaxRpcsPerChannel(50) + .build(); + + GrpcStorageOptions options = baseBuilder().setChannelPoolSettings(channelPoolSettings).build(); + + assertThat(options.getChannelPoolSettings()).isEqualTo(channelPoolSettings); + assertThat(resolveChannelProvider(options).getChannelPoolSettings()) + .isEqualTo(channelPoolSettings); + } + + @Test + public void channelPoolSettings_unset_usesDefaultChannelPool() throws Exception { + GrpcStorageOptions options = baseBuilder().build(); + + assertThat(options.getChannelPoolSettings()).isNull(); + // the default channel pool configuration must remain in effect + assertThat(resolveChannelProvider(options).getChannelPoolSettings()).isNotNull(); + } + + @Test + public void toBuilder_preservesChannelPoolSettings() { + ChannelPoolSettings channelPoolSettings = + ChannelPoolSettings.builder().setMaxChannelCount(4).build(); + + GrpcStorageOptions base = baseBuilder().setChannelPoolSettings(channelPoolSettings).build(); + GrpcStorageOptions rebuilt = base.toBuilder().build(); + + assertThat(rebuilt.getChannelPoolSettings()).isEqualTo(channelPoolSettings); + assertThat(rebuilt).isEqualTo(base); + assertThat(rebuilt.hashCode()).isEqualTo(base.hashCode()); + } +}