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

Commit 4f4b430

Browse files
committed
fix
1 parent c3ab878 commit 4f4b430

6 files changed

Lines changed: 128 additions & 52 deletions

File tree

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/dp/ClassicDirectAccessChecker.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import io.grpc.ClientInterceptors;
2626
import io.grpc.ManagedChannel;
2727
import java.util.Optional;
28+
import java.util.concurrent.ScheduledExecutorService;
2829
import java.util.logging.Level;
2930
import java.util.logging.Logger;
31+
import javax.annotation.Nullable;
3032

3133
/**
3234
* Evaluates whether a given channel has Direct Access (DirectPath) routing by executing a RPC and
@@ -37,11 +39,15 @@ public class ClassicDirectAccessChecker implements DirectAccessChecker {
3739
private static final Logger LOG = Logger.getLogger(ClassicDirectAccessChecker.class.getName());
3840
private final DirectPathCompatibleTracer tracer;
3941
private final ChannelPrimer channelPrimer;
42+
private final ScheduledExecutorService executor;
4043

4144
public ClassicDirectAccessChecker(
42-
DirectPathCompatibleTracer tracer, ChannelPrimer channelPrimer) {
45+
DirectPathCompatibleTracer tracer,
46+
ChannelPrimer channelPrimer,
47+
ScheduledExecutorService executor) {
4348
this.tracer = tracer;
4449
this.channelPrimer = channelPrimer;
50+
this.executor = executor;
4551
}
4652

4753
@VisibleForTesting
@@ -54,6 +60,7 @@ public boolean check(Channel channel) {
5460
try {
5561
return evaluateEligibility(channel);
5662
} catch (Exception e) {
63+
investigateFailure(e);
5764
LOG.log(Level.WARNING, "Failed to evaluate direct access eligibility.", e);
5865
return false;
5966
} finally {
@@ -81,7 +88,16 @@ private boolean evaluateEligibility(Channel channel) {
8188
if (isEligible) {
8289
// getIp should be non-null as isEligible is true
8390
tracer.recordSuccess(sidebandData.getIpProtocol());
91+
} else {
92+
investigateFailure(null);
8493
}
8594
return isEligible;
8695
}
96+
97+
@Override
98+
public void investigateFailure(@Nullable Throwable originalError) {
99+
if (executor != null) {
100+
executor.execute(() -> DirectAccessInvestigator.investigateAndReport(tracer, originalError));
101+
}
102+
}
87103
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/dp/DirectAccessChecker.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,11 @@ public interface DirectAccessChecker {
2828
* @return true if the channel is eligible for Direct Access
2929
*/
3030
boolean check(Channel channel);
31+
32+
/**
33+
* Triggers a investigation into why Direct Access routing failed.
34+
*
35+
* @param originalError An optional exception that caused the failure.
36+
*/
37+
void investigateFailure(Throwable originalError);
3138
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
* https://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+
package com.google.cloud.bigtable.data.v2.internal.dp;
17+
18+
import com.google.api.core.InternalApi;
19+
import io.grpc.Channel;
20+
import javax.annotation.Nullable;
21+
22+
@InternalApi
23+
public class NoopDirectAccessChecker implements DirectAccessChecker {
24+
public static final NoopDirectAccessChecker INSTANCE = new NoopDirectAccessChecker();
25+
26+
private NoopDirectAccessChecker() {}
27+
28+
@Override
29+
public boolean check(Channel channel) {
30+
// If it's disabled, it is never eligible.
31+
return false;
32+
}
33+
34+
@Override
35+
public void investigateFailure(@Nullable Throwable originalError) {
36+
// Do nothing. We don't investigate failures if the feature is disabled.
37+
}
38+
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
import com.google.cloud.bigtable.data.v2.internal.csm.MetricsImpl;
3434
import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
3535
import com.google.cloud.bigtable.data.v2.internal.csm.tracers.DirectPathCompatibleTracer;
36+
import com.google.cloud.bigtable.data.v2.internal.csm.tracers.NoopDirectPathCompatibleTracer;
37+
import com.google.cloud.bigtable.data.v2.internal.dp.ClassicDirectAccessChecker;
38+
import com.google.cloud.bigtable.data.v2.internal.dp.DirectAccessChecker;
39+
import com.google.cloud.bigtable.data.v2.internal.dp.NoopDirectAccessChecker;
3640
import com.google.cloud.bigtable.data.v2.stub.metrics.CustomOpenTelemetryMetricsProvider;
3741
import com.google.cloud.bigtable.gaxx.grpc.BigtableTransportChannelProvider;
3842
import com.google.cloud.bigtable.gaxx.grpc.ChannelPrimer;
@@ -46,7 +50,6 @@
4650
import java.io.IOException;
4751
import java.net.URI;
4852
import java.net.URISyntaxException;
49-
import java.util.Optional;
5053
import java.util.concurrent.ScheduledExecutorService;
5154
import java.util.logging.Level;
5255
import java.util.logging.Logger;
@@ -163,18 +166,24 @@ public static BigtableClientContext create(
163166
builder.getHeaderProvider().getHeaders());
164167
}
165168

