Skip to content

Commit d369733

Browse files
reuse gRPC xDS channel by ref-counting
1 parent 310ce7b commit d369733

2 files changed

Lines changed: 106 additions & 5 deletions

File tree

xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,39 +31,61 @@
3131
import io.grpc.Status;
3232
import io.grpc.xds.client.Bootstrapper;
3333
import io.grpc.xds.client.XdsTransportFactory;
34+
import java.util.Map;
35+
import java.util.concurrent.ConcurrentHashMap;
3436
import java.util.concurrent.TimeUnit;
3537

3638
final class GrpcXdsTransportFactory implements XdsTransportFactory {
3739

3840
private final CallCredentials callCredentials;
41+
// The map of xDS server info to its corresponding gRPC xDS transport.
42+
// This enables reusing and sharing the same underlying gRPC channel.
43+
private static final Map<Bootstrapper.ServerInfo, GrpcXdsTransport> xdsServerInfoToTransportMap =
44+
new ConcurrentHashMap<>();
3945

4046
GrpcXdsTransportFactory(CallCredentials callCredentials) {
4147
this.callCredentials = callCredentials;
4248
}
4349

4450
@Override
4551
public XdsTransport create(Bootstrapper.ServerInfo serverInfo) {
46-
return new GrpcXdsTransport(serverInfo, callCredentials);
52+
return xdsServerInfoToTransportMap.compute(
53+
serverInfo,
54+
(info, transport) -> {
55+
if (transport == null) {
56+
transport = new GrpcXdsTransport(serverInfo, callCredentials);
57+
}
58+
++transport.refCount;
59+
return transport;
60+
});
4761
}
4862

4963
@VisibleForTesting
5064
public XdsTransport createForTest(ManagedChannel channel) {
51-
return new GrpcXdsTransport(channel, callCredentials);
65+
return new GrpcXdsTransport(channel, callCredentials, null);
66+
}
67+
68+
@VisibleForTesting
69+
static boolean hasTransport(Bootstrapper.ServerInfo serverInfo) {
70+
return xdsServerInfoToTransportMap.containsKey(serverInfo);
5271
}
5372

5473
@VisibleForTesting
5574
static class GrpcXdsTransport implements XdsTransport {
5675

5776
private final ManagedChannel channel;
5877
private final CallCredentials callCredentials;
78+
private final Bootstrapper.ServerInfo serverInfo;
79+
// Must only be accessed within the provided atomic methods of ConcurrentHashMap.
80+
private int refCount = 0;
5981

6082
public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo) {
6183
this(serverInfo, null);
6284
}
6385

6486
@VisibleForTesting
6587
public GrpcXdsTransport(ManagedChannel channel) {
66-
this(channel, null);
88+
this(channel, null, null);
6789
}
6890

6991
public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo, CallCredentials callCredentials) {
@@ -73,12 +95,17 @@ public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo, CallCredentials call
7395
.keepAliveTime(5, TimeUnit.MINUTES)
7496
.build();
7597
this.callCredentials = callCredentials;
98+
this.serverInfo = serverInfo;
7699
}
77100

