All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.LoadBalancer Maven / Gradle / Ivy

/*
 * Copyright 2016 The gRPC Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.grpc;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;

/**
 * A pluggable component that receives resolved addresses from {@link NameResolver} and provides the
 * channel a usable subchannel when asked.
 *
 * 

Overview

* *

A LoadBalancer typically implements three interfaces: *

    *
  1. {@link LoadBalancer} is the main interface. All methods on it are invoked sequentially * in the same synchronization context (see next section) as returned by * {@link io.grpc.LoadBalancer.Helper#getSynchronizationContext}. It receives the results * from the {@link NameResolver}, updates of subchannels' connectivity states, and the * channel's request for the LoadBalancer to shutdown.
  2. *
  3. {@link SubchannelPicker SubchannelPicker} does the actual load-balancing work. It selects * a {@link Subchannel Subchannel} for each new RPC.
  4. *
  5. {@link Factory Factory} creates a new {@link LoadBalancer} instance. *
* *

{@link Helper Helper} is implemented by gRPC library and provided to {@link Factory * Factory}. It provides functionalities that a {@code LoadBalancer} implementation would typically * need. * *

The Synchronization Context

* *

All methods on the {@link LoadBalancer} interface are called from a Synchronization Context, * meaning they are serialized, thus the balancer implementation doesn't need to worry about * synchronization among them. {@link io.grpc.LoadBalancer.Helper#getSynchronizationContext} * allows implementations to schedule tasks to be run in the same Synchronization Context, with or * without a delay, thus those tasks don't need to worry about synchronizing with the balancer * methods. * *

However, the actual running thread may be the network thread, thus the following rules must be * followed to prevent blocking or even dead-locking in a network: * *

    * *
  1. Never block in the Synchronization Context. The callback methods must * return quickly. Examples or work that must be avoided: CPU-intensive calculation, waiting on * synchronization primitives, blocking I/O, blocking RPCs, etc.
  2. * *
  3. Avoid calling into other components with lock held. The Synchronization * Context may be under a lock, e.g., the transport lock of OkHttp. If your LoadBalancer has a * lock, holds the lock in a callback method (e.g., {@link #handleSubchannelState * handleSubchannelState()}) while calling into another class that may involve locks, be cautious * of deadlock. Generally you wouldn't need any locking in the LoadBalancer.
  4. * *
* *

The canonical implementation pattern

* *

A {@link LoadBalancer} keeps states like the latest addresses from NameResolver, the * Subchannel(s) and their latest connectivity states. These states are mutated within the Channel * Executor. * *

A typical {@link SubchannelPicker SubchannelPicker} holds a snapshot of these states. It may * have its own states, e.g., a picker from a round-robin load-balancer may keep a pointer to the * next Subchannel, which are typically mutated by multiple threads. The picker should only mutate * its own state, and should not mutate or re-acquire the states of the LoadBalancer. This way the * picker only needs to synchronize its own states, which is typically trivial to implement. * *

When the LoadBalancer states changes, e.g., Subchannels has become or stopped being READY, and * we want subsequent RPCs to use the latest list of READY Subchannels, LoadBalancer would create * a new picker, which holds a snapshot of the latest Subchannel list. Refer to the javadoc of * {@link #handleSubchannelState handleSubchannelState()} how to do this properly. * *

No synchronization should be necessary between LoadBalancer and its pickers if you follow * the pattern above. It may be possible to implement in a different way, but that would usually * result in more complicated threading. * * @since 1.2.0 */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") @NotThreadSafe public abstract class LoadBalancer { /** * The load-balancing config converted from an JSON object injected by the GRPC library. * *

