Skip to content

Commit 15ad9f5

Browse files
authored
binder: Simplify ownership of ServerAuthInterceptor's executor. (#11293)
Allocating this executor before BinderServer even exists is convoluted and actually leaks if the built server is never actually start()ed. Instead, have BinderServer own this executor directly, with a lifetime from start() until termination. Pass it to the ServerAuthInterceptor via TransportAuthorizationState Attribute instead of at construction time.
1 parent c540993 commit 15ad9f5

File tree

4 files changed

+86
-74
lines changed

4 files changed

+86
-74
lines changed

binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,12 @@
1717
package io.grpc.binder.internal;
1818

1919
import android.content.Context;
20-
import androidx.core.content.ContextCompat;
2120
import androidx.test.core.app.ApplicationProvider;
2221
import androidx.test.ext.junit.runners.AndroidJUnit4;
2322
import io.grpc.ServerStreamTracer;
2423
import io.grpc.binder.AndroidComponentAddress;
25-
import io.grpc.binder.BindServiceFlags;
26-
import io.grpc.binder.BinderChannelCredentials;
2724
import io.grpc.binder.HostServices;
28-
import io.grpc.binder.InboundParcelablePolicy;
29-
import io.grpc.binder.SecurityPolicies;
3025
import io.grpc.internal.AbstractTransportTest;
31-
import io.grpc.internal.ClientTransportFactory;
3226
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
3327
import io.grpc.internal.GrpcUtil;
3428
import io.grpc.internal.InternalServer;
@@ -57,6 +51,8 @@ public final class BinderTransportTest extends AbstractTransportTest {
5751
SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
5852
private final ObjectPool<Executor> offloadExecutorPool =
5953
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
54+
private final ObjectPool<Executor> serverExecutorPool =
55+
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
6056

6157
@Override
6258
@After
@@ -69,11 +65,13 @@ public void tearDown() throws InterruptedException {
6965
protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
7066
AndroidComponentAddress addr = HostServices.allocateService(appContext);
7167

72-
BinderServer binderServer = new BinderServer.Builder()
73-
.setListenAddress(addr)
74-
.setExecutorServicePool(executorServicePool)
75-
.setStreamTracerFactories(streamTracerFactories)
76-
.build();
68+
BinderServer binderServer =
69+
new BinderServer.Builder()
70+
.setListenAddress(addr)
71+
.setExecutorPool(serverExecutorPool)
72+
.setExecutorServicePool(executorServicePool)
73+
.setStreamTracerFactories(streamTracerFactories)
74+
.build();
7775

7876
HostServices.configureService(addr,
7977
HostServices.serviceParamsBuilder()

binder/src/main/java/io/grpc/binder/BinderServerBuilder.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@
3030
import io.grpc.binder.internal.BinderTransportSecurity;
3131
import io.grpc.internal.FixedObjectPool;
3232
import io.grpc.internal.ServerImplBuilder;
33-
import io.grpc.internal.ObjectPool;
3433

3534
import java.io.File;
36-
import java.util.concurrent.Executor;
3735
import java.util.concurrent.ScheduledExecutorService;
3836

3937
/**
@@ -163,10 +161,8 @@ public Server build() {
163161
checkState(!isBuilt, "BinderServerBuilder can only be used to build one server instance.");
164162
isBuilt = true;
165163
// We install the security interceptor last, so it's closest to the transport.
166-
ObjectPool<? extends Executor> executorPool = serverImplBuilder.getExecutorPool();
167-
Executor executor = executorPool.getObject();
168-
BinderTransportSecurity.installAuthInterceptor(this, executor);
169-
internalBuilder.setTerminationListener(() -> executorPool.returnObject(executor));
164+
BinderTransportSecurity.installAuthInterceptor(this);
165+
internalBuilder.setExecutorPool(serverImplBuilder.getExecutorPool());
170166
return super.build();
171167
}
172168
}

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

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.io.IOException;
4444
import java.net.SocketAddress;
4545
import java.util.List;
46+
import java.util.concurrent.Executor;
4647
import java.util.concurrent.ScheduledExecutorService;
4748
import java.util.logging.Level;
4849
import java.util.logging.Logger;
@@ -63,30 +64,34 @@ public final class BinderServer implements InternalServer, LeakSafeOneWayBinder.
6364
private static final Logger logger = Logger.getLogger(BinderServer.class.getName());
6465

6566
private final ObjectPool<ScheduledExecutorService> executorServicePool;
67+
private final ObjectPool<? extends Executor> executorPool;
6668
private final ImmutableList<ServerStreamTracer.Factory> streamTracerFactories;
6769
private final AndroidComponentAddress listenAddress;
6870
private final LeakSafeOneWayBinder hostServiceBinder;
6971
private final BinderTransportSecurity.ServerPolicyChecker serverPolicyChecker;
7072
private final InboundParcelablePolicy inboundParcelablePolicy;
71-
private final Runnable terminationListener;
7273

7374
@GuardedBy("this")
7475
private ServerListener listener;
7576

7677
@GuardedBy("this")
7778
private ScheduledExecutorService executorService;
7879

80+
@Nullable // Before start() and after termination.
81+
@GuardedBy("this")
82+
private Executor executor;
83+
7984
@GuardedBy("this")
8085
private boolean shutdown;
8186

8287
private BinderServer(Builder builder) {
8388
this.listenAddress = checkNotNull(builder.listenAddress);
89+
this.executorPool = checkNotNull(builder.executorPool);
8490
this.executorServicePool = builder.executorServicePool;
8591
this.streamTracerFactories =
8692
ImmutableList.copyOf(checkNotNull(builder.streamTracerFactories, "streamTracerFactories"));
8793
this.serverPolicyChecker = BinderInternal.createPolicyChecker(builder.serverSecurityPolicy);
8894
this.inboundParcelablePolicy = builder.inboundParcelablePolicy;
89-
this.terminationListener = builder.terminationListener;
9095
hostServiceBinder = new LeakSafeOneWayBinder(this);
9196
}
9297

@@ -97,8 +102,9 @@ public IBinder getHostBinder() {
97102

98103
@Override
99104
public synchronized void start(ServerListener serverListener) throws IOException {
100-
listener = new ActiveTransportTracker(serverListener, terminationListener);
105+
listener = new ActiveTransportTracker(serverListener, this::onTerminated);
101106
executorService = executorServicePool.getObject();
107+
executor = executorPool.getObject();
102108
}
103109

104110
@Override
@@ -129,10 +135,15 @@ public synchronized void shutdown() {
129135
// Break the connection to the binder. We'll receive no more transactions.
130136
hostServiceBinder.setHandler(GoAwayHandler.INSTANCE);
131137
listener.serverShutdown();
138+
// TODO(jdcormie): Shouldn't this happen in onTerminated()? Is this even used anywhere?
132139
executorService = executorServicePool.returnObject(executorService);
133140
}
134141
}
135142

143+
private synchronized void onTerminated() {
144+
executor = executorPool.returnObject(executor);
145+
}
146+
136147
@Override
137148
public String toString() {
138149
return "BinderServer[" + listenAddress + "]";
@@ -161,7 +172,11 @@ public synchronized boolean handleTransaction(int code, Parcel parcel) {
161172
.set(BinderTransport.REMOTE_UID, callingUid)
162173
.set(BinderTransport.SERVER_AUTHORITY, listenAddress.getAuthority())
163174
.set(BinderTransport.INBOUND_PARCELABLE_POLICY, inboundParcelablePolicy);
164-
BinderTransportSecurity.attachAuthAttrs(attrsBuilder, callingUid, serverPolicyChecker);
175+
BinderTransportSecurity.attachAuthAttrs(
176+
attrsBuilder,
177+
callingUid,
178+
serverPolicyChecker,
179+
checkNotNull(executor, "Not started?"));
165180
// Create a new transport and let our listener know about it.
166181
BinderTransport.BinderServerTransport transport =
167182
new BinderTransport.BinderServerTransport(
@@ -202,12 +217,12 @@ public boolean handleTransaction(int code, Parcel parcel) {
202217
public static class Builder {
203218
@Nullable AndroidComponentAddress listenAddress;
204219
@Nullable List<? extends ServerStreamTracer.Factory> streamTracerFactories;
220+
@Nullable ObjectPool<? extends Executor> executorPool;
205221

206222
ObjectPool<ScheduledExecutorService> executorServicePool =
207223
SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
208224
ServerSecurityPolicy serverSecurityPolicy = SecurityPolicies.serverInternalOnly();
209225
InboundParcelablePolicy inboundParcelablePolicy = InboundParcelablePolicy.DEFAULT;
210-
Runnable terminationListener = () -> {};
211226

212227
public BinderServer build() {
213228
return new BinderServer(this);
@@ -236,6 +251,16 @@ public Builder setStreamTracerFactories(List<? extends ServerStreamTracer.Factor
236251
return this;
237252
}
238253

254+
/**
255+
* Sets the executor to be used for calling into the application.
256+
*
257+
* <p>Required.
258+
*/
259+
public Builder setExecutorPool(ObjectPool<? extends Executor> executorPool) {
260+
this.executorPool = executorPool;
261+
return this;
262+
}
263+
239264
/**
240265
* Sets the executor to be used for scheduling channel timers.
241266
*
@@ -266,16 +291,5 @@ public Builder setInboundParcelablePolicy(InboundParcelablePolicy inboundParcela
266291
this.inboundParcelablePolicy = checkNotNull(inboundParcelablePolicy, "inboundParcelablePolicy");
267292
return this;
268293
}
269-
270-
/**
271-
* Installs a callback that will be invoked when this server is {@link #shutdown()} and all of
272-
* its transports are terminated.
273-
*
274-
* <p>Optional.
275-
*/
276-
public Builder setTerminationListener(Runnable terminationListener) {
277-
this.terminationListener = checkNotNull(terminationListener, "terminationListener");
278-
return this;
279-
}
280294
}
281295
}

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

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import com.google.common.util.concurrent.Futures;
2121
import com.google.common.util.concurrent.ListenableFuture;
2222
import com.google.common.util.concurrent.MoreExecutors;
23-
2423
import io.grpc.Attributes;
2524
import io.grpc.Internal;
2625
import io.grpc.Metadata;
@@ -32,7 +31,6 @@
3231
import io.grpc.ServerInterceptor;
3332
import io.grpc.Status;
3433
import io.grpc.internal.GrpcAttributes;
35-
3634
import java.util.concurrent.CancellationException;
3735
import java.util.concurrent.ConcurrentHashMap;
3836
import java.util.concurrent.ExecutionException;
@@ -57,11 +55,10 @@ private BinderTransportSecurity() {}
5755
* Install a security policy on an about-to-be created server.
5856
*
5957
* @param serverBuilder The ServerBuilder being used to create the server.
60-
* @param executor The executor in which the authorization result will be handled.
6158
*/
6259
@Internal
63-
public static void installAuthInterceptor(ServerBuilder<?> serverBuilder, Executor executor) {
64-
serverBuilder.intercept(new ServerAuthInterceptor(executor));
60+
public static void installAuthInterceptor(ServerBuilder<?> serverBuilder) {
61+
serverBuilder.intercept(new ServerAuthInterceptor());
6562
}
6663

6764
/**
@@ -71,14 +68,18 @@ public static void installAuthInterceptor(ServerBuilder<?> serverBuilder, Execut
7168
* @param builder The {@link Attributes.Builder} for the transport being created.
7269
* @param remoteUid The remote UID of the transport.
7370
* @param serverPolicyChecker The policy checker for this transport.
71+
* @param executor used for calling into the application. Must outlive the transport.
7472
*/
7573
@Internal
7674
public static void attachAuthAttrs(
77-
Attributes.Builder builder, int remoteUid, ServerPolicyChecker serverPolicyChecker) {
75+
Attributes.Builder builder,
76+
int remoteUid,
77+
ServerPolicyChecker serverPolicyChecker,
78+
Executor executor) {
7879
builder
7980
.set(
8081
TRANSPORT_AUTHORIZATION_STATE,
81-
new TransportAuthorizationState(remoteUid, serverPolicyChecker))
82+
new TransportAuthorizationState(remoteUid, serverPolicyChecker, executor))
8283
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY);
8384
}
8485