78101
@VisibleForTesting
79-
public GrpcXdsTransport(ManagedChannel channel, CallCredentials callCredentials) {
102+
public GrpcXdsTransport(
103+
ManagedChannel channel,
104+
CallCredentials callCredentials,
105+
Bootstrapper.ServerInfo serverInfo) {
80106
this.channel = checkNotNull(channel, "channel");
81107
this.callCredentials = callCredentials;
108+
this.serverInfo = serverInfo;
82109
}
83110

84111
@Override
@@ -98,7 +125,19 @@ public <ReqT, RespT> StreamingCall<ReqT, RespT> createStreamingCall(
98125

99126
@Override
100127
public void shutdown() {
101-
channel.shutdown();
128+
if (serverInfo == null) {
129+
channel.shutdown();
130+
return;
131+
}
132+
xdsServerInfoToTransportMap.computeIfPresent(
133+
serverInfo,
134+
(info, transport) -> {
135+
if (--transport.refCount == 0) { // Prefix decrement and return the updated value.
136+
transport.channel.shutdown();
137+
return null; // Remove mapping.
138+
}
139+
return transport;
140+
});
102141
}
103142

104143
private class XdsStreamingCall<ReqT, RespT> implements

xds/src/test/java/io/grpc/xds/GrpcXdsTransportFactoryTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,68 @@ public void callApis() throws Exception {
118118
xdsTransport.shutdown();
119119
}
120120

121+
@Test
122+
public void refCountedXdsTransport_sameXdsServerAddress_returnsExistingTransport() {
123+
Bootstrapper.ServerInfo xdsServerInfo =
124+
Bootstrapper.ServerInfo.create(
125+
"localhost:" + server.getPort(), InsecureChannelCredentials.create());
126+
GrpcXdsTransportFactory xdsTransportFactory = new GrpcXdsTransportFactory(null);
127+
// Verify calling create() for the first time creates a new GrpcXdsTransport instance.
128+
// The ref count was previously 0 and now is 1.
129+
XdsTransportFactory.XdsTransport transport1 = xdsTransportFactory.create(xdsServerInfo);
130+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo)).isTrue();
131+
// Verify calling create() for the second time to the same xDS server address returns the same
132+
// GrpcXdsTransport instance. The ref count was previously 1 and now is 2.
133+
XdsTransportFactory.XdsTransport transport2 = xdsTransportFactory.create(xdsServerInfo);
134+
assertThat(transport1).isSameInstanceAs(transport2);
135+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo)).isTrue();
136+
// Verify calling shutdown() for the first time does not shut down the GrpcXdsTransport
137+
// instance. The ref count was previously 2 and now is 1.
138+
transport1.shutdown();
139+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo)).isTrue();
140+
// Verify calling shutdown() for the second time shuts down and cleans up the
141+
// GrpcXdsTransport instance. The ref count was previously 1 and now is 0.
142+
transport2.shutdown();
143+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo)).isFalse();
144+
}
145+
146+
@Test
147+
public void refCountedXdsTransport_differentXdsServerAddress_returnsDifferentTransport()
148+
throws Exception {
149+
// Create and start a second xDS server on a different port.
150+
Server server2 =
151+
Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create())
152+
.addService(echoAdsService())
153+
.build()
154+
.start();
155+
Bootstrapper.ServerInfo xdsServerInfo1 =
156+
Bootstrapper.ServerInfo.create(
157+
"localhost:" + server.getPort(), InsecureChannelCredentials.create());
158+
Bootstrapper.ServerInfo xdsServerInfo2 =
159+
Bootstrapper.ServerInfo.create(
160+
"localhost:" + server2.getPort(), InsecureChannelCredentials.create());
161+
GrpcXdsTransportFactory xdsTransportFactory = new GrpcXdsTransportFactory(null);
162+
// Verify calling create() to the first xDS server creates a new GrpcXdsTransport instance.
163+
// The ref count was previously 0 and now is 1.
164+
XdsTransportFactory.XdsTransport transport1 = xdsTransportFactory.create(xdsServerInfo1);
165+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo1)).isTrue();
166+
// Verify calling create() to the second xDS server creates a different GrpcXdsTransport
167+
// instance. The ref count was previously 0 and now is 1.
168+
XdsTransportFactory.XdsTransport transport2 = xdsTransportFactory.create(xdsServerInfo2);
169+
assertThat(transport1).isNotSameInstanceAs(transport2);
170+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo2)).isTrue();
171+
// Verify calling shutdown() shuts down and cleans up the GrpcXdsTransport instance for
172+
// the first xDS server. The ref count was previously 1 and now is 0.
173+
transport1.shutdown();
174+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo1)).isFalse();
175+
// Verify calling shutdown() shuts down and cleans up the GrpcXdsTransport instance for
176+
// the second xDS server. The ref count was previously 1 and now is 0.
177+
transport2.shutdown();
178+
assertThat(GrpcXdsTransportFactory.hasTransport(xdsServerInfo2)).isFalse();
179+
// Clean up the second xDS server.
180+
server2.shutdown();
181+
}
182+
121183
private static class FakeEventHandler implements
122184
XdsTransportFactory.EventHandler<DiscoveryResponse> {
123185
private final BlockingQueue<DiscoveryResponse> respQ = new LinkedBlockingQueue<>();

0 commit comments

Comments
 (0)