166-
Optional<DirectPathCompatibleTracer> optionalTracer =
169+
DirectPathCompatibleTracer directPathCompatibleTracer =
167170
settings.isDirectPathEnabledByDefault()
168-
? Optional.of(metrics.getDirectPathCompatibleTracer())
169-
: Optional.empty();
171+
? metrics.getDirectPathCompatibleTracer()
172+
: NoopDirectPathCompatibleTracer.INSTANCE;
173+
174+
DirectAccessChecker directAccessChecker =
175+
settings.isDirectPathEnabledByDefault()
176+
? new ClassicDirectAccessChecker(
177+
directPathCompatibleTracer, channelPrimer, backgroundExecutor)
178+
: NoopDirectAccessChecker.INSTANCE;
170179

171180
BigtableTransportChannelProvider btTransportProvider =
172181
BigtableTransportChannelProvider.create(
173182
transportProvider.build(),
174183
channelPrimer,
175184
metrics.getChannelPoolMetricsTracer(),
176185
backgroundExecutor,
177-
optionalTracer);
186+
directAccessChecker);
178187

179188
builder.setTransportChannelProvider(btTransportProvider);
180189
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java

Lines changed: 19 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,12 @@
2323
import com.google.api.gax.rpc.TransportChannelProvider;
2424
import com.google.auth.Credentials;
2525
import com.google.cloud.bigtable.data.v2.internal.csm.tracers.ChannelPoolMetricsTracer;
26-
import com.google.cloud.bigtable.data.v2.internal.csm.tracers.DirectPathCompatibleTracer;
27-
import com.google.cloud.bigtable.data.v2.internal.dp.ClassicDirectAccessChecker;
2826
import com.google.cloud.bigtable.data.v2.internal.dp.DirectAccessChecker;
29-
import com.google.cloud.bigtable.data.v2.internal.dp.DirectAccessInvestigator;
3027
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings;
3128
import com.google.common.base.Preconditions;
3229
import io.grpc.ManagedChannel;
3330
import java.io.IOException;
3431
import java.util.Map;
35-
import java.util.Optional;
3632
import java.util.concurrent.Executor;
3733
import java.util.concurrent.ScheduledExecutorService;
3834
import java.util.function.Supplier;
@@ -52,20 +48,19 @@ public final class BigtableTransportChannelProvider implements TransportChannelP
5248
private final ChannelPrimer channelPrimer;
5349
@Nullable private final ChannelPoolMetricsTracer channelPoolMetricsTracer;
5450
@Nullable private final ScheduledExecutorService backgroundExecutor;
55-
private final Optional<DirectPathCompatibleTracer> directPathCompatibleTracer;
51+
private final DirectAccessChecker directAccessChecker;
5652

5753
private BigtableTransportChannelProvider(
5854
InstantiatingGrpcChannelProvider instantiatingGrpcChannelProvider,
5955
ChannelPrimer channelPrimer,
6056
@Nullable ChannelPoolMetricsTracer channelPoolMetricsTracer,
6157
@Nullable ScheduledExecutorService backgroundExecutor,
62-
Optional<DirectPathCompatibleTracer> directPathCompatibleTracer) {
58+
DirectAccessChecker directAccessChecker) {
6359
delegate = Preconditions.checkNotNull(instantiatingGrpcChannelProvider);
6460
this.channelPrimer = channelPrimer;
6561
this.channelPoolMetricsTracer = channelPoolMetricsTracer;
6662
this.backgroundExecutor = backgroundExecutor;
67-
this.directPathCompatibleTracer =
68-
directPathCompatibleTracer != null ? directPathCompatibleTracer : Optional.empty();
63+
this.directAccessChecker = directAccessChecker;
6964
}
7065