{@link NameResolver}s should not produce this attribute. */ @NameResolver.ResolutionResultAttr public static final Attributes.Key> ATTR_LOAD_BALANCING_CONFIG = Attributes.Key.create("io.grpc.LoadBalancer.loadBalancingConfig"); private int recursionCount; /** * Handles newly resolved server groups and metadata attributes from name resolution system. * {@code servers} contained in {@link EquivalentAddressGroup} should be considered equivalent * but may be flattened into a single list if needed. * *

Implementations should not modify the given {@code servers}. * * @param servers the resolved server addresses, never empty. * @param attributes extra information from naming system. * @deprecated override {@link #handleResolvedAddresses(ResolvedAddresses) instead} * @since 1.2.0 */ @Deprecated public void handleResolvedAddressGroups( List servers, @NameResolver.ResolutionResultAttr Attributes attributes) { if (recursionCount++ == 0) { handleResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attributes).build()); } recursionCount = 0; } /** * Handles newly resolved server groups and metadata attributes from name resolution system. * {@code servers} contained in {@link EquivalentAddressGroup} should be considered equivalent * but may be flattened into a single list if needed. * *

Implementations should not modify the given {@code servers}. * * @param resolvedAddresses the resolved server addresses, attributes, and config. * @since 1.21.0 */ @SuppressWarnings("deprecation") public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { if (recursionCount++ == 0) { handleResolvedAddressGroups( resolvedAddresses.getAddresses(), resolvedAddresses.getAttributes()); } recursionCount = 0; } /** * Represents a combination of the resolved server address, associated attributes and a load * balancing policy config. The config is from the {@link * LoadBalancerProvider#parseLoadBalancingPolicyConfig(Map)}. * * @since 1.21.0 */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public static final class ResolvedAddresses { private final List addresses; @NameResolver.ResolutionResultAttr private final Attributes attributes; @Nullable private final Object loadBalancingPolicyConfig; // Make sure to update toBuilder() below! private ResolvedAddresses( List addresses, @NameResolver.ResolutionResultAttr Attributes attributes, Object loadBalancingPolicyConfig) { this.addresses = Collections.unmodifiableList(new ArrayList<>(checkNotNull(addresses, "addresses"))); this.attributes = checkNotNull(attributes, "attributes"); this.loadBalancingPolicyConfig = loadBalancingPolicyConfig; } /** * Factory for constructing a new Builder. * * @since 1.21.0 */ public static Builder newBuilder() { return new Builder(); } /** * Converts this back to a builder. * * @since 1.21.0 */ public Builder toBuilder() { return newBuilder() .setAddresses(addresses) .setAttributes(attributes) .setLoadBalancingPolicyConfig(loadBalancingPolicyConfig); } /** * Gets the server addresses. * * @since 1.21.0 */ public List getAddresses() { return addresses; } /** * Gets the attributes associated with these addresses. If this was not previously set, * {@link Attributes#EMPTY} will be returned. * * @since 1.21.0 */ @NameResolver.ResolutionResultAttr public Attributes getAttributes() { return attributes; } /** * Gets the domain specific load balancing policy. This is the config produced by * {@link LoadBalancerProvider#parseLoadBalancingPolicyConfig(Map)}. * * @since 1.21.0 */ @Nullable public Object getLoadBalancingPolicyConfig() { return loadBalancingPolicyConfig; } /** * Builder for {@link ResolvedAddresses}. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public static final class Builder { private List addresses; @NameResolver.ResolutionResultAttr private Attributes attributes = Attributes.EMPTY; @Nullable private Object loadBalancingPolicyConfig; Builder() {} /** * Sets the addresses. This field is required. * * @return this. */ public Builder setAddresses(List addresses) { this.addresses = addresses; return this; } /** * Sets the attributes. This field is optional; if not called, {@link Attributes#EMPTY} * will be used. * * @return this. */ public Builder setAttributes(@NameResolver.ResolutionResultAttr Attributes attributes) { this.attributes = attributes; return this; } /** * Sets the load balancing policy config. This field is optional. * * @return this. */ public Builder setLoadBalancingPolicyConfig(@Nullable Object loadBalancingPolicyConfig) { this.loadBalancingPolicyConfig = loadBalancingPolicyConfig; return this; } /** * Constructs the {@link ResolvedAddresses}. */ public ResolvedAddresses build() { return new ResolvedAddresses(addresses, attributes, loadBalancingPolicyConfig); } } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("addresses", addresses) .add("attributes", attributes) .add("loadBalancingPolicyConfig", loadBalancingPolicyConfig) .toString(); } @Override public int hashCode() { return Objects.hashCode(addresses, attributes, loadBalancingPolicyConfig); } @Override public boolean equals(Object obj) { if (!(obj instanceof ResolvedAddresses)) { return false; } ResolvedAddresses that = (ResolvedAddresses) obj; return Objects.equal(this.addresses, that.addresses) && Objects.equal(this.attributes, that.attributes) && Objects.equal(this.loadBalancingPolicyConfig, that.loadBalancingPolicyConfig); } } /** * Handles an error from the name resolution system. * * @param error a non-OK status * @since 1.2.0 */ public abstract void handleNameResolutionError(Status error); /** * Handles a state change on a Subchannel. * *

The initial state of a Subchannel is IDLE. You won't get a notification for the initial IDLE * state. * *

If the new state is not SHUTDOWN, this method should create a new picker and call {@link * Helper#updateBalancingState Helper.updateBalancingState()}. Failing to do so may result in * unnecessary delays of RPCs. Please refer to {@link PickResult#withSubchannel * PickResult.withSubchannel()}'s javadoc for more information. * *

SHUTDOWN can only happen in two cases. One is that LoadBalancer called {@link * Subchannel#shutdown} earlier, thus it should have already discarded this Subchannel. The other * is that Channel is doing a {@link ManagedChannel#shutdownNow forced shutdown} or has already * terminated, thus there won't be further requests to LoadBalancer. Therefore, the LoadBalancer * usually don't need to react to a SHUTDOWN state. * * @param subchannel the involved Subchannel * @param stateInfo the new state * @since 1.2.0 * @deprecated This method will be removed. Stop overriding it. Instead, pass {@link * SubchannelStateListener} to {@link Subchannel#start} to receive Subchannel state * updates */ @Deprecated public void handleSubchannelState( Subchannel subchannel, ConnectivityStateInfo stateInfo) { // Do nothing. If the implemetation doesn't implement this, it will get subchannel states from // the new API. We don't throw because there may be forwarding LoadBalancers still plumb this. } /** * The channel asks the load-balancer to shutdown. No more methods on this class will be called * after this method. The implementation should shutdown all Subchannels and OOB channels, and do * any other cleanup as necessary. * * @since 1.2.0 */ public abstract void shutdown(); /** * Whether this LoadBalancer can handle empty address group list to be passed to {@link * #handleResolvedAddresses(ResolvedAddresses)}. The default implementation returns * {@code false}, meaning that if the NameResolver returns an empty list, the Channel will turn * that into an error and call {@link #handleNameResolutionError}. LoadBalancers that want to * accept empty lists should override this method and return {@code true}. * *

This method should always return a constant value. It's not specified when this will be * called. */ public boolean canHandleEmptyAddressListFromNameResolution() { return false; } /** * The channel asks the LoadBalancer to establish connections now (if applicable) so that the * upcoming RPC may then just pick a ready connection without waiting for connections. This * is triggered by {@link ManagedChannel#getState ManagedChannel.getState(true)}. * *

If LoadBalancer doesn't override it, this is no-op. If it infeasible to create connections * given the current state, e.g. no Subchannel has been created yet, LoadBalancer can ignore this * request. * * @since 1.22.0 */ public void requestConnection() {} /** * The main balancing logic. It must be thread-safe. Typically it should only * synchronize on its own state, and avoid synchronizing with the LoadBalancer's state. * * @since 1.2.0 */ @ThreadSafe @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public abstract static class SubchannelPicker { /** * Make a balancing decision for a new RPC. * * @param args the pick arguments * @since 1.3.0 */ public abstract PickResult pickSubchannel(PickSubchannelArgs args); /** * Tries to establish connections now so that the upcoming RPC may then just pick a ready * connection without having to connect first. * *

No-op if unsupported. * * @deprecated override {@link LoadBalancer#requestConnection} instead. * @since 1.11.0 */ @Deprecated public void requestConnection() {} } /** * Provides arguments for a {@link SubchannelPicker#pickSubchannel( * LoadBalancer.PickSubchannelArgs)}. * * @since 1.2.0 */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public abstract static class PickSubchannelArgs { /** * Call options. * * @since 1.2.0 */ public abstract CallOptions getCallOptions(); /** * Headers of the call. {@link SubchannelPicker#pickSubchannel} may mutate it before before * returning. * * @since 1.2.0 */ public abstract Metadata getHeaders(); /** * Call method. * * @since 1.2.0 */ public abstract MethodDescriptor getMethodDescriptor(); } /** * A balancing decision made by {@link SubchannelPicker SubchannelPicker} for an RPC. * *

The outcome of the decision will be one of the following: *

    *
  • Proceed: if a Subchannel is provided via {@link #withSubchannel withSubchannel()}, and is * in READY state when the RPC tries to start on it, the RPC will proceed on that * Subchannel.
  • *
  • Error: if an error is provided via {@link #withError withError()}, and the RPC is not * wait-for-ready (i.e., {@link CallOptions#withWaitForReady} was not called), the RPC will * fail immediately with the given error.
  • *
  • Buffer: in all other cases, the RPC will be buffered in the Channel, until the next * picker is provided via {@link Helper#updateBalancingState Helper.updateBalancingState()}, * when the RPC will go through the same picking process again.
  • *
* * @since 1.2.0 */ @Immutable @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public static final class PickResult { private static final PickResult NO_RESULT = new PickResult(null, null, Status.OK, false); @Nullable private final Subchannel subchannel; @Nullable private final ClientStreamTracer.Factory streamTracerFactory; // An error to be propagated to the application if subchannel == null // Or OK if there is no error. // subchannel being null and error being OK means RPC needs to wait private final Status status; // True if the result is created by withDrop() private final boolean drop; private PickResult( @Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory, Status status, boolean drop) { this.subchannel = subchannel; this.streamTracerFactory = streamTracerFactory; this.status = checkNotNull(status, "status"); this.drop = drop; } /** * A decision to proceed the RPC on a Subchannel. * *

The Subchannel should either be an original Subchannel returned by {@link * Helper#createSubchannel Helper.createSubchannel()}, or a wrapper of it preferrably based on * {@code ForwardingSubchannel}. At the very least its {@link Subchannel#getInternalSubchannel * getInternalSubchannel()} must return the same object as the one returned by the original. * Otherwise the Channel cannot use it for the RPC. * *

When the RPC tries to use the return Subchannel, which is briefly after this method * returns, the state of the Subchannel will decide where the RPC would go: * *

    *
  • READY: the RPC will proceed on this Subchannel.
  • *
  • IDLE: the RPC will be buffered. Subchannel will attempt to create connection.
  • *
  • All other states: the RPC will be buffered.
  • *
* *

All buffered RPCs will stay buffered until the next call of {@link * Helper#updateBalancingState Helper.updateBalancingState()}, which will trigger a new picking * process. * *

Note that Subchannel's state may change at the same time the picker is making the * decision, which means the decision may be made with (to-be) outdated information. For * example, a picker may return a Subchannel known to be READY, but it has become IDLE when is * about to be used by the RPC, which makes the RPC to be buffered. The LoadBalancer will soon * learn about the Subchannels' transition from READY to IDLE, create a new picker and allow the * RPC to use another READY transport if there is any. * *

You will want to avoid running into a situation where there are READY Subchannels out * there but some RPCs are still buffered for longer than a brief time. *

    *
  • This can happen if you return Subchannels with states other than READY and IDLE. For * example, suppose you round-robin on 2 Subchannels, in READY and CONNECTING states * respectively. If the picker ignores the state and pick them equally, 50% of RPCs will * be stuck in buffered state until both Subchannels are READY.
  • *
  • This can also happen if you don't create a new picker at key state changes of * Subchannels. Take the above round-robin example again. Suppose you do pick only READY * and IDLE Subchannels, and initially both Subchannels are READY. Now one becomes IDLE, * then CONNECTING and stays CONNECTING for a long time. If you don't create a new picker * in response to the CONNECTING state to exclude that Subchannel, 50% of RPCs will hit it * and be buffered even though the other Subchannel is READY.
  • *
* *

In order to prevent unnecessary delay of RPCs, the rules of thumb are: *

    *
  1. The picker should only pick Subchannels that are known as READY or IDLE. Whether to * pick IDLE Subchannels depends on whether you want Subchannels to connect on-demand or * actively: *
      *
    • If you want connect-on-demand, include IDLE Subchannels in your pick results, * because when an RPC tries to use an IDLE Subchannel, the Subchannel will try to * connect.
    • *
    • If you want Subchannels to be always connected even when there is no RPC, you * would call {@link Subchannel#requestConnection Subchannel.requestConnection()} * whenever the Subchannel has transitioned to IDLE, then you don't need to include * IDLE Subchannels in your pick results.
    • *
  2. *
  3. Always create a new picker and call {@link Helper#updateBalancingState * Helper.updateBalancingState()} whenever {@link #handleSubchannelState * handleSubchannelState()} is called, unless the new state is SHUTDOWN. See * {@code handleSubchannelState}'s javadoc for more details.
  4. *
* * @param subchannel the picked Subchannel. It must have been {@link Subchannel#start started} * @param streamTracerFactory if not null, will be used to trace the activities of the stream * created as a result of this pick. Note it's possible that no * stream is created at all in some cases. * @since 1.3.0 */ public static PickResult withSubchannel( Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory) { return new PickResult( checkNotNull(subchannel, "subchannel"), streamTracerFactory, Status.OK, false); } /** * Equivalent to {@code withSubchannel(subchannel, null)}. * * @since 1.2.0 */ public static PickResult withSubchannel(Subchannel subchannel) { return withSubchannel(subchannel, null); } /** * A decision to report a connectivity error to the RPC. If the RPC is {@link * CallOptions#withWaitForReady wait-for-ready}, it will stay buffered. Otherwise, it will fail * with the given error. * * @param error the error status. Must not be OK. * @since 1.2.0 */ public static PickResult withError(Status error) { Preconditions.checkArgument(!error.isOk(), "error status shouldn't be OK"); return new PickResult(null, null, error, false); } /** * A decision to fail an RPC immediately. This is a final decision and will ignore retry * policy. * * @param status the status with which the RPC will fail. Must not be OK. * @since 1.8.0 */ public static PickResult withDrop(Status status) { Preconditions.checkArgument(!status.isOk(), "drop status shouldn't be OK"); return new PickResult(null, null, status, true); } /** * No decision could be made. The RPC will stay buffered. * * @since 1.2.0 */ public static PickResult withNoResult() { return NO_RESULT; } /** * The Subchannel if this result was created by {@link #withSubchannel withSubchannel()}, or * null otherwise. * * @since 1.2.0 */ @Nullable public Subchannel getSubchannel() { return subchannel; } /** * The stream tracer factory this result was created with. * * @since 1.3.0 */ @Nullable public ClientStreamTracer.Factory getStreamTracerFactory() { return streamTracerFactory; } /** * The status associated with this result. Non-{@code OK} if created with {@link #withError * withError}, or {@code OK} otherwise. * * @since 1.2.0 */ public Status getStatus() { return status; } /** * Returns {@code true} if this result was created by {@link #withDrop withDrop()}. * * @since 1.8.0 */ public boolean isDrop() { return drop; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("subchannel", subchannel) .add("streamTracerFactory", streamTracerFactory) .add("status", status) .add("drop", drop) .toString(); } @Override public int hashCode() { return Objects.hashCode(subchannel, status, streamTracerFactory, drop); } /** * Returns true if the {@link Subchannel}, {@link Status}, and * {@link ClientStreamTracer.Factory} all match. */ @Override public boolean equals(Object other) { if (!(other instanceof PickResult)) { return false; } PickResult that = (PickResult) other; return Objects.equal(subchannel, that.subchannel) && Objects.equal(status, that.status) && Objects.equal(streamTracerFactory, that.streamTracerFactory) && drop == that.drop; } } /** * Arguments for creating a {@link Subchannel}. * * @since 1.22.0 */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public static final class CreateSubchannelArgs { private final List addrs; private final Attributes attrs; private final Object[][] customOptions; private CreateSubchannelArgs( List addrs, Attributes attrs, Object[][] customOptions) { this.addrs = checkNotNull(addrs, "addresses are not set"); this.attrs = checkNotNull(attrs, "attrs"); this.customOptions = checkNotNull(customOptions, "customOptions"); } /** * Returns the addresses, which is an unmodifiable list. */ public List getAddresses() { return addrs; } /** * Returns the attributes. */ public Attributes getAttributes() { return attrs; } /** * Get the value for a custom option or its inherent default. * * @param key Key identifying option */ @SuppressWarnings("unchecked") public T getOption(Key key) { Preconditions.checkNotNull(key, "key"); for (int i = 0; i < customOptions.length; i++) { if (key.equals(customOptions[i][0])) { return (T) customOptions[i][1]; } } return key.defaultValue; } /** * Returns a builder with the same initial values as this object. */ public Builder toBuilder() { return newBuilder().setAddresses(addrs).setAttributes(attrs).copyCustomOptions(customOptions); } /** * Creates a new builder. */ public static Builder newBuilder() { return new Builder(); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("addrs", addrs) .add("attrs", attrs) .add("customOptions", Arrays.deepToString(customOptions)) .toString(); } @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public static final class Builder { private List addrs; private Attributes attrs = Attributes.EMPTY; private Object[][] customOptions = new Object[0][2]; Builder() { } private Builder copyCustomOptions(Object[][] options) { customOptions = new Object[options.length][2]; System.arraycopy(options, 0, customOptions, 0, options.length); return this; } /** * Add a custom option. Any existing value for the key is overwritten. * *

This is an optional property. * * @param key the option key * @param value the option value */ public Builder addOption(Key key, T value) { Preconditions.checkNotNull(key, "key"); Preconditions.checkNotNull(value, "value"); int existingIdx = -1; for (int i = 0; i < customOptions.length; i++) { if (key.equals(customOptions[i][0])) { existingIdx = i; break; } } if (existingIdx == -1) { Object[][] newCustomOptions = new Object[customOptions.length + 1][2]; System.arraycopy(customOptions, 0, newCustomOptions, 0, customOptions.length); customOptions = newCustomOptions; existingIdx = customOptions.length - 1; } customOptions[existingIdx] = new Object[]{key, value}; return this; } /** * The addresses to connect to. All addresses are considered equivalent and will be tried * in the order they are provided. */ public Builder setAddresses(EquivalentAddressGroup addrs) { this.addrs = Collections.singletonList(addrs); return this; } /** * The addresses to connect to. All addresses are considered equivalent and will * be tried in the order they are provided. * *

This is a required property. * * @throws IllegalArgumentException if {@code addrs} is empty */ public Builder setAddresses(List addrs) { checkArgument(!addrs.isEmpty(), "addrs is empty"); this.addrs = Collections.unmodifiableList(new ArrayList<>(addrs)); return this; } /** * Attributes provided here will be included in {@link Subchannel#getAttributes}. * *

This is an optional property. Default is empty if not set. */ public Builder setAttributes(Attributes attrs) { this.attrs = checkNotNull(attrs, "attrs"); return this; } /** * Creates a new args object. */ public CreateSubchannelArgs build() { return new CreateSubchannelArgs(addrs, attrs, customOptions); } } /** * Key for a key-value pair. Uses reference equality. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public static final class Key { private final String debugString; private final T defaultValue; private Key(String debugString, T defaultValue) { this.debugString = debugString; this.defaultValue = defaultValue; } /** * Factory method for creating instances of {@link Key}. The default value of the key is * {@code null}. * * @param debugString a debug string that describes this key. * @param Key type * @return Key object */ public static Key create(String debugString) { Preconditions.checkNotNull(debugString, "debugString"); return new Key<>(debugString, /*defaultValue=*/ null); } /** * Factory method for creating instances of {@link Key}. * * @param debugString a debug string that describes this key. * @param defaultValue default value to return when value for key not set * @param Key type * @return Key object */ public static Key createWithDefault(String debugString, T defaultValue) { Preconditions.checkNotNull(debugString, "debugString"); return new Key<>(debugString, defaultValue); } /** * Returns the user supplied default value for this key. */ public T getDefault() { return defaultValue; } @Override public String toString() { return debugString; } } } /** * Provides essentials for LoadBalancer implementations. * * @since 1.2.0 */ @ThreadSafe @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public abstract static class Helper { /** * Equivalent to {@link #createSubchannel(List, Attributes)} with the given single {@code * EquivalentAddressGroup}. * * @since 1.2.0 * @deprecated Use {@link #createSubchannel(io.grpc.LoadBalancer.CreateSubchannelArgs)} * instead. Note the new API must be called from {@link #getSynchronizationContext * the Synchronization Context}. */ @Deprecated public final Subchannel createSubchannel(EquivalentAddressGroup addrs, Attributes attrs) { checkNotNull(addrs, "addrs"); return createSubchannel(Collections.singletonList(addrs), attrs); } /** * Creates a Subchannel, which is a logical connection to the given group of addresses which are * considered equivalent. The {@code attrs} are custom attributes associated with this * Subchannel, and can be accessed later through {@link Subchannel#getAttributes * Subchannel.getAttributes()}. * *

It is recommended you call this method from the Synchronization Context, otherwise your * logic around the creation may race with {@link #handleSubchannelState}. See * #5015 for more discussions. * *

The LoadBalancer is responsible for closing unused Subchannels, and closing all * Subchannels within {@link #shutdown}. * * @throws IllegalArgumentException if {@code addrs} is empty * @since 1.14.0 * @deprecated Use {@link #createSubchannel(io.grpc.LoadBalancer.CreateSubchannelArgs)} * instead. Note the new API must be called from {@link #getSynchronizationContext * the Synchronization Context}. */ @Deprecated public Subchannel createSubchannel(List addrs, Attributes attrs) { throw new UnsupportedOperationException(); } /** * Creates a Subchannel, which is a logical connection to the given group of addresses which are * considered equivalent. The {@code attrs} are custom attributes associated with this * Subchannel, and can be accessed later through {@link Subchannel#getAttributes * Subchannel.getAttributes()}. * *

The LoadBalancer is responsible for closing unused Subchannels, and closing all * Subchannels within {@link #shutdown}. * *

It must be called from {@link #getSynchronizationContext the Synchronization Context} * * @since 1.22.0 */ public Subchannel createSubchannel(CreateSubchannelArgs args) { throw new UnsupportedOperationException(); } /** * Equivalent to {@link #updateSubchannelAddresses(io.grpc.LoadBalancer.Subchannel, List)} with * the given single {@code EquivalentAddressGroup}. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * * @since 1.4.0 * @deprecated use {@link Subchannel#updateAddresses} instead */ @Deprecated public final void updateSubchannelAddresses( Subchannel subchannel, EquivalentAddressGroup addrs) { checkNotNull(addrs, "addrs"); updateSubchannelAddresses(subchannel, Collections.singletonList(addrs)); } /** * Replaces the existing addresses used with {@code subchannel}. This method is superior to * {@link #createSubchannel} when the new and old addresses overlap, since the subchannel can * continue using an existing connection. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * * @throws IllegalArgumentException if {@code subchannel} was not returned from {@link * #createSubchannel} or {@code addrs} is empty * @since 1.14.0 * @deprecated use {@link Subchannel#updateAddresses} instead */ @Deprecated public void updateSubchannelAddresses( Subchannel subchannel, List addrs) { throw new UnsupportedOperationException(); } /** * Out-of-band channel for LoadBalancer’s own RPC needs, e.g., talking to an external * load-balancer service. * *

The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB * channels within {@link #shutdown}. * * @since 1.4.0 */ // TODO(ejona): Allow passing a List here and to updateOobChannelAddresses, but want to // wait until https://github.com/grpc/grpc-java/issues/4469 is done. // https://github.com/grpc/grpc-java/issues/4618 public abstract ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority); /** * Updates the addresses used for connections in the {@code Channel} that was created by {@link * #createOobChannel(EquivalentAddressGroup, String)}. This is supperior to {@link * #createOobChannel(EquivalentAddressGroup, String)} when the old and new addresses overlap, * since the channel can continue using an existing connection. * * @throws IllegalArgumentException if {@code channel} was not returned from {@link * #createOobChannel} * @since 1.4.0 */ public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressGroup eag) { throw new UnsupportedOperationException(); } /** * Creates an out-of-band channel for LoadBalancer's own RPC needs, e.g., talking to an external * load-balancer service, that is specified by a target string. See the documentation on * {@link ManagedChannelBuilder#forTarget} for the format of a target string. * *

The target string will be resolved by a {@link NameResolver} created according to the * target string. The out-of-band channel doesn't have load-balancing. If multiple addresses * are resolved for the target, the first working address will be used. * *

The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB * channels within {@link #shutdown}. * *

NOT IMPLEMENTED: this method is currently a stub and not yet implemented by gRPC. * * @since 1.20.0 */ public ManagedChannel createResolvingOobChannel(String target) { throw new UnsupportedOperationException("Not implemented"); } /** * Set a new state with a new picker to the channel. * *

When a new picker is provided via {@code updateBalancingState()}, the channel will apply * the picker on all buffered RPCs, by calling {@link SubchannelPicker#pickSubchannel( * LoadBalancer.PickSubchannelArgs)}. * *

The channel will hold the picker and use it for all RPCs, until {@code * updateBalancingState()} is called again and a new picker replaces the old one. If {@code * updateBalancingState()} has never been called, the channel will buffer all RPCs until a * picker is provided. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * *

The passed state will be the channel's new state. The SHUTDOWN state should not be passed * and its behavior is undefined. * * @since 1.6.0 */ public abstract void updateBalancingState( @Nonnull ConnectivityState newState, @Nonnull SubchannelPicker newPicker); /** * Call {@link NameResolver#refresh} on the channel's resolver. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * * @since 1.18.0 */ public void refreshNameResolution() { throw new UnsupportedOperationException(); } /** * Schedule a task to be run in the Synchronization Context, which serializes the task with the * callback methods on the {@link LoadBalancer} interface. * * @since 1.2.0 * @deprecated use/implement {@code getSynchronizationContext()} instead */ @Deprecated public void runSerialized(Runnable task) { getSynchronizationContext().execute(task); } /** * Returns a {@link SynchronizationContext} that runs tasks in the same Synchronization Context * as that the callback methods on the {@link LoadBalancer} interface are run in. * *

Pro-tip: in order to call {@link SynchronizationContext#schedule}, you need to provide a * {@link ScheduledExecutorService}. {@link #getScheduledExecutorService} is provided for your * convenience. * * @since 1.17.0 */ public SynchronizationContext getSynchronizationContext() { // TODO(zhangkun): make getSynchronizationContext() abstract after runSerialized() is deleted throw new UnsupportedOperationException(); } /** * Returns a {@link ScheduledExecutorService} for scheduling delayed tasks. * *

This service is a shared resource and is only meant for quick tasks. DO NOT block or run * time-consuming tasks. * *

The returned service doesn't support {@link ScheduledExecutorService#shutdown shutdown()} * and {@link ScheduledExecutorService#shutdownNow shutdownNow()}. They will throw if called. * * @since 1.17.0 */ public ScheduledExecutorService getScheduledExecutorService() { throw new UnsupportedOperationException(); } /** * Returns the NameResolver of the channel. * * @since 1.2.0 * * @deprecated this method will be deleted in a future release. If you think it shouldn't be * deleted, please file an issue on github. */ @Deprecated public abstract NameResolver.Factory getNameResolverFactory(); /** * Returns the authority string of the channel, which is derived from the DNS-style target name. * * @since 1.2.0 */ public abstract String getAuthority(); /** * Returns the {@link ChannelLogger} for the Channel served by this LoadBalancer. * * @since 1.17.0 */ public ChannelLogger getChannelLogger() { throw new UnsupportedOperationException(); } /** * Returns the {@link NameResolver.Args} that the Channel uses to create {@link NameResolver}s. * * @since 1.22.0 */ public NameResolver.Args getNameResolverArgs() { throw new UnsupportedOperationException(); } /** * Returns the {@link NameResolverRegistry} that the Channel uses to look for {@link * NameResolver}s. * * @since 1.22.0 */ public NameResolverRegistry getNameResolverRegistry() { throw new UnsupportedOperationException(); } } /** * A logical connection to a server, or a group of equivalent servers represented by an {@link * EquivalentAddressGroup}. * *

It maintains at most one physical connection (aka transport) for sending new RPCs, while * also keeps track of previous transports that has been shut down but not terminated yet. * *

If there isn't an active transport yet, and an RPC is assigned to the Subchannel, it will * create a new transport. It won't actively create transports otherwise. {@link * #requestConnection requestConnection()} can be used to ask Subchannel to create a transport if * there isn't any. * *

{@link #start} must be called prior to calling any other methods, with the exception of * {@link #shutdown}, which can be called at any time. * * @since 1.2.0 */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public abstract static class Subchannel { /** * Starts the Subchannel. Can only be called once. * *

Must be called prior to any other method on this class, except for {@link #shutdown} which * may be called at any time. * *

Must be called from the {@link Helper#getSynchronizationContext Synchronization Context}, * otherwise it may throw. See * #5015 for more discussions. * * @param listener receives state updates for this Subchannel. */ public void start(SubchannelStateListener listener) { throw new UnsupportedOperationException("Not implemented"); } /** * Shuts down the Subchannel. After this method is called, this Subchannel should no longer * be returned by the latest {@link SubchannelPicker picker}, and can be safely discarded. * *

Calling it on an already shut-down Subchannel has no effect. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * * @since 1.2.0 */ public abstract void shutdown(); /** * Asks the Subchannel to create a connection (aka transport), if there isn't an active one. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * * @since 1.2.0 */ public abstract void requestConnection(); /** * Returns the addresses that this Subchannel is bound to. This can be called only if * the Subchannel has only one {@link EquivalentAddressGroup}. Under the hood it calls * {@link #getAllAddresses}. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * * @throws IllegalStateException if this subchannel has more than one EquivalentAddressGroup. * Use {@link #getAllAddresses} instead * @since 1.2.0 */ public final EquivalentAddressGroup getAddresses() { List groups = getAllAddresses(); Preconditions.checkState(groups.size() == 1, "%s does not have exactly one group", groups); return groups.get(0); } /** * Returns the addresses that this Subchannel is bound to. The returned list will not be empty. * *

It should be called from the Synchronization Context. Currently will log a warning if * violated. It will become an exception eventually. See #5015 for the background. * * @since 1.14.0 */ public List getAllAddresses() { throw new UnsupportedOperationException(); } /** * The same attributes passed to {@link Helper#createSubchannel Helper.createSubchannel()}. * LoadBalancer can use it to attach additional information here, e.g., the shard this * Subchannel belongs to. * * @since 1.2.0 */ public abstract Attributes getAttributes(); /** * (Internal use only) returns a {@link Channel} that is backed by this Subchannel. This allows * a LoadBalancer to issue its own RPCs for auxiliary purposes, such as health-checking, on * already-established connections. This channel has certain restrictions: *

    *
  1. It can issue RPCs only if the Subchannel is {@code READY}. If {@link * Channel#newCall} is called when the Subchannel is not {@code READY}, the RPC will fail * immediately.
  2. *
  3. It doesn't support {@link CallOptions#withWaitForReady wait-for-ready} RPCs. Such RPCs * will fail immediately.
  4. *
* *

RPCs made on this Channel is not counted when determining ManagedChannel's {@link * ManagedChannelBuilder#idleTimeout idle mode}. In other words, they won't prevent * ManagedChannel from entering idle mode. * *

Warning: RPCs made on this channel will prevent a shut-down transport from terminating. If * you make long-running RPCs, you need to make sure they will finish in time after the * Subchannel has transitioned away from {@code READY} state * (notified through {@link #handleSubchannelState}). * *

Warning: this is INTERNAL API, is not supposed to be used by external users, and may * change without notice. If you think you must use it, please file an issue. */ @Internal public Channel asChannel() { throw new UnsupportedOperationException(); } /** * Returns a {@link ChannelLogger} for this Subchannel. * * @since 1.17.0 */ public ChannelLogger getChannelLogger() { throw new UnsupportedOperationException(); } /** * Replaces the existing addresses used with this {@code Subchannel}. If the new and old * addresses overlap, the Subchannel can continue using an existing connection. * *

It must be called from the Synchronization Context or will throw. * * @throws IllegalArgumentException if {@code addrs} is empty * @since 1.22.0 */ public void updateAddresses(List addrs) { throw new UnsupportedOperationException(); } /** * (Internal use only) returns an object that represents the underlying subchannel that is used * by the Channel for sending RPCs when this {@link Subchannel} is picked. This is an opaque * object that is both provided and consumed by the Channel. Its type is not * {@code Subchannel}. * *

Warning: this is INTERNAL API, is not supposed to be used by external users, and may * change without notice. If you think you must use it, please file an issue and we can consider * removing its "internal" status. */ @Internal public Object getInternalSubchannel() { throw new UnsupportedOperationException(); } } /** * Receives state changes for one {@link Subchannel}. All methods are run under {@link * Helper#getSynchronizationContext}. * * @since 1.22.0 */ public interface SubchannelStateListener { /** * Handles a state change on a Subchannel. * *

The initial state of a Subchannel is IDLE. You won't get a notification for the initial * IDLE state. * *

If the new state is not SHUTDOWN, this method should create a new picker and call {@link * Helper#updateBalancingState Helper.updateBalancingState()}. Failing to do so may result in * unnecessary delays of RPCs. Please refer to {@link PickResult#withSubchannel * PickResult.withSubchannel()}'s javadoc for more information. * *

SHUTDOWN can only happen in two cases. One is that LoadBalancer called {@link * Subchannel#shutdown} earlier, thus it should have already discarded this Subchannel. The * other is that Channel is doing a {@link ManagedChannel#shutdownNow forced shutdown} or has * already terminated, thus there won't be further requests to LoadBalancer. Therefore, the * LoadBalancer usually don't need to react to a SHUTDOWN state. * @param newState the new state * * @since 1.22.0 */ void onSubchannelState(ConnectivityStateInfo newState); } /** * Factory to create {@link LoadBalancer} instance. * * @since 1.2.0 */ @ThreadSafe @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public abstract static class Factory { /** * Creates a {@link LoadBalancer} that will be used inside a channel. * * @since 1.2.0 */ public abstract LoadBalancer newLoadBalancer(Helper helper); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy