Skip to content

Commit 8cd4bc5

Browse files
committed
Add support for anonymous in-process servers.
Anonymous servers aren't registered statically, meaning they can't be referenced by name. Only the InProcessSocketAddress, fetched via Server.getListenSockets() can be used to connect to the server. This is particularly useful for production Android usage of in-process servers, where process startup latency is crucial, since a custom name resolver can be used to create the server instance on demand without directly impacting the startup latency of in-process gRPC clients. This approach supports a more-standard approach to "OnDeviceServer" referenced in gRFC L73. https://github.com/grpc/proposal/blob/master/L73-java-binderchannel.md#ondeviceserver
1 parent 9266174 commit 8cd4bc5

File tree

6 files changed

+116
-45
lines changed

6 files changed

+116
-45
lines changed

core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static com.google.common.base.Preconditions.checkArgument;
2020
import static com.google.common.base.Preconditions.checkNotNull;
2121

22-
import com.google.errorprone.annotations.DoNotCall;
2322
import io.grpc.ChannelCredentials;
2423
import io.grpc.ChannelLogger;
2524
import io.grpc.ExperimentalApi;
@@ -55,33 +54,37 @@ public final class InProcessChannelBuilder extends
5554
* @return a new builder
5655
*/
5756
public static InProcessChannelBuilder forName(String name) {
58-
return new InProcessChannelBuilder(name);
57+
return forAddress(new InProcessSocketAddress(checkNotNull(name, "name")));
5958
}
6059

6160
/**
62-
* Always fails. Call {@link #forName} instead.
61+
* Create a channel builder that will connect to the server referenced by the given target URI.
62+
* Only intended for use with a custom name resolver.
63+
*
64+
* @param target the identity of the server to connect to
65+
* @return a new builder
6366
*/
64-
@DoNotCall("Unsupported. Use forName() instead")
6567
public static InProcessChannelBuilder forTarget(String target) {
66-
throw new UnsupportedOperationException("call forName() instead");
68+
return new InProcessChannelBuilder(null, checkNotNull(target, "target"));
6769
}
6870

