• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 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.internal;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 import io.grpc.Attributes;
21 import io.grpc.NameResolver;
22 import io.grpc.Status;
23 import io.grpc.SynchronizationContext;
24 
25 /**
26  * This wrapper class can add retry capability to any polling {@link NameResolver} implementation
27  * that supports calling {@link ResolutionResultListener}s with the outcome of each resolution.
28  *
29  * <p>The {@link NameResolver} used with this
30  */
31 final class RetryingNameResolver extends ForwardingNameResolver {
32 
33   private final NameResolver retriedNameResolver;
34   private final RetryScheduler retryScheduler;
35   private final SynchronizationContext syncContext;
36 
37   static final Attributes.Key<ResolutionResultListener> RESOLUTION_RESULT_LISTENER_KEY
38       = Attributes.Key.create(
39           "io.grpc.internal.RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY");
40 
41   /**
42    * Creates a new {@link RetryingNameResolver}.
43    *
44    * @param retriedNameResolver A {@link NameResolver} that will have failed attempt retried.
45    * @param retryScheduler Used to schedule the retry attempts.
46    */
RetryingNameResolver(NameResolver retriedNameResolver, RetryScheduler retryScheduler, SynchronizationContext syncContext)47   RetryingNameResolver(NameResolver retriedNameResolver, RetryScheduler retryScheduler,
48       SynchronizationContext syncContext) {
49     super(retriedNameResolver);
50     this.retriedNameResolver = retriedNameResolver;
51     this.retryScheduler = retryScheduler;
52     this.syncContext = syncContext;
53   }
54 
55   @Override
start(Listener2 listener)56   public void start(Listener2 listener) {
57     super.start(new RetryingListener(listener));
58   }
59 
60   @Override
shutdown()61   public void shutdown() {
62     super.shutdown();
63     retryScheduler.reset();
64   }
65 
66   /**
67    * Used to get the underlying {@link NameResolver} that is getting its failed attempts retried.
68    */
69   @VisibleForTesting
getRetriedNameResolver()70   NameResolver getRetriedNameResolver() {
71     return retriedNameResolver;
72   }
73 
74   @VisibleForTesting
75   class DelayedNameResolverRefresh implements Runnable {
76     @Override
run()77     public void run() {
78       refresh();
79     }
80   }
81 
82   private class RetryingListener extends Listener2 {
83     private Listener2 delegateListener;
84 
RetryingListener(Listener2 delegateListener)85     RetryingListener(Listener2 delegateListener) {
86       this.delegateListener = delegateListener;
87     }
88 
89     @Override
onResult(ResolutionResult resolutionResult)90     public void onResult(ResolutionResult resolutionResult) {
91       // If the resolution result listener is already an attribute it indicates that a name resolver
92       // has already been wrapped with this class. This indicates a misconfiguration.
93       if (resolutionResult.getAttributes().get(RESOLUTION_RESULT_LISTENER_KEY) != null) {
94         throw new IllegalStateException(
95             "RetryingNameResolver can only be used once to wrap a NameResolver");
96       }
97 
98       delegateListener.onResult(resolutionResult.toBuilder().setAttributes(
99               resolutionResult.getAttributes().toBuilder()
100                   .set(RESOLUTION_RESULT_LISTENER_KEY, new ResolutionResultListener()).build())
101           .build());
102     }
103 
104     @Override
onError(Status error)105     public void onError(Status error) {
106       delegateListener.onError(error);
107       syncContext.execute(() -> retryScheduler.schedule(new DelayedNameResolverRefresh()));
108     }
109   }
110 
111   /**
112    * Simple callback class to store in {@link ResolutionResult} attributes so that
113    * ManagedChannel can indicate if the resolved addresses were accepted. Temporary until
114    * the Listener2.onResult() API can be changed to return a boolean for this purpose.
115    */
116   class ResolutionResultListener {
resolutionAttempted(boolean successful)117     public void resolutionAttempted(boolean successful) {
118       if (successful) {
119         retryScheduler.reset();
120       } else {
121         retryScheduler.schedule(new DelayedNameResolverRefresh());
122       }
123     }
124   }
125 }
126