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

Commit c6206f8

Browse files
committed
feat: recycle channel on consecutive new stream failures
1 parent 77952d2 commit c6206f8

2 files changed

Lines changed: 106 additions & 2 deletions

File tree

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/channels/ChannelPoolDpImpl.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public class ChannelPoolDpImpl implements ChannelPool {
6565
private static final String DEFAULT_LOG_NAME = "pool";
6666
private static final AtomicInteger INDEX = new AtomicInteger();
6767

68+
private static final int CONSECUTIVE_OPEN_FAILURE_THRESHOLD = 5;
69+
6870
private final String poolLogId;
6971

7072
@VisibleForTesting volatile int minGroups;
@@ -221,6 +223,7 @@ public void start(Listener responseListener, Metadata headers) {
221223
public void onBeforeSessionStart(PeerInfo peerInfo) {
222224
afeId = AfeId.extract(peerInfo);
223225
synchronized (ChannelPoolDpImpl.this) {
226+
channelWrapper.consecutiveFailures = 0;
224227
rehomeChannel(channelWrapper, afeId);
225228
sessionsPerAfeId.add(afeId);
226229
}
@@ -232,6 +235,8 @@ public void onClose(Status status, Metadata trailers) {
232235
synchronized (ChannelPoolDpImpl.this) {
233236
if (afeId != null) {
234237
sessionsPerAfeId.remove(afeId);
238+
} else if (!status.isOk() && status.getCode() != Code.CANCELLED) {
239+
channelWrapper.consecutiveFailures++;
235240
}
236241
releaseChannel(channelWrapper, status);
237242
}
@@ -306,12 +311,12 @@ private void releaseChannel(ChannelWrapper channelWrapper, Status status) {
306311
channelWrapper.group.numStreams--;
307312
channelWrapper.numOutstanding--;
308313

309-
if (shouldRecycleChannel(status)) {
314+
if (shouldRecycleChannel(channelWrapper, status)) {
310315
recycleChannel(channelWrapper);
311316
}
312317
}
313318

314-
private static boolean shouldRecycleChannel(Status status) {
319+
private static boolean shouldRecycleChannel(ChannelWrapper channelWrapper, Status status) {
315320
if (status.getCode() == Code.UNIMPLEMENTED) {
316321
return true;
317322
}
@@ -322,6 +327,10 @@ private static boolean shouldRecycleChannel(Status status) {
322327
return true;
323328
}
324329

330+
if (channelWrapper.consecutiveFailures >= CONSECUTIVE_OPEN_FAILURE_THRESHOLD) {
331+
return true;
332+
}
333+
325334
return false;
326335
}
327336

@@ -480,6 +489,7 @@ static class ChannelWrapper {
480489
private final ManagedChannel channel;
481490
private final Instant createdAt;
482491
private int numOutstanding = 0;
492+
private int consecutiveFailures = 0;
483493

484494
public ChannelWrapper(AfeChannelGroup group, ManagedChannel channel, Clock clock) {
485495
this.group = group;

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/channels/ChannelPoolDpImplTest.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,4 +469,98 @@ void testRecycledChannelDoesNotRejoinPool() throws InterruptedException {
469469

470470
pool.close();
471471
}
472+
473+
@Test
474+
void testRecycleChannelOnConsecutiveFailures() {
475+
when(channelSupplier.get()).thenReturn(channel);
476+
when(channel.newCall(any(), any())).thenReturn(clientCall);
477+
doNothing().when(clientCall).start(listener.capture(), any());
478+
479+
ChannelPoolDpImpl pool =
480+
new ChannelPoolDpImpl(channelSupplier, defaultConfig, debugTagTracer, bgExecutor);
481+
482+
for (int i = 0; i < 4; i++) {
483+
pool.newStream(FakeSessionGrpc.getOpenSessionMethod(), CallOptions.DEFAULT)
484+
.start(mock(Listener.class), new Metadata());
485+
listener.getValue().onClose(Status.UNAVAILABLE, new Metadata());
486+
487+
// Should not be recycled yet
488+
verify(channel, times(0)).shutdown();
489+
verify(channelSupplier, times(1)).get();
490+
}
491+
492+
// 5th failure
493+
pool.newStream(FakeSessionGrpc.getOpenSessionMethod(), CallOptions.DEFAULT)
494+
.start(mock(Listener.class), new Metadata());
495+
listener.getValue().onClose(Status.UNAVAILABLE, new Metadata());
496+
497+
// Now it should be recycled
498+
verify(channel, times(1)).shutdown();
499+
verify(channelSupplier, times(2)).get();
500+
501+
pool.close();
502+
}
503+
504+
@Test
505+
void testResetConsecutiveFailuresOnSuccess() {
506+
when(channelSupplier.get()).thenReturn(channel);
507+
when(channel.newCall(any(), any())).thenReturn(clientCall);
508+
doNothing().when(clientCall).start(listener.capture(), any());
509+
doReturn(Attributes.EMPTY).when(clientCall).getAttributes();
510+
511+
ChannelPoolDpImpl pool =
512+
new ChannelPoolDpImpl(channelSupplier, defaultConfig, debugTagTracer, bgExecutor);
513+
514+
// 4 failures
515+
for (int i = 0; i < 4; i++) {
516+
pool.newStream(FakeSessionGrpc.getOpenSessionMethod(), CallOptions.DEFAULT)
517+
.start(mock(Listener.class), new Metadata());
518+
listener.getValue().onClose(Status.UNAVAILABLE, new Metadata());
519+
}
520+
verify(channel, times(0)).shutdown();
521+
522+
// A success: onHeaders (which calls onBeforeSessionStart)
523+
pool.newStream(FakeSessionGrpc.getOpenSessionMethod(), CallOptions.DEFAULT)
524+
.start(mock(Listener.class), new Metadata());
525+
526+
PeerInfo peerInfo = PeerInfo.newBuilder().setApplicationFrontendId(555).build();
527+
Metadata headers = new Metadata();
528+
headers.put(
529+
SessionStreamImpl.PEER_INFO_KEY,
530+
Base64.getEncoder().encodeToString(peerInfo.toByteArray()));
531+
listener.getValue().onHeaders(headers);
532+
listener.getValue().onClose(Status.OK, new Metadata());
533+
534+
// Another 4 failures - should still not recycle because counter was reset
535+
for (int i = 0; i < 4; i++) {
536+
pool.newStream(FakeSessionGrpc.getOpenSessionMethod(), CallOptions.DEFAULT)
537+
.start(mock(Listener.class), new Metadata());
538+
listener.getValue().onClose(Status.UNAVAILABLE, new Metadata());
539+
}
540+
verify(channel, times(0)).shutdown();
541+
542+
pool.close();
543+
}
544+
545+
@Test
546+
void testCancelledDoesNotIncrementFailures() {
547+
when(channelSupplier.get()).thenReturn(channel);
548+
when(channel.newCall(any(), any())).thenReturn(clientCall);
549+
doNothing().when(clientCall).start(listener.capture(), any());
550+
551+
ChannelPoolDpImpl pool =
552+
new ChannelPoolDpImpl(channelSupplier, defaultConfig, debugTagTracer, bgExecutor);
553+
554+
for (int i = 0; i < 10; i++) {
555+
pool.newStream(FakeSessionGrpc.getOpenSessionMethod(), CallOptions.DEFAULT)
556+
.start(mock(Listener.class), new Metadata());
557+
listener.getValue().onClose(Status.CANCELLED, new Metadata());
558+
}
559+
560+
// Should never be recycled
561+
verify(channel, times(0)).shutdown();
562+
verify(channelSupplier, times(1)).get();
563+
564+
pool.close();
565+
}
472566
}

0 commit comments

Comments
 (0)