diff --git a/core/src/main/java/io/grpc/LoadBalancer2.java b/core/src/main/java/io/grpc/LoadBalancer2.java index b566eca2d4..2945efbb09 100644 --- a/core/src/main/java/io/grpc/LoadBalancer2.java +++ b/core/src/main/java/io/grpc/LoadBalancer2.java @@ -55,20 +55,21 @@ import javax.annotation.concurrent.ThreadSafe; * from the Channel Executor. It receives the results from the {@link NameResolver}, updates * of subchannels' connectivity states, and the channel's request for the LoadBalancer to * shutdown. - *
  • {@link SubchannelPicker} does the actual load-balancing work. It selects a - * {@link Subchannel} for each new RPC.
  • - *
  • {@link Factory} creates a new {@link LoadBalancer2} instance. + *
  • {@link SubchannelPicker SubchannelPicker} does the actual load-balancing work. It selects + * a {@link Subchannel Subchannel} for each new RPC.
  • + *
  • {@link Factory Factory} creates a new {@link LoadBalancer2} instance. * * - *

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

  • + *

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

    Channel Executor

    * *

    Channel Executor is an internal executor of the channel, which is used to serialize all the * callback methods on the {@link LoadBalancer2} interface, thus the balancer implementation doesn't * need to worry about synchronization among them. However, the actual thread of the Channel - * Executor is typically the network thread, thus following rules must be followed to prevent + * Executor is typically the network thread, thus the following rules must be followed to prevent * blocking or even dead-locking in a network * *

      @@ -79,13 +80,14 @@ import javax.annotation.concurrent.ThreadSafe; * *
    1. Avoid calling into other components with lock held. Channel Executor may * run callbacks 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}) while calling - * into another class that may involve locks, be cautious of deadlock. Generally you wouldn't - * need any locking in the LoadBalancer.
    2. + * 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. * *
    * - *

    {@link Helper#runSerialized} allows you to schedule a task to be run in the Channel Executor. + *

    {@link Helper#runSerialized Helper.runSerialized()} allows you to schedule a task to be run in + * the Channel Executor. * *

    The canonical implementation pattern

    * @@ -93,15 +95,16 @@ import javax.annotation.concurrent.ThreadSafe; * Subchannel(s) and their latest connectivity states. These states are mutated within the Channel * Executor. * - *

    A typical {@link 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. + *

    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. + * 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 @@ -133,6 +136,20 @@ public abstract class LoadBalancer2 { /** * 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#updatePicker Helper.updatePicker()}. 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, SHUTDOWN can be + * safely ignored. + * * @param subchannel the involved Subchannel * @param stateInfo the new state */ @@ -162,13 +179,25 @@ public abstract class LoadBalancer2 { } /** - * A balancing decision made by {@link SubchannelPicker} for an RPC. + * A balancing decision made by {@link SubchannelPicker SubchannelPicker} for an RPC. + * + *

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

    */ @Immutable public static final class PickResult { private static final PickResult NO_RESULT = new PickResult(null, Status.OK); - // A READY channel, or null @Nullable private final Subchannel subchannel; // An error to be propagated to the application if subchannel == null // Or OK if there is no error. @@ -181,12 +210,64 @@ public abstract class LoadBalancer2 { } /** - * A decision to proceed the RPC on a Subchannel. The state of the Subchannel is supposed to be - * {@link ConnectivityState#READY}. However, since such decisions are racy, a non-READY - * Subchannel will not fail the RPC, but will only leave it buffered. + * A decision to proceed the RPC on a Subchannel. * - *

    Only Subchannels returned by {@link Helper#createSubchannel} will work. DO NOT try to - * use your own implementations of Subchannels, as they won't work. + *

    Only Subchannels returned by {@link Helper#createSubchannel Helper.createSubchannel()} + * will work. DO NOT try to use your own implementations of Subchannels, as they won't work. + * + *

    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: + * + *

    + * + *

    All buffered RPCs will stay buffered until the next call of {@link + * Helper#updatePicker Helper.updatePicker()}, 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. + *

    + * + *

    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: + *
    2. + *
    3. Always create a new picker and call {@link Helper#updatePicker Helper.updatePicker()} + * whenever {@link #handleSubchannelState handleSubchannelState()} is called, unless the + * new state is SHUTDOWN. See {@code handleSubchannelState}'s javadoc for more + * details.
    4. + *
    */ public static PickResult withSubchannel(Subchannel subchannel) { return new PickResult(Preconditions.checkNotNull(subchannel, "subchannel"), Status.OK); @@ -212,7 +293,8 @@ public abstract class LoadBalancer2 { } /** - * The Subchannel if this result was created by {@link #withSubchannel}, or null otherwise. + * The Subchannel if this result was created by {@link #withSubchannel withSubchannel()}, or + * null otherwise. */ @Nullable public Subchannel getSubchannel() { @@ -220,8 +302,8 @@ public abstract class LoadBalancer2 { } /** - * The status associated with this result. Non-{@code OK} if created with {@link #withError}, - * or {@code OK} otherwise. + * The status associated with this result. Non-{@code OK} if created with {@link #withError + * withError}, or {@code OK} otherwise. */ public Status getStatus() { return status; @@ -241,7 +323,8 @@ public abstract class LoadBalancer2 { /** * 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, 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}. @@ -261,11 +344,12 @@ public abstract class LoadBalancer2 { /** * Set a new picker to the channel. * - *

    When a new picker is provided via {@link Helper#updatePicker}, the channel will apply the - * picker on all buffered RPCs, by calling {@link SubchannelPicker#pickSubchannel}. + *

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

    The channel will hold the picker and use it for all RPCs, until {@link #updatePicker} is - * called again and a new picker replaces the old one. If {@link #updatePicker} has never been + *

    The channel will hold the picker and use it for all RPCs, until {@code updatePicker()} is + * called again and a new picker replaces the old one. If {@code updatePicker()} has never been * called, the channel will buffer all RPCs until a picker is provided. */ public abstract void updatePicker(SubchannelPicker picker); @@ -288,7 +372,7 @@ public abstract class LoadBalancer2 { } /** - * A logical connection to a server, or a group of equivalent servers represented by an {@link + * 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 @@ -296,12 +380,14 @@ public abstract class LoadBalancer2 { * *

    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} can be used to ask Subchannel to create a transport if there isn't any. + * #requestConnection requestConnection()} can be used to ask Subchannel to create a transport if + * there isn't any. */ @ThreadSafe public abstract static class Subchannel { /** - * Shuts down the Subchannel. No new RPCs will be accepted. + * 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. */ public abstract void shutdown(); @@ -316,7 +402,7 @@ public abstract class LoadBalancer2 { public abstract EquivalentAddressGroup getAddresses(); /** - * The same attributes passed to {@link io.grpc.LoadBalancer2.Helper#createSubchannel}. + * 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. */ @@ -326,7 +412,7 @@ public abstract class LoadBalancer2 { @ThreadSafe public abstract static class Factory { /** - * Creates a {@link LoadBalancer} that will be used inside a channel. + * Creates a {@link LoadBalancer2} that will be used inside a channel. */ public abstract LoadBalancer2 newLoadBalancer(Helper helper); }