Skip to content

Commit 1c3a0ea

Browse files
committed
binder: Use register() pattern to cancel futures safely without holding locks
Signed-off-by: Hyunsang Han <[email protected]>
1 parent fc77da4 commit 1c3a0ea

File tree

2 files changed

+23
-22
lines changed

2 files changed

+23
-22
lines changed

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

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,6 @@ public final class BinderClientTransport extends BinderTransport
8787
@GuardedBy("this")
8888
private ScheduledFuture<?> readyTimeoutFuture; // != null iff timeout scheduled.
8989

90-
@GuardedBy("this")
91-
@Nullable
92-
private ListenableFuture<Status> authResultFuture; // null before we check auth.
93-
94-
@GuardedBy("this")
95-
@Nullable
96-
private ListenableFuture<Status> preAuthResultFuture; // null before we pre-auth.
9790

9891
/**
9992
* Constructs a new transport instance.
@@ -195,7 +188,8 @@ private void preAuthorize(ServiceInfo serviceInfo) {
195188
// unauthorized server a chance to run, but the connection will still fail by SecurityPolicy
196189
// check later in handshake. Pre-auth remains effective at mitigating abuse because malware
197190
// can't typically control the exact timing of its installation.
198-
preAuthResultFuture = checkServerAuthorizationAsync(serviceInfo.applicationInfo.uid);
191+
ListenableFuture<Status> preAuthResultFuture =
192+
register(checkServerAuthorizationAsync(serviceInfo.applicationInfo.uid));
199193
Futures.addCallback(
200194
preAuthResultFuture,
201195
new FutureCallback<Status>() {
@@ -316,9 +310,6 @@ void notifyTerminated() {
316310
readyTimeoutFuture.cancel(false);
317311
readyTimeoutFuture = null;
318312
}
319-
cancelAsyncIfNeeded(preAuthResultFuture);
320-
cancelAsyncIfNeeded(authResultFuture);
321-
322313
serviceBinding.unbind();
323314
clientTransportListener.transportTerminated();
324315
}
@@ -337,7 +328,8 @@ protected void handleSetupTransport(Parcel parcel) {
337328
shutdownInternal(
338329
Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true);
339330
} else {
340-
authResultFuture = checkServerAuthorizationAsync(remoteUid);
331+
ListenableFuture<Status> authResultFuture =
332+
register(checkServerAuthorizationAsync(remoteUid));
341333
Futures.addCallback(
342334
authResultFuture,
343335
new FutureCallback<Status>() {
@@ -396,16 +388,6 @@ protected void handlePingResponse(Parcel parcel) {
396388
pingTracker.onPingResponse(parcel.readInt());
397389
}
398390

399-
/**
400-
* Enqueues a future for cancellation later, on another thread.
401-
* Useful when the caller wants to cancel while holding locks but the future is visible to
402-
* user code which might have added listeners to run on directExecutor().
403-
*/
404-
private void cancelAsyncIfNeeded(@Nullable ListenableFuture<?> future) {
405-
if (future != null && !future.isDone()) {
406-
offloadExecutor.execute(() -> future.cancel(false));
407-
}
408-
}
409391

410392
private static ClientStream newFailingClientStream(
411393
Status failure, Attributes attributes, Metadata headers, ClientStreamTracer[] tracers) {

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
import java.util.ArrayList;
4242
import java.util.Iterator;
4343
import java.util.LinkedHashSet;
44+
import java.util.List;
4445
import java.util.Map;
4546
import java.util.NoSuchElementException;
4647
import java.util.concurrent.ConcurrentHashMap;
48+
import java.util.concurrent.Future;
4749
import java.util.concurrent.ScheduledExecutorService;
4850
import java.util.logging.Level;
4951
import java.util.logging.Logger;
@@ -161,6 +163,9 @@ protected enum TransportState {
161163
@GuardedBy("this")
162164
private final LinkedHashSet<Integer> callIdsToNotifyWhenReady = new LinkedHashSet<>();
163165

166+
@GuardedBy("this")
167+
private final List<Future<?>> ownedFutures = new ArrayList<>(); // To cancel upon terminate.
168+
164169
@GuardedBy("this")
165170
protected Attributes attributes;
166171

@@ -236,6 +241,13 @@ void releaseExecutors() {
236241
executorServicePool.returnObject(scheduledExecutorService);
237242
}
238243

244+
// Registers the specified future for eventual safe cancellation upon shutdown/terminate.
245+
@GuardedBy("this")
246+
protected final <T extends Future<?>> T register(T future) {
247+
ownedFutures.add(future);
248+
return future;
249+
}
250+
239251
@GuardedBy("this")
240252
boolean inState(TransportState transportState) {
241253
return this.transportState == transportState;
@@ -286,6 +298,8 @@ final void shutdownInternal(Status shutdownStatus, boolean forceTerminate) {
286298
sendShutdownTransaction();
287299
ArrayList<Inbound<?>> calls = new ArrayList<>(ongoingCalls.values());
288300
ongoingCalls.clear();
301+
ArrayList<Future<?>> futuresToCancel = new ArrayList<>(ownedFutures);
302+
ownedFutures.clear();
289303
scheduledExecutorService.execute(
290304
() -> {
291305
for (Inbound<?> inbound : calls) {
@@ -297,6 +311,11 @@ final void shutdownInternal(Status shutdownStatus, boolean forceTerminate) {
297311
notifyTerminated();
298312
}
299313
releaseExecutors();
314+
315+
for (Future<?> future : futuresToCancel) {
316+
// Not holding any locks here just in case some listener runs on a direct Executor.
317+
future.cancel(false); // No effect if already isDone().
318+
}
300319
});
301320
}
302321
}

0 commit comments

Comments
 (0)