Skip to content

Commit 91d15ce

Browse files
Add ClientTransportFilter (grpc#10646)
* Add ClientTransportFilter
1 parent 7692a9f commit 91d15ce

File tree

20 files changed

+205
-14
lines changed

20 files changed

+205
-14
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2023 The gRPC Authors
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 io.grpc;
18+
19+
/**
20+
* Listens on the client transport life-cycle events. These filters do not have the capability
21+
* to modify the channels or transport life-cycle event behavior, but they can be useful hooks
22+
* for transport observability. Multiple filters may be registered to the client.
23+
*
24+
* @since 1.61.0
25+
*/
26+
@ExperimentalApi("https://gitub.com/grpc/grpc-java/issues/10652")
27+
public abstract class ClientTransportFilter {
28+
/**
29+
* Called when a transport is ready to accept traffic (when a connection has been established).
30+
* The default implementation is a no-op.
31+
*
32+
* @param transportAttrs current transport attributes
33+
*
34+
* @return new transport attributes. Default implementation returns the passed-in attributes
35+
* intact.
36+
*/
37+
public Attributes transportReady(Attributes transportAttrs) {
38+
return transportAttrs;
39+
}
40+
41+
/**
42+
* Called when a transport completed shutting down. All resources have been released.
43+
* All streams have either been closed or transferred off this transport.
44+
* Default implementation is a no-op
45+
*
46+
* @param transportAttrs the effective transport attributes, which is what is returned by {@link
47+
* #transportReady} of the last executed filter.
48+
*/
49+
public void transportTerminated(Attributes transportAttrs) {
50+
}
51+
}

api/src/main/java/io/grpc/ForwardingChannelBuilder2.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ public T intercept(ClientInterceptor... interceptors) {
9494
return thisT();
9595
}
9696

97+
@Override
98+
public T addTransportFilter(ClientTransportFilter transportFilter) {
99+
delegate().addTransportFilter(transportFilter);
100+
return thisT();
101+
}
102+
97103
@Override
98104
public T userAgent(String userAgent) {
99105
delegate().userAgent(userAgent);

api/src/main/java/io/grpc/ManagedChannelBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,18 @@ public T offloadExecutor(Executor executor) {
159159
*/
160160
public abstract T intercept(ClientInterceptor... interceptors);
161161

162+
/**
163+
* Adds a {@link ClientTransportFilter}. The order of filters being added is the order they will
164+
* be executed
165+
*
166+
* @return this
167+
* @since 1.60.0
168+
*/
169+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10652")
170+
public T addTransportFilter(ClientTransportFilter filter) {
171+
throw new UnsupportedOperationException();
172+
}
173+
162174
/**
163175
* Provides a custom {@code User-Agent} for the application.
164176
*

binder/src/main/java/io/grpc/binder/internal/BinderTransport.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ private void checkSecurityPolicy(IBinder binder) {
758758
// triggers), could have shut us down.
759759
if (!isShutdown()) {
760760
setState(TransportState.READY);
761+
attributes = clientTransportListener.filterTransport(attributes);
761762
clientTransportListener.transportReady();
762763
}
763764
}

core/src/main/java/io/grpc/internal/InternalSubchannel.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import io.grpc.ChannelLogger;
3636
import io.grpc.ChannelLogger.ChannelLogLevel;
3737
import io.grpc.ClientStreamTracer;
38+
import io.grpc.ClientTransportFilter;
3839
import io.grpc.ConnectivityState;
3940
import io.grpc.ConnectivityStateInfo;
4041
import io.grpc.EquivalentAddressGroup;
@@ -77,6 +78,8 @@ final class InternalSubchannel implements InternalInstrumented<ChannelStats>, Tr
7778
private final ChannelTracer channelTracer;
7879
private final ChannelLogger channelLogger;
7980

81+
private final List<ClientTransportFilter> transportFilters;
82+
8083
/**
8184
* All field must be mutated in the syncContext.
8285
*/
@@ -159,7 +162,8 @@ protected void handleNotInUse() {
159162
ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor,
160163
Supplier<Stopwatch> stopwatchSupplier, SynchronizationContext syncContext, Callback callback,
161164
InternalChannelz channelz, CallTracer callsTracer, ChannelTracer channelTracer,
162-
InternalLogId logId, ChannelLogger channelLogger) {
165+
InternalLogId logId, ChannelLogger channelLogger,
166+
List<ClientTransportFilter> transportFilters) {
163167
Preconditions.checkNotNull(addressGroups, "addressGroups");
164168
Preconditions.checkArgument(!addressGroups.isEmpty(), "addressGroups is empty");
165169
checkListHasNoNulls(addressGroups, "addressGroups contains null entry");
@@ -180,6 +184,7 @@ protected void handleNotInUse() {
180184
this.channelTracer = Preconditions.checkNotNull(channelTracer, "channelTracer");
181185
this.logId = Preconditions.checkNotNull(logId, "logId");
182186
this.channelLogger = Preconditions.checkNotNull(channelLogger, "channelLogger");
187+
this.transportFilters = transportFilters;
183188
}
184189

185190
ChannelLogger getChannelLogger() {
@@ -539,6 +544,15 @@ private class TransportListener implements ManagedClientTransport.Listener {
539544
this.transport = transport;
540545
}
541546

547+
@Override
548+
public Attributes filterTransport(Attributes attributes) {
549+
for (ClientTransportFilter filter : transportFilters) {
550+
attributes = Preconditions.checkNotNull(filter.transportReady(attributes),
551+
"Filter %s returned null", filter);
552+
}
553+
return attributes;
554+
}
555+
542556
@Override
543557
public void transportReady() {
544558
channelLogger.log(ChannelLogLevel.INFO, "READY");
@@ -607,6 +621,9 @@ public void transportTerminated() {
607621
channelLogger.log(ChannelLogLevel.INFO, "{0} Terminated", transport.getLogId());
608622
channelz.removeClientSocket(transport);
609623
handleTransportInUseState(transport, false);
624+
for (ClientTransportFilter filter : transportFilters) {
625+
filter.transportTerminated(transport.getAttributes());
626+
}
610627
syncContext.execute(new Runnable() {
611628
@Override
612629
public void run() {

core/src/main/java/io/grpc/internal/ManagedChannelImpl.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import io.grpc.ClientInterceptor;
4343
import io.grpc.ClientInterceptors;
4444
import io.grpc.ClientStreamTracer;
45+
import io.grpc.ClientTransportFilter;
4546
import io.grpc.CompressorRegistry;
4647
import io.grpc.ConnectivityState;
4748
import io.grpc.ConnectivityStateInfo;
@@ -209,6 +210,8 @@ public void uncaughtException(Thread t, Throwable e) {
209210
* {@link RealChannel}.
210211
*/
211212
private final Channel interceptorChannel;
213+
214+
private final List<ClientTransportFilter> transportFilters;
212215
@Nullable private final String userAgent;
213216

214217
// Only null after channel is terminated. Must be assigned from the syncContext.
@@ -661,6 +664,7 @@ ClientStream newSubstream(
661664
channel = builder.binlog.wrapChannel(channel);
662665
}
663666
this.interceptorChannel = ClientInterceptors.intercept(channel, interceptors);
667+
this.transportFilters = new ArrayList<>(builder.transportFilters);
664668
this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
665669
if (builder.idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) {
666670
this.idleTimeoutMillis = builder.idleTimeoutMillis;
@@ -1566,7 +1570,8 @@ void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) {
15661570
callTracerFactory.create(),
15671571
subchannelTracer,
15681572
subchannelLogId,
1569-
subchannelLogger);
1573+
subchannelLogger,
1574+
transportFilters);
15701575
oobChannelTracer.reportEvent(new ChannelTrace.Event.Builder()
15711576
.setDescription("Child Subchannel created")
15721577
.setSeverity(ChannelTrace.Event.Severity.CT_INFO)
@@ -1990,7 +1995,8 @@ void onNotInUse(InternalSubchannel is) {
19901995
callTracerFactory.create(),
19911996
subchannelTracer,
19921997
subchannelLogId,
1993-
subchannelLogger);
1998+
subchannelLogger,
1999+
transportFilters);
19942000

19952001
channelTracer.reportEvent(new ChannelTrace.Event.Builder()
19962002
.setDescription("Child Subchannel started")
@@ -2148,6 +2154,11 @@ public void transportReady() {
21482154
// Don't care
21492155
}
21502156

2157+
@Override
2158+
public Attributes filterTransport(Attributes attributes) {
2159+
return attributes;
2160+
}
2161+
21512162
@Override
21522163
public void transportInUse(final boolean inUse) {
21532164
inUseStateAggregator.updateObjectInUse(delayedTransport, inUse);

core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.grpc.internal;
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
20+
import static com.google.common.base.Preconditions.checkNotNull;
2021

2122
import com.google.common.annotations.VisibleForTesting;
2223
import com.google.common.base.Preconditions;
@@ -27,6 +28,7 @@
2728
import io.grpc.CallCredentials;
2829
import io.grpc.ChannelCredentials;
2930
import io.grpc.ClientInterceptor;
31+
import io.grpc.ClientTransportFilter;
3032
import io.grpc.CompressorRegistry;
3133
import io.grpc.DecompressorRegistry;
3234
import io.grpc.EquivalentAddressGroup;
@@ -137,6 +139,8 @@ public static ManagedChannelBuilder<?> forTarget(String target) {
137139
private final List<ClientInterceptor> interceptors = new ArrayList<>();
138140
NameResolverRegistry nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
139141

142+
final List<ClientTransportFilter> transportFilters = new ArrayList<>();
143+
140144
final String target;
141145
@Nullable
142146
final ChannelCredentials channelCredentials;
@@ -267,11 +271,11 @@ public ManagedChannelImplBuilder(
267271
String target, @Nullable ChannelCredentials channelCreds, @Nullable CallCredentials callCreds,
268272
ClientTransportFactoryBuilder clientTransportFactoryBuilder,
269273
@Nullable ChannelBuilderDefaultPortProvider channelBuilderDefaultPortProvider) {
270-
this.target = Preconditions.checkNotNull(target, "target");
274+
this.target = checkNotNull(target, "target");
271275
this.channelCredentials = channelCreds;
272276
this.callCredentials = callCreds;
273-
this.clientTransportFactoryBuilder = Preconditions
274-
.checkNotNull(clientTransportFactoryBuilder, "clientTransportFactoryBuilder");
277+
this.clientTransportFactoryBuilder = checkNotNull(clientTransportFactoryBuilder,
278+
"clientTransportFactoryBuilder");
275279
this.directServerAddress = null;
276280

277281
if (channelBuilderDefaultPortProvider != null) {
@@ -323,8 +327,8 @@ public ManagedChannelImplBuilder(SocketAddress directServerAddress, String autho
323327
this.target = makeTargetStringForDirectAddress(directServerAddress);
324328
this.channelCredentials = channelCreds;
325329
this.callCredentials = callCreds;
326-
this.clientTransportFactoryBuilder = Preconditions
327-
.checkNotNull(clientTransportFactoryBuilder, "clientTransportFactoryBuilder");
330+
this.clientTransportFactoryBuilder = checkNotNull(clientTransportFactoryBuilder,
331+
"clientTransportFactoryBuilder");
328332
this.directServerAddress = directServerAddress;
329333
NameResolverRegistry reg = new NameResolverRegistry();
330334
reg.register(new DirectAddressNameResolverProvider(directServerAddress,
@@ -374,6 +378,12 @@ public ManagedChannelImplBuilder intercept(ClientInterceptor... interceptors) {
374378
return intercept(Arrays.asList(interceptors));
375379
}
376380

381+
@Override
382+
public ManagedChannelImplBuilder addTransportFilter(ClientTransportFilter hook) {
383+
transportFilters.add(checkNotNull(hook, "transport filter"));
384+
return this;
385+
}
386+
377387
@Deprecated
378388
@Override
379389
public ManagedChannelImplBuilder nameResolverFactory(NameResolver.Factory resolverFactory) {

core/src/main/java/io/grpc/internal/ManagedClientTransport.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.grpc.internal;
1818

19+
import io.grpc.Attributes;
1920
import io.grpc.Status;
2021
import javax.annotation.CheckReturnValue;
2122
import javax.annotation.Nullable;
@@ -104,5 +105,11 @@ interface Listener {
104105
* at least one stream.
105106
*/
106107
void transportInUse(boolean inUse);
108+
109+
/**
110+
* Called just before {@link #transportReady} to allow direct modification of transport
111+
* Attributes.
112+
*/
113+
Attributes filterTransport(Attributes attributes);
107114
}
108115
}

core/src/main/java/io/grpc/internal/OobChannel.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ public void transportReady() {
130130
// Don't care
131131
}
132132

133+
@Override
134+
public Attributes filterTransport(Attributes attributes) {
135+
return attributes;
136+
}
137+
133138
@Override
134139
public void transportInUse(boolean inUse) {
135140
// Don't care

core/src/test/java/io/grpc/internal/InternalSubchannelTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import io.grpc.internal.TestUtils.MockClientTransportInfo;
5555
import java.net.SocketAddress;
5656
import java.util.Arrays;
57+
import java.util.Collections;
5758
import java.util.LinkedList;
5859
import java.util.List;
5960
import java.util.concurrent.BlockingQueue;
@@ -1360,7 +1361,8 @@ private void createInternalSubchannel(EquivalentAddressGroup ... addrs) {
13601361
channelz, CallTracer.getDefaultFactory().create(),
13611362
subchannelTracer,
13621363
logId,
1363-
new ChannelLoggerImpl(subchannelTracer, fakeClock.getTimeProvider()));
1364+
new ChannelLoggerImpl(subchannelTracer, fakeClock.getTimeProvider()),
1365+
Collections.emptyList());
13641366
}
13651367

13661368
private void assertNoCallbackInvoke() {

0 commit comments

Comments
 (0)