6971
/**
70-
* Always fails. Call {@link #forName} instead.
72+
* Create a channel builder that will connect to the server referenced by the given address.
73+
*
74+
* @param address the address of the server to connect to
75+
* @return a new builder
7176
*/
72-
@DoNotCall("Unsupported. Use forName() instead")
73-
public static InProcessChannelBuilder forAddress(String name, int port) {
74-
throw new UnsupportedOperationException("call forName() instead");
77+
public static InProcessChannelBuilder forAddress(InProcessSocketAddress address) {
78+
return new InProcessChannelBuilder(checkNotNull(address, "address"), null);
7579
}
7680

7781
private final ManagedChannelImplBuilder managedChannelImplBuilder;
78-
private final String name;
7982
private ScheduledExecutorService scheduledExecutorService;
8083
private int maxInboundMetadataSize = Integer.MAX_VALUE;
8184
private boolean transportIncludeStatusCause = false;
8285

83-
private InProcessChannelBuilder(String name) {
84-
this.name = checkNotNull(name, "name");
86+
private InProcessChannelBuilder(
87+
@Nullable InProcessSocketAddress directAddress, @Nullable String target) {
8588

8689
final class InProcessChannelTransportFactoryBuilder implements ClientTransportFactoryBuilder {
8790
@Override
@@ -90,8 +93,13 @@ public ClientTransportFactory buildClientTransportFactory() {
9093
}
9194
}
9295

93-
managedChannelImplBuilder = new ManagedChannelImplBuilder(new InProcessSocketAddress(name),
94-
"localhost", new InProcessChannelTransportFactoryBuilder(), null);
96+
if (directAddress != null) {
97+
managedChannelImplBuilder = new ManagedChannelImplBuilder(directAddress, "localhost",
98+
new InProcessChannelTransportFactoryBuilder(), null);
99+
} else {
100+
managedChannelImplBuilder = new ManagedChannelImplBuilder(target,
101+
new InProcessChannelTransportFactoryBuilder(), null);
102+
}
95103

96104
// In-process transport should not record its traffic to the stats module.
97105
// https://github.com/grpc/grpc-java/issues/2284
@@ -204,7 +212,7 @@ public InProcessChannelBuilder propagateCauseWithStatus(boolean enable) {
204212

205213
ClientTransportFactory buildTransportFactory() {
206214
return new InProcessClientTransportFactory(
207-
name, scheduledExecutorService, maxInboundMetadataSize, transportIncludeStatusCause);
215+
scheduledExecutorService, maxInboundMetadataSize, transportIncludeStatusCause);
208216
}
209217

210218
void setStatsEnabled(boolean value) {
@@ -215,18 +223,15 @@ void setStatsEnabled(boolean value) {
215223
* Creates InProcess transports. Exposed for internal use, as it should be private.
216224
*/
217225
static final class InProcessClientTransportFactory implements ClientTransportFactory {
218-
private final String name;
219226
private final ScheduledExecutorService timerService;
220227
private final boolean useSharedTimer;
221228
private final int maxInboundMetadataSize;
222229
private boolean closed;
223230
private final boolean includeCauseWithStatus;
224231

225232
private InProcessClientTransportFactory(
226-
String name,
227233
@Nullable ScheduledExecutorService scheduledExecutorService,
228234
int maxInboundMetadataSize, boolean includeCauseWithStatus) {
229-
this.name = name;
230235
useSharedTimer = scheduledExecutorService == null;
231236
timerService = useSharedTimer
232237
? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : scheduledExecutorService;
@@ -242,7 +247,7 @@ public ConnectionClientTransport newClientTransport(
242247
}
243248
// TODO(carl-mastrangelo): Pass channelLogger in.
244249
return new InProcessTransport(
245-
name, maxInboundMetadataSize, options.getAuthority(), options.getUserAgent(),
250+
addr, maxInboundMetadataSize, options.getAuthority(), options.getUserAgent(),
246251
options.getEagAttributes(), includeCauseWithStatus);
247252
}
248253

core/src/main/java/io/grpc/inprocess/InProcessServer.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,20 @@ final class InProcessServer implements InternalServer {
4040
private static final ConcurrentMap<String, InProcessServer> registry
4141
= new ConcurrentHashMap<>();
4242

43-
static InProcessServer findServer(String name) {
44-
return registry.get(name);
43+
static InProcessServer findServer(SocketAddress addr) {
44+
if (addr instanceof InProcessSocketAddress) {
45+
InProcessSocketAddress inProcessAddress = (InProcessSocketAddress) addr;
46+
if (inProcessAddress.getServer() != null) {
47+
return inProcessAddress.getServer();
48+
} else {
49+
return registry.get(inProcessAddress.getName());
50+
}
51+
}
52+
return null;
4553
}
4654

4755
private final String name;
56+
private final boolean anonymous;
4857
private final int maxInboundMetadataSize;
4958
private final List<ServerStreamTracer.Factory> streamTracerFactories;
5059
private ServerListener listener;
@@ -61,6 +70,7 @@ static InProcessServer findServer(String name) {
6170
InProcessServerBuilder builder,
6271
List<? extends ServerStreamTracer.Factory> streamTracerFactories) {
6372
this.name = builder.name;
73+
this.anonymous = builder.anonymous;
6474
this.schedulerPool = builder.schedulerPool;
6575
this.maxInboundMetadataSize = builder.maxInboundMetadataSize;
6676
this.streamTracerFactories =
@@ -71,15 +81,17 @@ static InProcessServer findServer(String name) {
7181
public void start(ServerListener serverListener) throws IOException {
7282
this.listener = serverListener;
7383
this.scheduler = schedulerPool.getObject();
74-
// Must be last, as channels can start connecting after this point.
75-
if (registry.putIfAbsent(name, this) != null) {
76-
throw new IOException("name already registered: " + name);
84+
if (!anonymous) {
85+
// Must be last, as channels can start connecting after this point.
86+
if (registry.putIfAbsent(name, this) != null) {
87+
throw new IOException("name already registered: " + name);
88+
}
7789
}
7890
}
7991

8092
@Override
8193
public SocketAddress getListenSocketAddress() {
82-
return new InProcessSocketAddress(name);
94+
return new InProcessSocketAddress(name, anonymous ? this : null);
8395
}
8496

8597
@Override

core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,18 @@ public final class InProcessServerBuilder extends
8181
* @return a new builder
8282
*/
8383
public static InProcessServerBuilder forName(String name) {
84-
return new InProcessServerBuilder(name);
84+
return new InProcessServerBuilder(name, false);
85+
}
86+
87+
/**
88+
* Create a server builder for an anonymous in-process server.
89+
* Anonymouns servers can only be connected to via their listen address,
90+
* and can't be referenced by name.
91+
* @param name a server identifier used for logging purposes only.
92+
* @return a new builder
93+
*/
94+
public static InProcessServerBuilder anonymous(String name) {
95+
return new InProcessServerBuilder("anon:" + name, true);
8596
}
8697

8798
/**
@@ -101,12 +112,14 @@ public static String generateName() {
101112

102113
private final ServerImplBuilder serverImplBuilder;
103114
final String name;
115+
final boolean anonymous;
104116
int maxInboundMetadataSize = Integer.MAX_VALUE;
105117
ObjectPool<ScheduledExecutorService> schedulerPool =
106118
SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
107119

108-
private InProcessServerBuilder(String name) {
120+
private InProcessServerBuilder(String name, boolean anonymous) {
109121
this.name = Preconditions.checkNotNull(name, "name");
122+
this.anonymous = anonymous;
110123

111124
final class InProcessClientTransportServersBuilder implements ClientTransportServersBuilder {
112125
@Override

core/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.google.common.base.Preconditions.checkNotNull;
2020

2121
import java.net.SocketAddress;
22+
import javax.annotation.Nullable;
2223

2324
/**
2425
* Custom SocketAddress class for {@link InProcessTransport}.
@@ -27,13 +28,25 @@ public final class InProcessSocketAddress extends SocketAddress {
2728
private static final long serialVersionUID = -2803441206326023474L;
2829

2930
private final String name;
31+
@Nullable
32+
private final InProcessServer server;
3033

3134
/**
3235
* @param name - The name of the inprocess channel or server.
3336
* @since 1.0.0
3437
*/
3538
public InProcessSocketAddress(String name) {
39+
this(name, null);
40+
}
41+
42+
/**
43+
* @param name - The name of the inprocess channel or server.
44+
* @param server - The concrete {@link InProcessServer} instance, Will be present on the listen
45+
* address of an anonymous server.
46+
*/
47+
InProcessSocketAddress(String name, @Nullable InProcessServer server) {
3648
this.name = checkNotNull(name, "name");
49+
this.server = server;
3750
}
3851

3952
/**
@@ -45,6 +58,11 @@ public String getName() {
4558
return name;
4659
}
4760

61+
@Nullable
62+
InProcessServer getServer() {
63+
return server;
64+
}
65+
4866
/**
4967
* @since 1.14.0
5068
*/
@@ -58,7 +76,13 @@ public String toString() {
5876
*/
5977
@Override
6078
public int hashCode() {
61-
return name.hashCode();
79+
if (server != null) {
80+
// Since there's a single canonical InProcessSocketAddress instance for
81+
// an anonymous inprocess server, we can just use identity equality.
82+
return super.hashCode();
83+
} else {
84+
return name.hashCode();
85+
}
6286
}
6387

6488
/**
@@ -69,6 +93,13 @@ public boolean equals(Object obj) {
6993
if (!(obj instanceof InProcessSocketAddress)) {
7094
return false;
7195
}
72-
return name.equals(((InProcessSocketAddress) obj).name);
96+
InProcessSocketAddress addr = (InProcessSocketAddress) obj;
97+
if (server == null && addr.server == null) {
98+
return name.equals(addr.name);
99+
} else {
100+
// Since there's a single canonical InProcessSocketAddress instance for
101+
// an anonymous inprocess server, we can just use identity equality.
102+
return addr == this;
103+
}
73104
}
74105
}

core/src/main/java/io/grpc/inprocess/InProcessTransport.java

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import io.grpc.internal.StatsTraceContext;
6060
import io.grpc.internal.StreamListener;
6161
import java.io.InputStream;
62+
import java.net.SocketAddress;
6263
import java.util.ArrayDeque;
6364
import java.util.ArrayList;
6465
import java.util.Collections;
@@ -80,7 +81,7 @@ final class InProcessTransport implements ServerTransport, ConnectionClientTrans
8081
private static final Logger log = Logger.getLogger(InProcessTransport.class.getName());
8182

8283
private final InternalLogId logId;
83-
private final String name;
84+
private final SocketAddress address;
8485
private final int clientMaxInboundMetadataSize;
8586
private final String authority;
8687
private final String userAgent;
@@ -119,29 +120,29 @@ protected void handleNotInUse() {
119120
}
120121
};
121122

122-
private InProcessTransport(String name, int maxInboundMetadataSize, String authority,
123+
private InProcessTransport(SocketAddress address, int maxInboundMetadataSize, String authority,
123124
String userAgent, Attributes eagAttrs,
124125
Optional<ServerListener> optionalServerListener, boolean includeCauseWithStatus) {
125-
this.name = name;
126+
this.address = address;
126127
this.clientMaxInboundMetadataSize = maxInboundMetadataSize;
127128
this.authority = authority;
128129
this.userAgent = GrpcUtil.getGrpcUserAgent("inprocess", userAgent);
129130
checkNotNull(eagAttrs, "eagAttrs");
130131
this.attributes = Attributes.newBuilder()
131132
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
132133
.set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs)
133-
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InProcessSocketAddress(name))
134-
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InProcessSocketAddress(name))
134+
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, address)
135+
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, address)
135136
.build();
136137
this.optionalServerListener = optionalServerListener;
137-
logId = InternalLogId.allocate(getClass(), name);
138+
logId = InternalLogId.allocate(getClass(), getServerName(address));
138139
this.includeCauseWithStatus = includeCauseWithStatus;
139140
}
140141

141142
public InProcessTransport(
142-
String name, int maxInboundMetadataSize, String authority, String userAgent,
143+
SocketAddress address, int maxInboundMetadataSize, String authority, String userAgent,
143144
Attributes eagAttrs, boolean includeCauseWithStatus) {
144-
this(name, maxInboundMetadataSize, authority, userAgent, eagAttrs,
145+
this(address, maxInboundMetadataSize, authority, userAgent, eagAttrs,
145146
Optional.<ServerListener>absent(), includeCauseWithStatus);
146147
}
147148

@@ -150,7 +151,7 @@ public InProcessTransport(
150151
Attributes eagAttrs, ObjectPool<ScheduledExecutorService> serverSchedulerPool,
151152
List<ServerStreamTracer.Factory> serverStreamTracerFactories,
152153
ServerListener serverListener) {
153-
this(name, maxInboundMetadataSize, authority, userAgent, eagAttrs,
154+
this(new InProcessSocketAddress(name), maxInboundMetadataSize, authority, userAgent, eagAttrs,
154155
Optional.of(serverListener), false);
155156
this.serverMaxInboundMetadataSize = maxInboundMetadataSize;
156157
this.serverSchedulerPool = serverSchedulerPool;
@@ -165,7 +166,7 @@ public synchronized Runnable start(ManagedClientTransport.Listener listener) {
165166
serverScheduler = serverSchedulerPool.getObject();
166167
serverTransportListener = optionalServerListener.get().transportCreated(this);
167168
} else {
168-
InProcessServer server = InProcessServer.findServer(name);
169+
InProcessServer server = InProcessServer.findServer(address);
169170
if (server != null) {
170171
serverMaxInboundMetadataSize = server.getMaxInboundMetadataSize();
171172
serverSchedulerPool = server.getScheduledExecutorServicePool();
@@ -176,7 +177,8 @@ public synchronized Runnable start(ManagedClientTransport.Listener listener) {
176177
}
177178
}
178179
if (serverTransportListener == null) {
179-
shutdownStatus = Status.UNAVAILABLE.withDescription("Could not find server: " + name);
180+
shutdownStatus =
181+
Status.UNAVAILABLE.withDescription("Could not find server: " + getServerName(address));
180182
final Status localShutdownStatus = shutdownStatus;
181183
return new Runnable() {
182184
@Override
@@ -194,8 +196,8 @@ public void run() {
194196
public void run() {
195197
synchronized (InProcessTransport.this) {
196198
Attributes serverTransportAttrs = Attributes.newBuilder()
197-
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InProcessSocketAddress(name))
198-
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InProcessSocketAddress(name))
199+
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, address)
200+
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, address)
199201
.build();
200202
serverStreamAttributes = serverTransportListener.transportReady(serverTransportAttrs);
201203
clientTransportListener.transportReady();
@@ -307,7 +309,7 @@ public void shutdownNow(Status reason) {
307309
public String toString() {
308310
return MoreObjects.toStringHelper(this)
309311
.add("logId", logId.getId())
310-
.add("name", name)
312+
.add("name", getServerName(address))
311313
.toString();
312314
}
313315

@@ -882,6 +884,14 @@ private static Status cleanStatus(Status status, boolean includeCauseWithStatus)
882884
return clientStatus;
883885
}
884886

887+
private static String getServerName(SocketAddress addr) {
888+
if (addr instanceof InProcessSocketAddress) {
889+
return ((InProcessSocketAddress) addr).getName();
890+
} else {
891+
return "Bad Server Address: " + addr;
892+
}
893+
}
894+
885895
private static class SingleMessageProducer implements StreamListener.MessageProducer {
886896
private InputStream message;
887897

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy