• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 
22 import com.google.common.annotations.VisibleForTesting;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.LinkedHashMap;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31 import javax.annotation.Nullable;
32 import javax.annotation.concurrent.ThreadSafe;
33 
34 /**
35  * Registry of {@link LoadBalancerProvider}s.  The {@link #getDefaultRegistry default instance}
36  * loads providers at runtime through the Java service provider mechanism.
37  *
38  * @since 1.17.0
39  */
40 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
41 @ThreadSafe
42 public final class LoadBalancerRegistry {
43   private static final Logger logger = Logger.getLogger(LoadBalancerRegistry.class.getName());
44   private static LoadBalancerRegistry instance;
45   private static final Iterable<Class<?>> HARDCODED_CLASSES = getHardCodedClasses();
46 
47   private final LinkedHashSet<LoadBalancerProvider> allProviders =
48       new LinkedHashSet<>();
49   private final LinkedHashMap<String, LoadBalancerProvider> effectiveProviders =
50       new LinkedHashMap<>();
51 
52   /**
53    * Register a provider.
54    *
55    * <p>If the provider's {@link LoadBalancerProvider#isAvailable isAvailable()} returns
56    * {@code false}, this method will throw {@link IllegalArgumentException}.
57    *
58    * <p>If more than one provider with the same {@link LoadBalancerProvider#getPolicyName policy
59    * name} are registered, the one with the highest {@link LoadBalancerProvider#getPriority
60    * priority} will be effective.  If there are more than one name-sake providers rank the highest
61    * priority, the one registered first will be effective.
62    */
register(LoadBalancerProvider provider)63   public synchronized void register(LoadBalancerProvider provider) {
64     addProvider(provider);
65     refreshProviderMap();
66   }
67 
addProvider(LoadBalancerProvider provider)68   private synchronized void addProvider(LoadBalancerProvider provider) {
69     checkArgument(provider.isAvailable(), "isAvailable() returned false");
70     allProviders.add(provider);
71   }
72 
73   /**
74    * Deregisters a provider.  No-op if the provider is not in the registry.  If there are more
75    * than one providers with the same policy name as the deregistered one in the registry, one
76    * of them will become the effective provider for that policy, per the rule documented in {@link
77    * #register}.
78    *
79    * @param provider the provider that was added to the register via {@link #register}.
80    */
deregister(LoadBalancerProvider provider)81   public synchronized void deregister(LoadBalancerProvider provider) {
82     allProviders.remove(provider);
83     refreshProviderMap();
84   }
85 
refreshProviderMap()86   private synchronized void refreshProviderMap() {
87     effectiveProviders.clear();
88     for (LoadBalancerProvider provider : allProviders) {
89       String policy = provider.getPolicyName();
90       LoadBalancerProvider existing = effectiveProviders.get(policy);
91       if (existing == null || existing.getPriority() < provider.getPriority()) {
92         effectiveProviders.put(policy, provider);
93       }
94     }
95   }
96 
97   /**
98    * Returns the default registry that loads providers via the Java service loader mechanism.
99    */
getDefaultRegistry()100   public static synchronized LoadBalancerRegistry getDefaultRegistry() {
101     if (instance == null) {
102       List<LoadBalancerProvider> providerList = ServiceProviders.loadAll(
103           LoadBalancerProvider.class,
104           HARDCODED_CLASSES,
105           LoadBalancerProvider.class.getClassLoader(),
106           new LoadBalancerPriorityAccessor());
107       instance = new LoadBalancerRegistry();
108       for (LoadBalancerProvider provider : providerList) {
109         logger.fine("Service loader found " + provider);
110         instance.addProvider(provider);
111       }
112       instance.refreshProviderMap();
113     }
114     return instance;
115   }
116 
117   /**
118    * Returns the effective provider for the given load-balancing policy, or {@code null} if no
119    * suitable provider can be found.  Each provider declares its policy name via {@link
120    * LoadBalancerProvider#getPolicyName}.
121    */
122   @Nullable
getProvider(String policy)123   public synchronized LoadBalancerProvider getProvider(String policy) {
124     return effectiveProviders.get(checkNotNull(policy, "policy"));
125   }
126 
127   /**
128    * Returns effective providers in a new map.
129    */
130   @VisibleForTesting
providers()131   synchronized Map<String, LoadBalancerProvider> providers() {
132     return new LinkedHashMap<>(effectiveProviders);
133   }
134 
135   @VisibleForTesting
getHardCodedClasses()136   static List<Class<?>> getHardCodedClasses() {
137     // Class.forName(String) is used to remove the need for ProGuard configuration. Note that
138     // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader):
139     // https://sourceforge.net/p/proguard/bugs/418/
140     ArrayList<Class<?>> list = new ArrayList<>();
141     try {
142       list.add(Class.forName("io.grpc.internal.PickFirstLoadBalancerProvider"));
143     } catch (ClassNotFoundException e) {
144       logger.log(Level.WARNING, "Unable to find pick-first LoadBalancer", e);
145     }
146     try {
147       list.add(Class.forName("io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"));
148     } catch (ClassNotFoundException e) {
149       // Since hard-coded list is only used in Android environment, and we don't expect round-robin
150       // to be actually used there, we log it as a lower level.
151       logger.log(Level.FINE, "Unable to find round-robin LoadBalancer", e);
152     }
153     return Collections.unmodifiableList(list);
154   }
155 
156   private static final class LoadBalancerPriorityAccessor
157       implements ServiceProviders.PriorityAccessor<LoadBalancerProvider> {
158 
LoadBalancerPriorityAccessor()159     LoadBalancerPriorityAccessor() {}
160 
161     @Override
isAvailable(LoadBalancerProvider provider)162     public boolean isAvailable(LoadBalancerProvider provider) {
163       return provider.isAvailable();
164     }
165 
166     @Override
getPriority(LoadBalancerProvider provider)167     public int getPriority(LoadBalancerProvider provider) {
168       return provider.getPriority();
169     }
170   }
171 }
172