2424
2525import 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 ;
4340
4441/**
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 ();
5249
5350 public RoundRobinLoadBalancer (Helper helper ) {
5451 super (helper );
55- this .random = new Random ();
5652 }
5753
5854 @ Override
5955 protected SubchannelPicker getSubchannelPicker (Map <Object , SubchannelPicker > childPickers ) {
6056 throw new UnsupportedOperationException (); // local updateOverallBalancingState doesn't use this
6157 }
6258
63- 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 }
8377
8478 if (isConnecting ) {
85- updateBalancingState (CONNECTING , new EmptyPicker (Status . OK ));
79+ updateBalancingState (CONNECTING , new EmptyPicker ());
8680 } else {
8781 updateBalancingState (TRANSIENT_FAILURE , createReadyPicker (getChildLbStates ()));
8882 }
@@ -91,45 +85,45 @@ protected void updateOverallBalancingState() {
9185 }
9286 }
9387
94- 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 )) {
9690 getHelper ().updateBalancingState (state , picker );
9791 currentConnectivityState = state ;
9892 currentPicker = picker ;
9993 }
10094 }
10195
102- 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 ) {
10797 List <SubchannelPicker > pickerList = new ArrayList <>();
10898 for (ChildLbState child : children ) {
10999 SubchannelPicker picker = child .getCurrentPicker ();
110100 pickerList .add (picker );
111101 }
112102
113- 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 }
119105
120106 @ 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 {
125108 private final List <SubchannelPicker > subchannelPickers ; // non-empty
126- @ SuppressWarnings ( "unused" )
127- private volatile int index ;
109+ private final AtomicInteger index ;
110+ private final int hashCode ;
128111
129- public ReadyPicker (List <SubchannelPicker > list , int startIndex ) {
112+ public ReadyPicker (List <SubchannelPicker > list , AtomicInteger index ) {
130113 checkArgument (!list .isEmpty (), "empty list" );
131114 this .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 }
134128
135129 @ Override
@@ -145,14 +139,8 @@ public String toString() {
145139 }
146140
147141 private 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 }
157145
158146 @ VisibleForTesting
@@ -161,53 +149,42 @@ List<SubchannelPicker> getSubchannelPickers() {
161149 }
162150
163151 @ 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 )) {
166159 return 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 }
175172
176173 @ 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
186176 public PickResult pickSubchannel (PickSubchannelArgs args ) {
187- return status . isOk () ? PickResult .withNoResult () : PickResult . withError ( status );
177+ return PickResult .withNoResult ();
188178 }
189179
190180 @ 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 }
195184
196185 @ 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}
0 commit comments