1 /* 2 * Copyright 2015 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.checkNotNull; 20 import static io.grpc.ConnectivityState.CONNECTING; 21 import static io.grpc.ConnectivityState.SHUTDOWN; 22 import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; 23 24 import com.google.common.annotations.VisibleForTesting; 25 import io.grpc.LoadBalancer.PickResult; 26 import io.grpc.LoadBalancer.PickSubchannelArgs; 27 import io.grpc.LoadBalancer.Subchannel; 28 import io.grpc.LoadBalancer.SubchannelPicker; 29 import java.util.List; 30 31 /** 32 * A {@link LoadBalancer} that provides no load balancing mechanism over the 33 * addresses from the {@link NameResolver}. The channel's default behavior 34 * (currently pick-first) is used for all addresses found. 35 */ 36 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") 37 public final class PickFirstBalancerFactory extends LoadBalancer.Factory { 38 39 private static final PickFirstBalancerFactory INSTANCE = new PickFirstBalancerFactory(); 40 41 private PickFirstBalancerFactory() { 42 } 43 44 /** 45 * Gets an instance of this factory. 46 */ 47 public static PickFirstBalancerFactory getInstance() { 48 return INSTANCE; 49 } 50 51 @Override 52 public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) { 53 return new PickFirstBalancer(helper); 54 } 55 56 @VisibleForTesting 57 static final class PickFirstBalancer extends LoadBalancer { 58 private final Helper helper; 59 private Subchannel subchannel; 60 61 PickFirstBalancer(Helper helper) { 62 this.helper = checkNotNull(helper, "helper"); 63 } 64 65 @Override 66 public void handleResolvedAddressGroups( 67 List<EquivalentAddressGroup> servers, Attributes attributes) { 68 if (subchannel == null) { 69 subchannel = helper.createSubchannel(servers, Attributes.EMPTY); 70 71 // The channel state does not get updated when doing name resolving today, so for the moment 72 // let LB report CONNECTION and call subchannel.requestConnection() immediately. 73 helper.updateBalancingState(CONNECTING, new Picker(PickResult.withSubchannel(subchannel))); 74 subchannel.requestConnection(); 75 } else { 76 helper.updateSubchannelAddresses(subchannel, servers); 77 } 78 } 79 80 @Override 81 public void handleNameResolutionError(Status error) { 82 if (subchannel != null) { 83 subchannel.shutdown(); 84 subchannel = null; 85 } 86 // NB(lukaszx0) Whether we should propagate the error unconditionally is arguable. It's fine 87 // for time being. 88 helper.updateBalancingState(TRANSIENT_FAILURE, new Picker(PickResult.withError(error))); 89 } 90 91 @Override 92 public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) { 93 ConnectivityState currentState = stateInfo.getState(); 94 if (subchannel != this.subchannel || currentState == SHUTDOWN) { 95 return; 96 } 97 98 SubchannelPicker picker; 99 switch (currentState) { 100 case IDLE: 101 picker = new RequestConnectionPicker(subchannel); 102 break; 103 case CONNECTING: 104 // It's safe to use RequestConnectionPicker here, so when coming from IDLE we could leave 105 // the current picker in-place. But ignoring the potential optimization is simpler. 106 picker = new Picker(PickResult.withNoResult()); 107 break; 108 case READY: 109 picker = new Picker(PickResult.withSubchannel(subchannel)); 110 break; 111 case TRANSIENT_FAILURE: 112 picker = new Picker(PickResult.withError(stateInfo.getStatus())); 113 break; 114 default: 115 throw new IllegalArgumentException("Unsupported state:" + currentState); 116 } 117 118 helper.updateBalancingState(currentState, picker); 119 } 120 121 @Override 122 public void shutdown() { 123 if (subchannel != null) { 124 subchannel.shutdown(); 125 } 126 } 127 } 128 129 /** 130 * No-op picker which doesn't add any custom picking logic. It just passes already known result 131 * received in constructor. 132 */ 133 private static final class Picker extends SubchannelPicker { 134 private final PickResult result; 135 136 Picker(PickResult result) { 137 this.result = checkNotNull(result, "result"); 138 } 139 140 @Override 141 public PickResult pickSubchannel(PickSubchannelArgs args) { 142 return result; 143 } 144 } 145 146 /** Picker that requests connection during pick, and returns noResult. */ 147 private static final class RequestConnectionPicker extends SubchannelPicker { 148 private final Subchannel subchannel; 149 150 RequestConnectionPicker(Subchannel subchannel) { 151 this.subchannel = checkNotNull(subchannel, "subchannel"); 152 } 153 154 @Override 155 public PickResult pickSubchannel(PickSubchannelArgs args) { 156 subchannel.requestConnection(); 157 return PickResult.withNoResult(); 158 } 159 160 @Override 161 public void requestConnection() { 162 subchannel.requestConnection(); 163 } 164 } 165 } 166