util: Pass an AtomicInteger to RR's ReadyPicker · grpc/grpc-java@dca89b2

@@ -24,44 +24,38 @@

24242525

import com.google.common.annotations.VisibleForTesting;

2626

import com.google.common.base.MoreObjects;

27-

import com.google.common.base.Objects;

2827

import com.google.common.base.Preconditions;

2928

import io.grpc.ConnectivityState;

3029

import io.grpc.EquivalentAddressGroup;

3130

import io.grpc.Internal;

3231

import io.grpc.LoadBalancer;

3332

import io.grpc.NameResolver;

34-

import io.grpc.Status;

3533

import java.util.ArrayList;

3634

import java.util.Collection;

3735

import java.util.HashSet;

3836

import java.util.List;

3937

import java.util.Map;

4038

import 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

4946

public 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();

52495350

public RoundRobinLoadBalancer(Helper helper) {

5451

super(helper);

55-

this.random = new Random();

5652

}

57535854

@Override

5955

protected SubchannelPicker getSubchannelPicker(Map<Object, SubchannelPicker> childPickers) {

6056

throw 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

}

83778478

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

}

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)) {

9690

getHelper().updateBalancingState(state, picker);

9791

currentConnectivityState = state;

9892

currentPicker = 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) {

10797

List<SubchannelPicker> pickerList = new ArrayList<>();

10898

for (ChildLbState child : children) {

10999

SubchannelPicker picker = child.getCurrentPicker();

110100

pickerList.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 {

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;

128111129-

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

}

134128135129

@Override

@@ -145,14 +139,8 @@ public String toString() {

145139

}

146140147141

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

}

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)) {

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

}

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

186176

public 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

}