@@ -88,25 +89,20 @@ public static void attachAuthAttrs(
8889
*/
8990
private static final class ServerAuthInterceptor implements ServerInterceptor {
9091

91-
private final Executor executor;
92-
93-
ServerAuthInterceptor(Executor executor) {
94-
this.executor = executor;
95-
}
96-
9792
@Override
9893
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
9994
ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
95+
TransportAuthorizationState transportAuthState =
96+
call.getAttributes().get(TRANSPORT_AUTHORIZATION_STATE);
10097
ListenableFuture<Status> authStatusFuture =
101-
call.getAttributes()
102-
.get(TRANSPORT_AUTHORIZATION_STATE)
103-
.checkAuthorization(call.getMethodDescriptor());
98+
transportAuthState.checkAuthorization(call.getMethodDescriptor());
10499

105100
// Most SecurityPolicy will have synchronous implementations that provide an
106101
// immediately-resolved Future. In that case, short-circuit to avoid unnecessary allocations
107102
// and asynchronous code if the authorization result is already present.
108103
if (!authStatusFuture.isDone()) {
109-
return newServerCallListenerForPendingAuthResult(authStatusFuture, call, headers, next);
104+
return newServerCallListenerForPendingAuthResult(
105+
authStatusFuture, transportAuthState.executor, call, headers, next);
110106
}
111107

112108
Status authStatus;
@@ -130,31 +126,33 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
130126
}
131127

132128
private <ReqT, RespT> ServerCall.Listener<ReqT> newServerCallListenerForPendingAuthResult(
133-
ListenableFuture<Status> authStatusFuture,
134-
ServerCall<ReqT, RespT> call,
135-
Metadata headers,
136-
ServerCallHandler<ReqT, RespT> next) {
129+
ListenableFuture<Status> authStatusFuture,
130+
Executor executor,
131+
ServerCall<ReqT, RespT> call,
132+
Metadata headers,
133+
ServerCallHandler<ReqT, RespT> next) {
137134
PendingAuthListener<ReqT, RespT> listener = new PendingAuthListener<>();
138135
Futures.addCallback(
139-
authStatusFuture,
140-
new FutureCallback<Status>() {
141-
@Override
142-
public void onSuccess(Status authStatus) {
143-
if (!authStatus.isOk()) {
144-
call.close(authStatus, new Metadata());
145-
return;
146-
}
147-
148-
listener.startCall(call, headers, next);
149-
}
150-
151-
@Override
152-
public void onFailure(Throwable t) {
153-
call.close(
154-
Status.INTERNAL.withCause(t).withDescription("Authorization future failed"),
155-
new Metadata());
156-
}
157-
}, executor);
136+
authStatusFuture,
137+
new FutureCallback<Status>() {
138+
@Override
139+
public void onSuccess(Status authStatus) {
140+
if (!authStatus.isOk()) {
141+
call.close(authStatus, new Metadata());
142+
return;
143+
}
144+
145+
listener.startCall(call, headers, next);
146+
}
147+
148+
@Override
149+
public void onFailure(Throwable t) {
150+
call.close(
151+
Status.INTERNAL.withCause(t).withDescription("Authorization future failed"),
152+
new Metadata());
153+
}
154+
},
155+
executor);
158156
return listener;
159157
}
160158
}
@@ -167,10 +165,16 @@ private static final class TransportAuthorizationState {
167165
private final int uid;
168166
private final ServerPolicyChecker serverPolicyChecker;
169167
private final ConcurrentHashMap<String, ListenableFuture<Status>> serviceAuthorization;
168+
private final Executor executor;
170169

171-
TransportAuthorizationState(int uid, ServerPolicyChecker serverPolicyChecker) {
170+
/**
171+
* @param executor used for calling into the application. Must outlive the transport.
172+
*/
173+
TransportAuthorizationState(
174+
int uid, ServerPolicyChecker serverPolicyChecker, Executor executor) {
172175
this.uid = uid;
173176
this.serverPolicyChecker = serverPolicyChecker;
177+
this.executor = executor;
174178
serviceAuthorization = new ConcurrentHashMap<>(8);
175179
}
176180

0 commit comments

Comments
 (0)