util: Pass an AtomicInteger to RR's ReadyPicker · grpc/grpc-java@dca89b2
@@ -24,44 +24,38 @@
24242525import com.google.common.annotations.VisibleForTesting;
2626import com.google.common.base.MoreObjects;
27-import com.google.common.base.Objects;
2827import com.google.common.base.Preconditions;
2928import io.grpc.ConnectivityState;
3029import io.grpc.EquivalentAddressGroup;
3130import io.grpc.Internal;
3231import io.grpc.LoadBalancer;
3332import io.grpc.NameResolver;
34-import io.grpc.Status;
3533import java.util.ArrayList;
3634import java.util.Collection;
3735import java.util.HashSet;
3836import java.util.List;
3937import java.util.Map;
4038import java.util.Random;
41-import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
42-import javax.annotation.Nonnull;
39+import java.util.concurrent.atomic.AtomicInteger;
43404441/**
4542 * A {@link LoadBalancer} that provides round-robin load-balancing over the {@link
4643 * EquivalentAddressGroup}s from the {@link NameResolver}.
4744 */
4845@Internal
4946public class RoundRobinLoadBalancer extends MultiChildLoadBalancer {
50-private final Random random;
51-protected RoundRobinPicker currentPicker = new EmptyPicker(EMPTY_OK);
47+private final AtomicInteger sequence = new AtomicInteger(new Random().nextInt());
48+protected SubchannelPicker currentPicker = new EmptyPicker();
52495350public RoundRobinLoadBalancer(Helper helper) {
5451super(helper);
55-this.random = new Random();
5652 }
57535854@Override
5955protected SubchannelPicker getSubchannelPicker(Map<Object, SubchannelPicker> childPickers) {
6056throw new UnsupportedOperationException(); // local updateOverallBalancingState doesn't use this
6157 }
625863-private static final Status EMPTY_OK = Status.OK.withDescription("no subchannels ready");
64-6559/**
6660 * Updates picker with the list of active subchannels (state == READY).
6761 */
@@ -82,7 +76,7 @@ protected void updateOverallBalancingState() {
8276 }
83778478if (isConnecting) {
85-updateBalancingState(CONNECTING, new EmptyPicker(Status.OK));
79+updateBalancingState(CONNECTING, new EmptyPicker());
8680 } else {
8781updateBalancingState(TRANSIENT_FAILURE, createReadyPicker(getChildLbStates()));
8882 }
@@ -91,45 +85,45 @@ protected void updateOverallBalancingState() {
9185 }
9286 }
938794-private void updateBalancingState(ConnectivityState state, RoundRobinPicker picker) {
95-if (state != currentConnectivityState || !picker.isEquivalentTo(currentPicker)) {
88+private void updateBalancingState(ConnectivityState state, SubchannelPicker picker) {
89+if (state != currentConnectivityState || !picker.equals(currentPicker)) {
9690getHelper().updateBalancingState(state, picker);
9791currentConnectivityState = state;
9892currentPicker = picker;
9993 }
10094 }
10195102-protected RoundRobinPicker createReadyPicker(Collection<ChildLbState> children) {
103-// initialize the Picker to a random start index to ensure that a high frequency of Picker
104-// churn does not skew subchannel selection.
105-int startIndex = random.nextInt(children.size());
106-96+protected SubchannelPicker createReadyPicker(Collection<ChildLbState> children) {
10797List<SubchannelPicker> pickerList = new ArrayList<>();
10898for (ChildLbState child : children) {
10999SubchannelPicker picker = child.getCurrentPicker();
110100pickerList.add(picker);
111101 }
112102113-return new ReadyPicker(pickerList, startIndex);
114- }
115-116-public abstract static class RoundRobinPicker extends SubchannelPicker {
117-public abstract boolean isEquivalentTo(RoundRobinPicker picker);
103+return new ReadyPicker(pickerList, sequence);
118104 }
119105120106@VisibleForTesting
121-static class ReadyPicker extends RoundRobinPicker {
122-private static final AtomicIntegerFieldUpdater<ReadyPicker> indexUpdater =
123-AtomicIntegerFieldUpdater.newUpdater(ReadyPicker.class, "index");
124-107+static class ReadyPicker extends SubchannelPicker {
125108private final List<SubchannelPicker> subchannelPickers; // non-empty
126-@SuppressWarnings("unused")
127-private volatile int index;
109+private final AtomicInteger index;
110+private final int hashCode;
128111129-public ReadyPicker(List<SubchannelPicker> list, int startIndex) {
112+public ReadyPicker(List<SubchannelPicker> list, AtomicInteger index) {
130113checkArgument(!list.isEmpty(), "empty list");
131114this.subchannelPickers = list;
132-this.index = startIndex - 1;
115+this.index = Preconditions.checkNotNull(index, "index");
116+117+// Every created picker is checked for equality in updateBalancingState() at least once.
118+// Pre-compute the hash so it can be checked cheaply. Using the hash in equals() makes it very
119+// fast except when the pickers are (very likely) equal.
120+//
121+// For equality we treat children as a set; use hash code as defined by Set
122+int sum = 0;
123+for (SubchannelPicker picker : subchannelPickers) {
124+sum += picker.hashCode();
125+ }
126+this.hashCode = sum;
133127 }
134128135129@Override
@@ -145,14 +139,8 @@ public String toString() {
145139 }
146140147141private int nextIndex() {
148-int size = subchannelPickers.size();
149-int i = indexUpdater.incrementAndGet(this);
150-if (i >= size) {
151-int oldi = i;
152-i %= size;
153-indexUpdater.compareAndSet(this, oldi, i);
154- }
155-return i;
142+int i = index.getAndIncrement() & Integer.MAX_VALUE;
143+return i % subchannelPickers.size();
156144 }
157145158146@VisibleForTesting
@@ -161,53 +149,42 @@ List<SubchannelPicker> getSubchannelPickers() {
161149 }
162150163151@Override
164-public boolean isEquivalentTo(RoundRobinPicker picker) {
165-if (!(picker instanceof ReadyPicker)) {
152+public int hashCode() {
153+return hashCode;
154+ }
155+156+@Override
157+public boolean equals(Object o) {
158+if (!(o instanceof ReadyPicker)) {
166159return false;
167160 }
168-ReadyPicker other = (ReadyPicker) picker;
161+ReadyPicker other = (ReadyPicker) o;
162+if (other == this) {
163+return true;
164+ }
169165// the lists cannot contain duplicate subchannels
170-return other == this
171- || (subchannelPickers.size() == other.subchannelPickers.size() && new HashSet<>(
172-subchannelPickers).containsAll(other.subchannelPickers));
166+return hashCode == other.hashCode
167+&& index == other.index
168+&& subchannelPickers.size() == other.subchannelPickers.size()
169+&& new HashSet<>(subchannelPickers).containsAll(other.subchannelPickers);
173170 }
174171 }
175172176173@VisibleForTesting
177-static final class EmptyPicker extends RoundRobinPicker {
178-179-private final Status status;
180-181-EmptyPicker(@Nonnull Status status) {
182-this.status = Preconditions.checkNotNull(status, "status");
183- }
184-174+static final class EmptyPicker extends SubchannelPicker {
185175@Override
186176public PickResult pickSubchannel(PickSubchannelArgs args) {
187-return status.isOk() ? PickResult.withNoResult() : PickResult.withError(status);
177+return PickResult.withNoResult();
188178 }
189179190180@Override
191-public boolean isEquivalentTo(RoundRobinPicker picker) {
192-return picker instanceof EmptyPicker && (Objects.equal(status, ((EmptyPicker) picker).status)
193- || (status.isOk() && ((EmptyPicker) picker).status.isOk()));
181+public int hashCode() {
182+return getClass().hashCode();
194183 }
195184196185@Override
197-public String toString() {
198-return MoreObjects.toStringHelper(EmptyPicker.class).add("status", status).toString();
199- }
200- }
201-202-/**
203- * A lighter weight Reference than AtomicReference.
204- */
205-@VisibleForTesting
206-static final class Ref<T> {
207-T value;
208-209-Ref(T value) {
210-this.value = value;
186+public boolean equals(Object o) {
187+return o instanceof EmptyPicker;
211188 }
212189 }
213190}