Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
03e341c
feat(gax-grpc): add configurable resize delta and warning for repeate…
lqiu96 Apr 17, 2026
2060fcf
chore(gax-grpc): revert surefire plugin configuration in pom.xml
lqiu96 Apr 17, 2026
e876b31
chore(gax-grpc): add comments and remove magic numbers for resize delta
lqiu96 Apr 17, 2026
b3ffb64
docs(gax-grpc): explain resizing detection choice in comments
lqiu96 Apr 17, 2026
8a5daa1
chore(gax-grpc): replace magic number 5 with constant in ChannelPool
lqiu96 Apr 17, 2026
813e101
docs(gax-grpc): update javadoc for maxResizeDelta to explain burst ha…
lqiu96 Apr 17, 2026
91ffb52
chore: generate libraries at Fri Apr 17 21:02:58 UTC 2026
cloud-java-bot Apr 17, 2026
de28aef
docs(gax-grpc): reference MAX_RESIZE_DELTA constant in javadoc
lqiu96 Apr 17, 2026
827b22d
docs(gax-grpc): explain use of == for log threshold
lqiu96 Apr 17, 2026
899736f
feat(gax-grpc): optimize ChannelPool resize and add thread safety com…
lqiu96 Apr 17, 2026
f9792b6
style(gax-grpc): format ChannelPool.java
lqiu96 Apr 17, 2026
856f3f2
style(gax-grpc): use constant for warning message and simplify comments
lqiu96 Apr 17, 2026
a6a34dc
Merge branch 'main' into feat/channelpool-resizing
lqiu96 Apr 20, 2026
2be58da
Revert "chore: generate libraries at Fri Apr 17 21:02:58 UTC 2026"
lqiu96 Apr 20, 2026
e61486f
chore: Update ocmments and refactor
lqiu96 Apr 20, 2026
cbef704
feat(gax-grpc): remove maxResizeDelta validation and update javadoc
lqiu96 Apr 20, 2026
2895af5
style(gax-grpc): update javadoc in ChannelPoolSettings to be a warning
lqiu96 Apr 20, 2026
068887e
feat(gax-grpc): restore maxResizeDelta validation and javadoc
lqiu96 Apr 20, 2026
947fad5
docs(gax-grpc): add comment explaining resize delta clamping in stati…
lqiu96 Apr 20, 2026
f62aa0c
chore: Add a comment to explain the resize delta logic in static size
lqiu96 Apr 20, 2026
697c336
feat(gax-grpc): add cap of 25 to maxResizeDelta and test
lqiu96 Apr 20, 2026
387c68e
docs(gax-grpc): add warning about high resize delta values in javadoc
lqiu96 Apr 20, 2026
defe1bb
docs(gax-grpc): add warning about high resize delta values to setter …
lqiu96 Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@
* <p>Package-private for internal use.
*/
class ChannelPool extends ManagedChannel {
static final String CHANNEL_POOL_CONSECUTIVE_RESIZING_WARNING =
"Channel pool is repeatedly resizing. "
+ "Consider adjusting `initialChannelCount` or `maxResizeDelta` to a more reasonable value. "
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would it make sense to also add max/minRpcsPerChannel in the message? E.g.

"Consider adjusting `initialChannelCount`, `maxResizeDelta`, `minRpcsPerChannel`, or `maxRpcsPerChannel` to a more reasonable value. "

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My plan is to change this to reference a public guide that I'm going to write (after metrics). WDYT if I leave this for now (for the immediate Datastore ask) and then update it to the public guide?

+ "See https://docs.cloud.google.com/java/docs/troubleshooting to enable logging "
+ "and set `com.google.api.gax.grpc.ChannelPool.level=FINEST` to log the channel pool resize behavior.";
@VisibleForTesting static final Logger LOG = Logger.getLogger(ChannelPool.class.getName());
private static final java.time.Duration REFRESH_PERIOD = java.time.Duration.ofMinutes(50);

Expand All @@ -84,6 +89,16 @@ class ChannelPool extends ManagedChannel {
private final AtomicInteger indexTicker = new AtomicInteger();
private final String authority;

// The number of consecutive resize cycles to wait before logging a warning about repeated
// resizing. This value was chosen to detect repeated requests for changes (multiple continuous
// increase or decrease attempts) without being too sensitive.
private static final int CONSECUTIVE_RESIZE_THRESHOLD = 5;

// Tracks the number of consecutive resize cycles where a resize actually occurred (either expand
// or shrink). Used to detect repeated resizing activity and log a warning.
// Note: This field is only accessed safely within resizeSafely() and does not need to be atomic.
private int consecutiveResizes = 0;
Comment thread
lqiu96 marked this conversation as resolved.

static ChannelPool create(
ChannelPoolSettings settings,
ChannelFactory channelFactory,
Expand Down Expand Up @@ -275,7 +290,8 @@ private void resizeSafely() {
* <li>Get the maximum number of outstanding RPCs since last invocation
* <li>Determine a valid range of number of channels to handle that many outstanding RPCs
* <li>If the current number of channel falls outside of that range, add or remove at most
* {@link ChannelPoolSettings#MAX_RESIZE_DELTA} to get closer to middle of that range.
* {@link ChannelPoolSettings#DEFAULT_MAX_RESIZE_DELTA} to get closer to middle of that
* range.
* </ul>
*
* <p>Not threadsafe, must be called under the entryWriteLock monitor
Expand Down Expand Up @@ -313,9 +329,25 @@ void resize() {
int currentSize = localEntries.size();
int delta = tentativeTarget - currentSize;
int dampenedTarget = tentativeTarget;
if (Math.abs(delta) > ChannelPoolSettings.MAX_RESIZE_DELTA) {
dampenedTarget =
currentSize + (int) Math.copySign(ChannelPoolSettings.MAX_RESIZE_DELTA, delta);
if (Math.abs(delta) > settings.getMaxResizeDelta()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know why there was a limit in the first place? Were there any technical limitations?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, it looks to just be a choice. Dampening and rate limit the channel growth to not overwhelm the client for a sudden burst.

dampenedTarget = currentSize + (int) Math.copySign(settings.getMaxResizeDelta(), delta);
}

// Only count as "resized" if the thresholds are crossed and Gax attempts to scale. Checking
// that `dampenedTarget != currentSize` would cause false positives when the pool is within
// bounds but not at the target (target aims for the middle of the bounds)
boolean resized = (currentSize < minChannels || currentSize > maxChannels);
if (resized) {
consecutiveResizes++;
} else {
consecutiveResizes = 0;
}

// Log warning only once when the consecutive threshold is reached to avoid spamming logs. Log
// message will repeat if the number of consecutive resizes resets (e.g. stabilizes for a bit).
// However, aim to log once to ensure that this does not incur log spam.
if (consecutiveResizes == CONSECUTIVE_RESIZE_THRESHOLD) {
LOG.warning(CHANNEL_POOL_CONSECUTIVE_RESIZING_WARNING);
}

// Only resize the pool when thresholds are crossed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ public abstract class ChannelPoolSettings {
static final Duration RESIZE_INTERVAL = Duration.ofMinutes(1);

/** The maximum number of channels that can be added or removed at a time. */
static final int MAX_RESIZE_DELTA = 2;
static final int DEFAULT_MAX_RESIZE_DELTA = 2;

// Arbitrary limit to prevent unbounded growth and protect server/client resources.
// Capping at 25 ensures we don't scale too aggressively in a single cycle.
private static final int MAX_ALLOWED_RESIZE_DELTA = 25;

/**
* Threshold to start scaling down the channel pool.
Expand Down Expand Up @@ -92,6 +96,22 @@ public abstract class ChannelPoolSettings {
*/
public abstract int getMaxChannelCount();

/**
* The maximum number of channels that can be added or removed at a time.
*
* <p>This setting limits the rate at which the channel pool can grow or shrink in a single resize
* period. The default value is {@value #DEFAULT_MAX_RESIZE_DELTA}. Increasing this value can help
* the pool better handle sudden bursts or spikes in requests by allowing it to scale up faster.
* Regardless of this setting, the number of channels will never exceed {@link
* #getMaxChannelCount()}.
*
* <p><b>Note:</b> This value cannot exceed {@value #MAX_ALLOWED_RESIZE_DELTA}.
*
* <p><b>Warning:</b> Higher values for resize delta may still result in performance degradation
* during spikes due to rapid scaling.
*/
public abstract int getMaxResizeDelta();

/**
* The initial size of the channel pool.
*
Expand All @@ -116,11 +136,7 @@ boolean isStaticSize() {
return true;
}
// When the scaling threshold are not set
if (getMinRpcsPerChannel() == 0 && getMaxRpcsPerChannel() == Integer.MAX_VALUE) {
return true;
}

return false;
return getMinRpcsPerChannel() == 0 && getMaxRpcsPerChannel() == Integer.MAX_VALUE;
}

public abstract Builder toBuilder();
Expand All @@ -132,6 +148,9 @@ public static ChannelPoolSettings staticallySized(int size) {
.setMaxRpcsPerChannel(Integer.MAX_VALUE)
.setMinChannelCount(size)
.setMaxChannelCount(size)
// Static pools don't resize so this value doesn't affect operation. However,
// validation still checks that resize delta doesn't exceed channel pool size.
.setMaxResizeDelta(Math.min(DEFAULT_MAX_RESIZE_DELTA, size))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still give it a max limit instead of being unbounded. Otherwise customers might expect this to handle 100x request spike as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delta is capped to between [1, MAX_CHANNEL_COUNT]. The javadocs already mention that the number of channels can never exceed the total number of channels configured (Default 200)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is 200 a realistic number? I think it makes sense to allow customers configure resizeDelta to be more than 2, but more than 10 (We need to come up with an more acurate number) may introduce other performance issues.

For example

  • resize is in a synchronized block.
  • We use AtomicInteger to keep tracking the number of outstanding RPCs, which could cause issues in high contention scenarios. In which case LongAdder may be preferred.

Can we reach out to the gRPC team and get some suggestions?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR does not intend to fix channel pool's issues. It only exposing a setting to allow users to configure a value that they want that fits within the current bounds of the existing logic. The default resize delta remains 2 and if they choose a different value, then they can test it and figure if it works for their workload.

There are limitations to the existing channel pool logic that can use some broader changes. There is a project proposal to investigate (b/503856499) how to make this better overall.

But is 200 a realistic number? I think it makes sense to allow customers configure resizeDelta to be more than 2, but more than 10 (We need to come up with an more acurate number) may introduce other performance issues.

I just aim provide a safe default and let customers tinker with that they think best. Delta of 10 or 25 may work better for different workloads.

If we want to fix ChannelPooling, then I think other changes would be more beneficial than investigating the resize delta value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this PR is not to fix ChannelPooling. However, the new setter could make it easier for customers to exploit the current limitations of ChannelPooling, hence I think it would still be better to set an upper limit.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exploit the current limitations of ChannelPooling

I'm not sure what you mean by exploiting here. If they configure a high value where performance degrades and doesn't work for them, they can rollback this change. If a high delta works for their use case, then they can opt to keep it.


Regardless, I think we may have to agree to disagree on setting a hard bound here. I don't think we should set an arbitrary bound for user configuration regardless of current limitations, barring something that doesn't fit the logic like negative resize delta.

IMO, if it has drastic performance concerns, it would be beneficial to see user workload configurations as well as the issue reports. It gives us signal about their requirements and helps us see what would be needed for a future ChannelPool overhaul (e.g. channel priming, power-of-two, etc). And the channelpool issues give us more direct data point to prioritize the project (instead of slightly tangential reports of increased client-side request latency). I'm worried that limiting this "exploit" hides the need to fix the underlying channelpool issues.

Rather than having to talk to gRPC and having to investigate the possible default upper bound limits, how we compromise and I set this to a higher default upper bound value (e.g. 25?). I'll add javadocs about potential performance concerns for setting a higher delta?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to clamp to 25 max for the resize delta with warning about performance

.build();
}

Expand All @@ -142,7 +161,8 @@ public static Builder builder() {
.setMaxChannelCount(200)
.setMinRpcsPerChannel(0)
.setMaxRpcsPerChannel(Integer.MAX_VALUE)
.setPreemptiveRefreshEnabled(false);
.setPreemptiveRefreshEnabled(false)
.setMaxResizeDelta(DEFAULT_MAX_RESIZE_DELTA);
}

@AutoValue.Builder
Expand All @@ -159,6 +179,15 @@ public abstract static class Builder {

public abstract Builder setPreemptiveRefreshEnabled(boolean enabled);

/**
* Sets the maximum number of channels that can be added or removed in a single resize cycle.
* This acts as a rate limiter to prevent wild fluctuations.
*
* <p><b>Warning:</b> Higher values for resize delta may still result in performance degradation
* during spikes due to rapid scaling.
*/
public abstract Builder setMaxResizeDelta(int count);

abstract ChannelPoolSettings autoBuild();

public ChannelPoolSettings build() {
Expand All @@ -178,6 +207,14 @@ public ChannelPoolSettings build() {
"initial channel count must be less than maxChannelCount");
Preconditions.checkState(
s.getInitialChannelCount() > 0, "Initial channel count must be greater than 0");
Preconditions.checkState(
s.getMaxResizeDelta() > 0, "Max resize delta must be greater than 0");
Preconditions.checkState(
s.getMaxResizeDelta() <= MAX_ALLOWED_RESIZE_DELTA,
"Max resize delta cannot be greater than " + MAX_ALLOWED_RESIZE_DELTA);
Preconditions.checkState(
s.getMaxResizeDelta() <= s.getMaxChannelCount(),
"Max resize delta cannot be greater than max channel count");
return s;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,60 @@
assertThat(pool.entries.get()).hasSize(2);
}

@Test
void customResizeDeltaIsRespected() throws Exception {
ScheduledExecutorService executor = Mockito.mock(ScheduledExecutorService.class);
FixedExecutorProvider provider = FixedExecutorProvider.create(executor);

List<ManagedChannel> channels = new ArrayList<>();

Check warning on line 531 in sdk-platform-java/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/ChannelPoolTest.java

View check run for this annotation

SonarQubeCloud / [gapic-generator-java-root] SonarCloud Code Analysis

Consume or remove this unused collection

See more on https://sonarcloud.io/project/issues?id=googleapis_google-cloud-java_showcase&issues=AZ2rto5jhU7HPh64hZKZ&open=AZ2rto5jhU7HPh64hZKZ&pullRequest=12838
List<ClientCall<Object, Object>> startedCalls = new ArrayList<>();

ChannelFactory channelFactory =
() -> {
ManagedChannel channel = Mockito.mock(ManagedChannel.class);
Mockito.when(channel.newCall(Mockito.any(), Mockito.any()))
.thenAnswer(
invocation -> {
@SuppressWarnings("unchecked")
ClientCall<Object, Object> clientCall = Mockito.mock(ClientCall.class);
startedCalls.add(clientCall);
return clientCall;
});

channels.add(channel);
return channel;
};

pool =
new ChannelPool(
ChannelPoolSettings.builder()
.setInitialChannelCount(2)
.setMinRpcsPerChannel(1)
.setMaxRpcsPerChannel(2)
.setMaxResizeDelta(5)
.build(),
channelFactory,
provider);
assertThat(pool.entries.get()).hasSize(2);

// Add 20 RPCs to push expansion
for (int i = 0; i < 20; i++) {
ClientCalls.futureUnaryCall(
pool.newCall(METHOD_RECOGNIZE, CallOptions.DEFAULT), Color.getDefaultInstance());
}
pool.resize();
// delta is 15 - 2 = 13. Capped at maxResizeDelta = 5.
// Expected size = 2 + 5 = 7.
assertThat(pool.entries.get()).hasSize(7);
}

@Test
void removedIdleChannelsAreShutdown() throws Exception {
ScheduledExecutorService executor = Mockito.mock(ScheduledExecutorService.class);
FixedExecutorProvider provider = FixedExecutorProvider.create(executor);

List<ManagedChannel> channels = new ArrayList<>();
List<ClientCall<Object, Object>> startedCalls = new ArrayList<>();

Check warning on line 579 in sdk-platform-java/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/ChannelPoolTest.java

View check run for this annotation

SonarQubeCloud / [gapic-generator-java-root] SonarCloud Code Analysis

Consume or remove this unused collection

See more on https://sonarcloud.io/project/issues?id=googleapis_google-cloud-java_showcase&issues=AZ2rto5jhU7HPh64hZKa&open=AZ2rto5jhU7HPh64hZKa&pullRequest=12838

ChannelFactory channelFactory =
() -> {
Expand Down Expand Up @@ -679,6 +726,121 @@
assertThat(e.getMessage()).isEqualTo("Call is already cancelled");
}

@Test
void repeatedResizingLogsWarningOnExpand() throws Exception {
ScheduledExecutorService executor = Mockito.mock(ScheduledExecutorService.class);
FixedExecutorProvider provider = FixedExecutorProvider.create(executor);

List<ManagedChannel> channels = new ArrayList<>();

Check warning on line 734 in sdk-platform-java/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/ChannelPoolTest.java

View check run for this annotation

SonarQubeCloud / [gapic-generator-java-root] SonarCloud Code Analysis

Consume or remove this unused collection

See more on https://sonarcloud.io/project/issues?id=googleapis_google-cloud-java_showcase&issues=AZ2rto5jhU7HPh64hZKb&open=AZ2rto5jhU7HPh64hZKb&pullRequest=12838
List<ClientCall<Object, Object>> startedCalls = new ArrayList<>();

Check warning on line 735 in sdk-platform-java/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/ChannelPoolTest.java

View check run for this annotation

SonarQubeCloud / [gapic-generator-java-root] SonarCloud Code Analysis

Consume or remove this unused collection

See more on https://sonarcloud.io/project/issues?id=googleapis_google-cloud-java_showcase&issues=AZ2rto5jhU7HPh64hZKc&open=AZ2rto5jhU7HPh64hZKc&pullRequest=12838

ChannelFactory channelFactory =
() -> {
ManagedChannel channel = Mockito.mock(ManagedChannel.class);
Mockito.when(channel.newCall(Mockito.any(), Mockito.any()))
.thenAnswer(
invocation -> {
@SuppressWarnings("unchecked")
ClientCall<Object, Object> clientCall = Mockito.mock(ClientCall.class);
startedCalls.add(clientCall);
return clientCall;
});

channels.add(channel);
return channel;
};

pool =
new ChannelPool(
ChannelPoolSettings.builder()
.setInitialChannelCount(1)
.setMinRpcsPerChannel(1)
.setMaxRpcsPerChannel(2)
.setMaxResizeDelta(1)
.setMinChannelCount(1)
.setMaxChannelCount(10)
.build(),
channelFactory,
provider);
assertThat(pool.entries.get()).hasSize(1);

FakeLogHandler logHandler = new FakeLogHandler();
ChannelPool.LOG.addHandler(logHandler);

try {
// Add 20 RPCs to push expansion
for (int i = 0; i < 20; i++) {
ClientCalls.futureUnaryCall(
pool.newCall(METHOD_RECOGNIZE, CallOptions.DEFAULT), Color.getDefaultInstance());
}

// Resize 4 times, should not log warning yet
for (int i = 0; i < 4; i++) {
pool.resize();
}
assertThat(logHandler.getAllMessages()).isEmpty();

// 5th resize, should log warning
pool.resize();
assertThat(logHandler.getAllMessages()).hasSize(1);
assertThat(logHandler.getAllMessages())
.contains(ChannelPool.CHANNEL_POOL_CONSECUTIVE_RESIZING_WARNING);

// 6th resize, should not log again
pool.resize();
assertThat(logHandler.getAllMessages()).hasSize(1);
} finally {
ChannelPool.LOG.removeHandler(logHandler);
}
}

@Test
void repeatedResizingLogsWarningOnShrink() throws Exception {
ScheduledExecutorService executor = Mockito.mock(ScheduledExecutorService.class);
FixedExecutorProvider provider = FixedExecutorProvider.create(executor);

List<ManagedChannel> channels = new ArrayList<>();

Check warning on line 802 in sdk-platform-java/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/ChannelPoolTest.java

View check run for this annotation

SonarQubeCloud / [gapic-generator-java-root] SonarCloud Code Analysis

Consume or remove this unused collection

See more on https://sonarcloud.io/project/issues?id=googleapis_google-cloud-java_showcase&issues=AZ2rto5jhU7HPh64hZKd&open=AZ2rto5jhU7HPh64hZKd&pullRequest=12838
ChannelFactory channelFactory =
() -> {
ManagedChannel channel = Mockito.mock(ManagedChannel.class);
channels.add(channel);
return channel;
};

pool =
new ChannelPool(
ChannelPoolSettings.builder()
.setInitialChannelCount(10)
.setMinRpcsPerChannel(1)
.setMaxRpcsPerChannel(2)
.setMaxResizeDelta(1)
.setMinChannelCount(1)
.setMaxChannelCount(10)
.build(),
channelFactory,
provider);
assertThat(pool.entries.get()).hasSize(10);

FakeLogHandler logHandler = new FakeLogHandler();
ChannelPool.LOG.addHandler(logHandler);

try {
// 0 RPCs, should shrink every cycle
// Resize 4 times, should not log warning yet
for (int i = 0; i < 4; i++) {
pool.resize();
}
assertThat(logHandler.getAllMessages()).isEmpty();

// 5th resize, should log warning
pool.resize();
assertThat(logHandler.getAllMessages())
.contains(ChannelPool.CHANNEL_POOL_CONSECUTIVE_RESIZING_WARNING);
} finally {
ChannelPool.LOG.removeHandler(logHandler);
}
}

@Test
void testDoubleRelease() throws Exception {
FakeLogHandler logHandler = new FakeLogHandler();
Expand Down Expand Up @@ -737,4 +899,12 @@
ChannelPool.LOG.removeHandler(logHandler);
}
}

@Test
void settingsValidationFailsWhenMaxResizeDeltaExceedsLimit() {
ChannelPoolSettings.Builder builder =
ChannelPoolSettings.builder().setMaxResizeDelta(26).setMaxChannelCount(30);
org.junit.jupiter.api.Assertions.assertThrows(
IllegalStateException.class, () -> builder.build());

Check warning on line 908 in sdk-platform-java/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/ChannelPoolTest.java

View check run for this annotation

SonarQubeCloud / [gapic-generator-java-root] SonarCloud Code Analysis

Replace this lambda with method reference 'builder::build'.

See more on https://sonarcloud.io/project/issues?id=googleapis_google-cloud-java_showcase&issues=AZ2tJ842YOlt5-IdKklK&open=AZ2tJ842YOlt5-IdKklK&pullRequest=12838
}
}
Loading