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