Skip to content

Commit e163513

Browse files
committed
binder: Let servers load their SecurityPolicy asynchronously
Android IPC servers can't defer "listening" while some slow or async initialization process completes. Instead, Android *tells* a server to initialize itself just-in-time for the first client connection. And this instruction arrives as a callback to Service#onCreate() then Service#onBind() on the app's main thread, where blocking to load a security policy would risk an "Application Not Responding" (ANR) error. Introduce AsyncSecurityPolicies#deferredAsync which defers policy creation until it's actually needed by a Channel or Server. TAG=agy CONV=16317da3-5abd-46b0-bd97-850ecde2727e
1 parent ac02c6f commit e163513

2 files changed

Lines changed: 441 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2026 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.binder;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import com.google.common.base.Preconditions;
22+
import com.google.common.util.concurrent.AsyncCallable;
23+
import com.google.common.util.concurrent.Futures;
24+
import com.google.common.util.concurrent.ListenableFuture;
25+
import com.google.errorprone.annotations.CheckReturnValue;
26+
import io.grpc.ExperimentalApi;
27+
import io.grpc.Status;
28+
import java.util.concurrent.Executor;
29+
30+
/** Static factory methods for creating asynchronous security policies. */
31+
@CheckReturnValue
32+
public final class AsyncSecurityPolicies {
33+
34+
private AsyncSecurityPolicies() {}
35+
36+
/**
37+
* Returns an {@link AsyncSecurityPolicy} that delegates to some other policy that's provided
38+
* lazily and asynchronously.
39+
*
40+
* <p>Use this when your security policy is slow or expensive to load and not immediately
41+
* available at Channel or Server initialization. It's particularly useful in {@code
42+
* android.app.Service#onCreate()} where blocking the main thread to load a server's security
43+
* policy risks an "Application Not Responding" (ANR) error. Implementations of 'policyProvider'
44+
* must not block the calling thread either.
45+
*
46+
* <p>The provided {@link AsyncCallable} is invoked each time the returned policy is evaluated.
47+
* This happens once per connection for a grpc-binder Channel and once per (service, incoming
48+
* connection) called on a Server. So 'policyProvider' must be prepared to be invoked more than
49+
* once but not normally for every RPC. Depending on the cost of loading the policy, a provider
50+
* may want to memoize and reuse its products.
51+
*
52+
* <p>Binder Channels and Servers try to coalesce multiple identical authorization checks that
53+
* overlap in time but this isn't guaranteed. So 'policyProvider' must be thread-safe but may or
54+
* may not go to the trouble of coalescing simultaneous calls for itself. Those that do should use
55+
* {@link Futures#nonCancellationPropagating} or similar to protect a future returned to multiple
56+
* callers from individual cancellation.
57+
*
58+
* <p>'policyProvider' can express the failure to load a security policy by returning a failed
59+
* future. This failure will propagate to the Server or Channel operation that needed authorizing,
60+
* but will not be cached, leaving open the possibility of success upon retry. A memoizing policy
61+
* provider may want to retain only successes for the same reason.
62+
*
63+
* @param policyProvider used to get the delegate SecurityPolicy when needed
64+
* @param executor used to call into the delegate once provided. If the delegate is a
65+
* SecurityPolicy, note that many implementations of checkAuthorization() block.
66+
*/
67+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022")
68+
public static <T extends SecurityPolicy> AsyncSecurityPolicy deferredAsync(
69+
AsyncCallable<T> policyProvider, Executor executor) {
70+
checkNotNull(policyProvider, "policyProvider");
71+
checkNotNull(executor, "executor");
72+
return new AsyncSecurityPolicy() {
73+
@Override
74+
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
75+
try {
76+
return Futures.transformAsync(
77+
policyProvider.call(),
78+
policy -> {
79+
checkNotNull(policy, "policyProvider returned a null SecurityPolicy");
80+
if (policy instanceof AsyncSecurityPolicy) {
81+
return ((AsyncSecurityPolicy) policy).checkAuthorizationAsync(uid);
82+
}
83+
// This may block but 'executor' must be prepared for that.
84+
return Futures.immediateFuture(policy.checkAuthorization(uid));
85+
},
86+
executor);
87+
} catch (Exception e) {
88+
return Futures.immediateFailedFuture(e);
89+
}
90+
}
91+
};
92+
}
93+
}

0 commit comments

Comments
 (0)