Home | History | Annotate | Download | only in grpc
      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