@@ -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.
@@ -174,6 +192,8 @@ static final class TransportAuthorizationState {
174192 private final ConcurrentHashMap <String , ListenableFuture <Status >> serviceAuthorization ;
175193 private final Executor executor ;
176194
195+ private volatile boolean isTerminated ;
196+
177197 /**
178198 * @param executor used for calling into the application. Must outlive the transport.
179199 */
@@ -204,11 +224,20 @@ ListenableFuture<Status> checkAuthorization(MethodDescriptor<?, ?> method) {
204224 return nonCancellationPropagating (checkThenActRaceWinner );
205225 }
206226
207- try {
208- newPendingAuthResult .setFuture (
209- serverPolicyChecker .checkAuthorizationForServiceAsync (uid , serviceName ));
210- } catch (Exception e ) { // Not just RuntimeException! Handle the "sneaky" checked case too.
211- newPendingAuthResult .setException (e );
227+ // We only check isTerminated *after* the new future is visible to other threads in
228+ // serviceAuthorization. In case of a race with a simultaneous call to notifyTerminated(),
229+ // better to harmlessly cancel the new future twice rather than not cancel it at all.
230+ if (!isTerminated ) {
231+ // If notifyTerminated() already cancelled newPendingAuthResult, setFuture() will forward
232+ // that cancellation to its argument so it's safe to ignore the return value here.
233+ try {
234+ newPendingAuthResult .setFuture (
235+ serverPolicyChecker .checkAuthorizationForServiceAsync (uid , serviceName ));
236+ } catch (Exception e ) { // Not just RuntimeException! Handle the "sneaky" checked case too.
237+ newPendingAuthResult .setException (e );
238+ }
239+ } else {
240+ newPendingAuthResult .cancel (false );
212241 }
213242
214243 Futures .addCallback (
@@ -236,6 +265,21 @@ public void onFailure(Throwable t) {
236265 return nonCancellationPropagating (newPendingAuthResult );
237266 }
238267
268+ /**
269+ * After this method returns, every future ever returned by a prior or concurrent call to
270+ * checkAuthorization() is guaranteed to be complete. Every future returned by subsequent call
271+ * to checkAuthorization() is also guaranteed to be complete.
272+ */
273+ void notifyTerminatedUnlocked () {
274+ // Any entries added to serviceAuthorization *after* this assignment will be immediately
275+ // canceled by the adding thread in checkAuthorization().
276+ isTerminated = true ;
277+
278+ // Cancel any entries added to serviceAuthorization *before* the volatile assignment above.
279+ for (ListenableFuture <Status > authResult : serviceAuthorization .values ()) {
280+ authResult .cancel (false ); // No-op for cached results (already done).
281+ }
282+ }
239283 }
240284
241285 /**
0 commit comments