blob: 04431fbd620975520f6fea523a2ac5e5c70ef51a [file] [log] [blame]
/*
* 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.
*
* <h3>Overview</h3>
*
* <p>A LoadBalancer typically implements three interfaces:
* <ol>
* <li>{@link LoadBalancer} is the main interface. All methods on it are invoked sequentially
* in the same <strong>synchronization context</strong> (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.</li>
* <li>{@link SubchannelPicker SubchannelPicker} does the actual load-balancing work. It selects
* a {@link Subchannel Subchannel} for each new RPC.</li>
* <li>{@link Factory Factory} creates a new {@link LoadBalancer} instance.
* </ol>
*
* <p>{@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.
*
* <h3>The Synchronization Context</h3>
*
* <p>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.
*
* <p>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:
*
* <ol>
*
* <li><strong>Never block in the Synchronization Context</strong>. 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.</li>
*
* <li><strong>Avoid calling into other components with lock held</strong>. The Synchronization
* Context may be under a lock, e.g., the transport lock of OkHttp. If your LoadBalancer holds a
* lock in a callback method (e.g., {@link #handleResolvedAddresses handleResolvedAddresses()})
* while calling into another method that also involves locks, be cautious of deadlock. Generally
* you wouldn't need any locking in the LoadBalancer if you follow the canonical implementation
* pattern below.</li>
*
* </ol>
*
* <h3>The canonical implementation pattern</h3>
*
* <p>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
* Synchronization Context,
*
* <p>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.
*
* <p>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
* io.grpc.LoadBalancer.SubchannelStateListener#onSubchannelState onSubchannelState()} how to do
* this properly.
*
* <p>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 {
@Internal
@NameResolver.ResolutionResultAttr
public static final Attributes.Key<Map<String, ?>> ATTR_HEALTH_CHECKING_CONFIG =
Attributes.Key.create("health-checking-config");
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.
*
* <p>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<EquivalentAddressGroup> 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.
*
* <p>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<EquivalentAddressGroup> addresses;
@NameResolver.ResolutionResultAttr
private final Attributes attributes;
@Nullable
private final Object loadBalancingPolicyConfig;
// Make sure to update toBuilder() below!
private ResolvedAddresses(
List<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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.
*
* <p>The initial state of a Subchannel is IDLE. You won't get a notification for the initial IDLE
* state.
*
* <p>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.
*
* <p>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 implementation 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}.
*
* <p>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)}.
*
* <p>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 <strong>must be thread-safe</strong>. 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.
*
* <p>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.
*
* <p>The outcome of the decision will be one of the following:
* <ul>
* <li>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.</li>
* <li>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.</li>
* <li>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.</li>
* </ul>
*
* @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.
*
* <p>The Subchannel should either be an original Subchannel returned by {@link
* Helper#createSubchannel Helper.createSubchannel()}, or a wrapper of it preferably 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.
*
* <p>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:
*
* <ul>
* <li>READY: the RPC will proceed on this Subchannel.</li>
* <li>IDLE: the RPC will be buffered. Subchannel will attempt to create connection.</li>
* <li>All other states: the RPC will be buffered.</li>
* </ul>
*
* <p><strong>All buffered RPCs will stay buffered</strong> until the next call of {@link
* Helper#updateBalancingState Helper.updateBalancingState()}, which will trigger a new picking
* process.
*
* <p>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.
*
* <p>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.
* <ul>
* <li>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.</li>
* <li>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.</li>
* </ul>
*
* <p>In order to prevent unnecessary delay of RPCs, the rules of thumb are:
* <ol>
* <li>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:
* <ul>
* <li>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.</li>
* <li>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.</li>
* </ul></li>
* <li>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.</li>
* </ol>
*
* @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<EquivalentAddressGroup> addrs;
private final Attributes attrs;
private final Object[][] customOptions;
private CreateSubchannelArgs(
List<EquivalentAddressGroup> 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<EquivalentAddressGroup> 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> T getOption(Key<T> 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<EquivalentAddressGroup> addrs;
private Attributes attrs = Attributes.EMPTY;
private Object[][] customOptions = new Object[0][2];
Builder() {
}
private <T> 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.
*
* <p>This is an <strong>optional</strong> property.
*
* @param key the option key
* @param value the option value
*/
public <T> Builder addOption(Key<T> 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.
*
* <p>This is a <strong>required</strong> property.
*
* @throws IllegalArgumentException if {@code addrs} is empty
*/
public Builder setAddresses(List<EquivalentAddressGroup> 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}.
*
* <p>This is an <strong>optional</strong> 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<T> {
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 <T> Key type
* @return Key object
*/
public static <T> Key<T> 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 <T> Key type
* @return Key object
*/
public static <T> Key<T> 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 {
/**
* 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()}.
*
* <p>The LoadBalancer is responsible for closing unused Subchannels, and closing all
* Subchannels within {@link #shutdown}.
*
* <p>It must be called from {@link #getSynchronizationContext the Synchronization Context}
*
* @since 1.22.0
*/
public Subchannel createSubchannel(CreateSubchannelArgs args) {
throw new UnsupportedOperationException();
}
/**
* Out-of-band channel for LoadBalancer’s own RPC needs, e.g., talking to an external
* load-balancer service.
*
* <p>The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
* channels within {@link #shutdown}.
*
* @since 1.4.0
*/
public abstract ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority);
/**
* Accept a list of EAG for multiple authorities: https://github.com/grpc/grpc-java/issues/4618
* */
public ManagedChannel createOobChannel(List<EquivalentAddressGroup> eag,
String authority) {
throw new UnsupportedOperationException();
}
/**
* Updates the addresses used for connections in the {@code Channel} that was created by {@link
* #createOobChannel(EquivalentAddressGroup, String)}. This is superior 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();
}
/**
* Updates the addresses with a new EAG list. Connection is continued when old and new addresses
* overlap.
* */
public void updateOobChannelAddresses(ManagedChannel channel,
List<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.
*
* <p>The target string will be resolved by a {@link NameResolver} created according to the
* target string.
*
* <p>The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
* channels within {@link #shutdown}.
*
* @since 1.20.0
*/
public ManagedChannel createResolvingOobChannel(String target) {
return createResolvingOobChannelBuilder(target).build();
}
/**
* Creates an out-of-band channel builder 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.
*
* <p>The target string will be resolved by a {@link NameResolver} created according to the
* target string.
*
* <p>The returned oob-channel builder defaults to use the same authority and ChannelCredentials
* (without bearer tokens) as the parent channel's for authentication. This is different from
* {@link #createResolvingOobChannelBuilder(String, ChannelCredentials)}.
*
* <p>The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
* channels within {@link #shutdown}.
*
* @deprecated Use {@link #createResolvingOobChannelBuilder(String, ChannelCredentials)}
* instead.
* @since 1.31.0
*/
@Deprecated
public ManagedChannelBuilder<?> createResolvingOobChannelBuilder(String target) {
throw new UnsupportedOperationException("Not implemented");
}
/**
* Creates an out-of-band channel builder for LoadBalancer's own RPC needs, e.g., talking to an
* external load-balancer service, that is specified by a target string and credentials. See
* the documentation on {@link Grpc#newChannelBuilder} for the format of a target string.
*
* <p>The target string will be resolved by a {@link NameResolver} created according to the
* target string.
*
* <p>The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
* channels within {@link #shutdown}.
*
* @since 1.35.0
*/
public ManagedChannelBuilder<?> createResolvingOobChannelBuilder(
String target, ChannelCredentials creds) {
throw new UnsupportedOperationException();
}
/**
* Set a new state with a new picker to the channel.
*
* <p>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)}.
*
* <p>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.
*
* <p>It should be called from the Synchronization Context. Currently will log a warning if
* violated. It will become an exception eventually. See <a
* href="https://github.com/grpc/grpc-java/issues/5015">#5015</a> for the background.
*
* <p>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.
*
* <p>It should be called from the Synchronization Context. Currently will log a warning if
* violated. It will become an exception eventually. See <a
* href="https://github.com/grpc/grpc-java/issues/5015">#5015</a> for the background.
*
* @since 1.18.0
*/
public void refreshNameResolution() {
throw new UnsupportedOperationException();
}
/**
* 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.
*
* <p>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.
*
* <p>This service is a shared resource and is only meant for quick tasks. DO NOT block or run
* time-consuming tasks.
*
* <p>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 authority string of the channel, which is derived from the DNS-style target name.
* If overridden by a load balancer, {@link #getUnsafeChannelCredentials} must also be
* overridden to call {@link #getChannelCredentials} or provide appropriate credentials.
*
* @since 1.2.0
*/
public abstract String getAuthority();
/**
* Returns the ChannelCredentials used to construct the channel, without bearer tokens.
*
* @since 1.35.0
*/
public ChannelCredentials getChannelCredentials() {
return getUnsafeChannelCredentials().withoutBearerTokens();
}
/**
* Returns the UNSAFE ChannelCredentials used to construct the channel,
* including bearer tokens. Load balancers should generally have no use for
* these credentials and use of them is heavily discouraged. These must be used
* <em>very</em> carefully to avoid sending bearer tokens to untrusted servers
* as the server could then impersonate the client. Generally it is only safe
* to use these credentials when communicating with the backend.
*
* @since 1.35.0
*/
public ChannelCredentials getUnsafeChannelCredentials() {
throw new UnsupportedOperationException();
}
/**
* 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}.
*
* <p>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.
*
* <p>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.
*
* <p>{@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.
*
* <p>Must be called prior to any other method on this class, except for {@link #shutdown} which
* may be called at any time.
*
* <p>Must be called from the {@link Helper#getSynchronizationContext Synchronization Context},
* otherwise it may throw. See <a href="https://github.com/grpc/grpc-java/issues/5015">
* #5015</a> 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.
*
* <p>Calling it on an already shut-down Subchannel has no effect.
*
* <p>It should be called from the Synchronization Context. Currently will log a warning if
* violated. It will become an exception eventually. See <a
* href="https://github.com/grpc/grpc-java/issues/5015">#5015</a> 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.
*
* <p>It should be called from the Synchronization Context. Currently will log a warning if
* violated. It will become an exception eventually. See <a
* href="https://github.com/grpc/grpc-java/issues/5015">#5015</a> 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}.
*
* <p>It should be called from the Synchronization Context. Currently will log a warning if
* violated. It will become an exception eventually. See <a
* href="https://github.com/grpc/grpc-java/issues/5015">#5015</a> 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<EquivalentAddressGroup> 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.
*
* <p>It should be called from the Synchronization Context. Currently will log a warning if
* violated. It will become an exception eventually. See <a
* href="https://github.com/grpc/grpc-java/issues/5015">#5015</a> for the background.
*
* @since 1.14.0
*/
public List<EquivalentAddressGroup> 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:
* <ol>
* <li>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.</li>
* <li>It doesn't support {@link CallOptions#withWaitForReady wait-for-ready} RPCs. Such RPCs
* will fail immediately.</li>
* </ol>
*
* <p>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.
*
* <p>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}).
*
* <p>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.
*
* <p>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<EquivalentAddressGroup> 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 <strong>is not</strong>
* {@code Subchannel}.
*
* <p>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.
*
* <p>The initial state of a Subchannel is IDLE. You won't get a notification for the initial
* IDLE state.
*
* <p>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.
*
* <p>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);
}
}