@@ -87,6 +87,24 @@ public static void attachAuthAttrs(
8787 .set (GrpcAttributes .ATTR_SECURITY_LEVEL , SecurityLevel .PRIVACY_AND_INTEGRITY );
8888 }
8989
90+ /**
91+ * Informs this module that the transport with the specified 'attributes' is terminating.
92+ *
93+ * <p>Any resources allocated by this module will be released and no further resources will be
94+ * allocated. Any ongoing background work will be canceled and no further background work will be
95+ * initiated.
96+ *
97+ * <p>This method completes futures and therefore may execute arbitrary listener code on a
98+ * potentially direct Executor. To avoid deadlock, callers must not hold any locks.
99+ */
100+ @ Internal
101+ public static void notifyTerminatedUnlocked (Attributes attributes ) {
102+ TransportAuthorizationState state = attributes .get (TRANSPORT_AUTHORIZATION_STATE );
103+ if (state != null ) {
104+ state .notifyTerminatedUnlocked ();
105+ }
106+ }
107+
90108 /**
91109 * Intercepts server calls and ensures they're authorized before allowing them to proceed.
92110 * Authentication state is fetched from the call attributes, inherited from the transport.
@@ -173,6 +191,8 @@ static final class TransportAuthorizationState {
173191 private final ConcurrentHashMap <String , ListenableFuture <Status >> serviceAuthorization ;
174192 private final Executor executor ;
175193
194+ private volatile boolean isTerminated ;
195+
176196 /**
177197 * @param executor used for calling into the application. Must outlive the transport.
178198 */
@@ -203,11 +223,17 @@ ListenableFuture<Status> checkAuthorization(MethodDescriptor<?, ?> method) {
203223 return nonCancellationPropagating (checkThenActRaceWinner );
204224 }
205225
206- // newPendingAuthResult is visible to other threads at this point but not as a SettableFuture
207- // and always wrapped with nonCancellationPropagating(). Since it must not be complete, we can
208- // ignore the result of setFuture() -- it must always succeed.
209- newPendingAuthResult .setFuture (
210- serverPolicyChecker .checkAuthorizationForServiceAsync (uid , serviceName ));
226+ // We only check isTerminated *after* the new future is visible to other threads in
227+ // serviceAuthorization. In case of a race with a simultaneous call to notifyTerminated(),
228+ // better to harmlessly cancel the new future twice rather than not cancel it at all.
229+ if (!isTerminated ) {
230+ // If notifyTerminated() already cancelled newPendingAuthResult, setFuture() will forward
231+ // that cancellation to its argument so it's safe to ignore the return value here.
232+ newPendingAuthResult .setFuture (
233+ serverPolicyChecker .checkAuthorizationForServiceAsync (uid , serviceName ));
234+ } else {
235+ newPendingAuthResult .cancel (false );
236+ }
211237
212238 Futures .addCallback (
213239 newPendingAuthResult ,
@@ -233,6 +259,22 @@ public void onFailure(Throwable t) {
233259
234260 return nonCancellationPropagating (newPendingAuthResult );
235261 }
262+
263+ /**
264+ * After this method returns, every future ever returned by a prior or concurrent call to
265+ * checkAuthorization() is guaranteed to be complete. Every future returned by subsequent call
266+ * to checkAuthorization() is also guaranteed to be complete.
267+ */
268+ void notifyTerminatedUnlocked () {
269+ // Any entries added to serviceAuthorization *after* this assignment will be immediately
270+ // canceled by the adding thread in checkAuthorization().
271+ isTerminated = true ;
272+
273+ // Cancel any entries added to serviceAuthorization *before* the volatile assignment above.
274+ for (ListenableFuture <Status > authResult : serviceAuthorization .values ()) {
275+ authResult .cancel (false ); // No-op for cached results (already done).
276+ }
277+ }
236278 }
237279
238280 /**
0 commit comments