7166
@Override
@@ -96,7 +91,7 @@ public BigtableTransportChannelProvider withExecutor(Executor executor) {
9691
channelPrimer,
9792
channelPoolMetricsTracer,
9893
backgroundExecutor,
99-
directPathCompatibleTracer);
94+
directAccessChecker);
10095
}
10196

10297
@Override
@@ -109,11 +104,7 @@ public TransportChannelProvider withBackgroundExecutor(ScheduledExecutorService
109104
InstantiatingGrpcChannelProvider newChannelProvider =
110105
(InstantiatingGrpcChannelProvider) delegate.withBackgroundExecutor(executor);
111106
return new BigtableTransportChannelProvider(
112-
newChannelProvider,
113-
channelPrimer,
114-
channelPoolMetricsTracer,
115-
executor,
116-
directPathCompatibleTracer);
107+
newChannelProvider, channelPrimer, channelPoolMetricsTracer, executor, directAccessChecker);
117108
}
118109

119110
@Override
@@ -130,7 +121,7 @@ public BigtableTransportChannelProvider withHeaders(Map<String, String> headers)
130121
channelPrimer,
131122
channelPoolMetricsTracer,
132123
backgroundExecutor,
133-
directPathCompatibleTracer);
124+
directAccessChecker);
134125
}
135126

136127
@Override
@@ -147,7 +138,7 @@ public TransportChannelProvider withEndpoint(String endpoint) {
147138
channelPrimer,
148139
channelPoolMetricsTracer,
149140
backgroundExecutor,
150-
directPathCompatibleTracer);
141+
directAccessChecker);
151142
}
152143

153144
@Deprecated
@@ -166,7 +157,7 @@ public TransportChannelProvider withPoolSize(int size) {
166157
channelPrimer,
167158
channelPoolMetricsTracer,
168159
backgroundExecutor,
169-
directPathCompatibleTracer);
160+
directAccessChecker);
170161
}
171162

172163
/** Expected to only be called once when BigtableClientContext is created */
@@ -179,29 +170,14 @@ public TransportChannel getTransportChannel() throws IOException {
179170

180171
boolean isDirectAccessEligible = false;
181172

182-
if (!directPathCompatibleTracer.isPresent()) {
183-
LOG.fine("Direct access check skipped. Reason: user_disabled or tracer absent");
184-
} else {
185-
DirectPathCompatibleTracer tracer = directPathCompatibleTracer.get();
186-
DirectAccessChecker directAccessChecker =
187-
new ClassicDirectAccessChecker(tracer, channelPrimer);
188-
try {
189-
GrpcTransportChannel grpcTransportChannel =
190-
(GrpcTransportChannel) directAccessProvider.getTransportChannel();
191-
ManagedChannel directAccessChannel = (ManagedChannel) grpcTransportChannel.getChannel();
192-
193-
isDirectAccessEligible = directAccessChecker.check(directAccessChannel);
194-
if (!isDirectAccessEligible && backgroundExecutor != null) {
195-
backgroundExecutor.execute(
196-
() -> DirectAccessInvestigator.investigateAndReport(tracer, null));
197-
}
198-
} catch (Exception e) {
199-
LOG.log(Level.FINE, "Client is not direct access eligible, using standard transport.", e);
200-
if (backgroundExecutor != null) {
201-
backgroundExecutor.execute(
202-
() -> DirectAccessInvestigator.investigateAndReport(tracer, e));
203-
}
204-
}
173+
try {
174+
GrpcTransportChannel grpcTransportChannel =
175+
(GrpcTransportChannel) directAccessProvider.getTransportChannel();
176+
ManagedChannel directAccessChannel = (ManagedChannel) grpcTransportChannel.getChannel();
177+
isDirectAccessEligible = directAccessChecker.check(directAccessChannel);
178+
} catch (Exception e) {
179+
LOG.log(Level.WARNING, "Failed to check for direct access.", e);
180+
directAccessChecker.investigateFailure(e);
205181
}
206182

207183
InstantiatingGrpcChannelProvider selectedProvider;
@@ -268,7 +244,7 @@ public TransportChannelProvider withCredentials(Credentials credentials) {
268244
channelPrimer,
269245
channelPoolMetricsTracer,
270246
backgroundExecutor,
271-
directPathCompatibleTracer);
247+
directAccessChecker);
272248
}
273249

274250
/** Creates a BigtableTransportChannelProvider. */
@@ -277,12 +253,12 @@ public static BigtableTransportChannelProvider create(
277253
ChannelPrimer channelPrimer,
278254
ChannelPoolMetricsTracer outstandingRpcsMetricTracker,
279255
ScheduledExecutorService backgroundExecutor,
280-
Optional<DirectPathCompatibleTracer> directPathCompatibleTracer) {
256+
DirectAccessChecker directAccessChecker) {
281257
return new BigtableTransportChannelProvider(
282258
instantiatingGrpcChannelProvider,
283259
channelPrimer,
284260
outstandingRpcsMetricTracker,
285261
backgroundExecutor,
286-
directPathCompatibleTracer);
262+
directAccessChecker);
287263
}
288264
}

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/internal/dp/ClassicDirectAccessCheckerTest.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717

1818
import static com.google.common.truth.Truth.assertThat;
1919
import static org.mockito.ArgumentMatchers.any;
20-
import static org.mockito.Mockito.*;
20+
import static org.mockito.Mockito.anyString;
21+
import static org.mockito.Mockito.doReturn;
22+
import static org.mockito.Mockito.doThrow;
23+
import static org.mockito.Mockito.spy;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.verifyNoInteractions;
26+
import static org.mockito.Mockito.when;
2127

2228
import com.google.bigtable.v2.PeerInfo;
2329
import com.google.cloud.bigtable.data.v2.internal.csm.attributes.Util;
@@ -26,11 +32,13 @@
2632
import com.google.cloud.bigtable.gaxx.grpc.ChannelPrimer;
2733
import io.grpc.Channel;
2834
import io.grpc.ManagedChannel;
35+
import java.util.concurrent.ScheduledExecutorService;
2936
import org.junit.Before;
3037
import org.junit.Rule;
3138
import org.junit.Test;
3239
import org.junit.runner.RunWith;
3340
import org.junit.runners.JUnit4;
41+
import org.mockito.ArgumentCaptor;
3442
import org.mockito.Mock;
3543
import org.mockito.junit.MockitoJUnit;
3644
import org.mockito.junit.MockitoRule;
@@ -45,12 +53,14 @@ public class ClassicDirectAccessCheckerTest {
4553
@Mock private DirectPathCompatibleTracer mockTracer;
4654
@Mock private MetadataExtractorInterceptor mockInterceptor;
4755
@Mock private MetadataExtractorInterceptor.SidebandData mockSidebandData;
56+
@Mock private ScheduledExecutorService mockExecutor;
4857

4958
private ClassicDirectAccessChecker checker;
5059

5160
@Before
5261
public void setUp() throws Exception {
53-
checker = spy(new ClassicDirectAccessChecker(mockTracer, mockChannelPrimer));
62+
// Pass null for the executor by default so background investigations aren't triggered
63+
checker = spy(new ClassicDirectAccessChecker(mockTracer, mockChannelPrimer, null));
5464
doReturn(mockInterceptor).when(checker).createInterceptor();
5565
when(mockInterceptor.getSidebandData()).thenReturn(mockSidebandData);
5666
}
@@ -83,7 +93,7 @@ public void testNotEligibleCFE() {
8393
boolean isEligible = checker.check(mockChannel);
8494

8595
assertThat(isEligible).isFalse();
86-
verifyNoInteractions(mockTracer);
96+
verifyNoInteractions(mockTracer); // No interactions because executor is null
8797
verify(mockChannel).shutdownNow();
8898
}
8999

@@ -122,4 +132,24 @@ public void testNullPeerInfoIsHandledSafely() {
122132
verifyNoInteractions(mockTracer);
123133
verify(mockChannel).shutdownNow();
124134
}
135+
136+
@Test
137+
public void testInvestigationTriggeredOnFailure() {
138+
// Re-instantiate the checker with a mock executor to verify investigation is scheduled
139+
checker = spy(new ClassicDirectAccessChecker(mockTracer, mockChannelPrimer, mockExecutor));
140+
doReturn(mockInterceptor).when(checker).createInterceptor();
141+
when(mockInterceptor.getSidebandData()).thenReturn(null); // Force a failure
142+
143+
boolean isEligible = checker.check(mockChannel);
144+
145+
assertThat(isEligible).isFalse();
146+
147+
// Verify the checker submitted a Runnable task to the background executor
148+
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
149+
verify(mockExecutor).execute(runnableCaptor.capture());
150+
151+
// Execute the captured runnable to ensure it safely calls the tracer
152+
runnableCaptor.getValue().run();
153+
verify(mockTracer).recordFailure(anyString());
154+
}
125155
}

0 commit comments

Comments
 (0)