Skip to content

Commit 8be6691

Browse files
committed
Polishing.
Simplify tests. Reuse existing interfaces from Spring. Remove inappropriate nullability annotations and introduce annotations where required. Replace Future mocking with easier to maintain and to read future method overrides. Remove superfluous code and replace with infrastructure classes provided by Spring Framework. Consistently name callbacks. Make exception collector concept explicit. Reformat code. See #2518 Original pull request: #2719
1 parent 781fda6 commit 8be6691

File tree

3 files changed

+242
-542
lines changed

3 files changed

+242
-542
lines changed

src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java

+85-84
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,13 @@
1515
*/
1616
package org.springframework.data.redis.connection;
1717

18-
import java.util.ArrayList;
19-
import java.util.Arrays;
20-
import java.util.Collection;
21-
import java.util.Collections;
22-
import java.util.Comparator;
23-
import java.util.HashMap;
24-
import java.util.HashSet;
25-
import java.util.Iterator;
26-
import java.util.LinkedHashMap;
27-
import java.util.List;
28-
import java.util.Map;
18+
import java.util.*;
2919
import java.util.Map.Entry;
30-
import java.util.Objects;
31-
import java.util.Random;
32-
import java.util.Set;
33-
import java.util.TreeMap;
3420
import java.util.concurrent.Callable;
3521
import java.util.concurrent.ExecutionException;
3622
import java.util.concurrent.Future;
3723
import java.util.concurrent.TimeUnit;
3824
import java.util.concurrent.TimeoutException;
39-
import java.util.function.BiConsumer;
4025
import java.util.function.Function;
4126
import java.util.stream.Collectors;
4227

@@ -111,47 +96,46 @@ public ClusterCommandExecutor(ClusterTopologyProvider topologyProvider, ClusterN
11196
/**
11297
* Run {@link ClusterCommandCallback} on a random node.
11398
*
114-
* @param clusterCommand must not be {@literal null}.
99+
* @param commandCallback must not be {@literal null}.
115100
* @return never {@literal null}.
116101
*/
117-
public <T> NodeResult<T> executeCommandOnArbitraryNode(ClusterCommandCallback<?, T> clusterCommand) {
102+
public <T> NodeResult<T> executeCommandOnArbitraryNode(ClusterCommandCallback<?, T> commandCallback) {
118103

119-
Assert.notNull(clusterCommand, "ClusterCommandCallback must not be null");
104+
Assert.notNull(commandCallback, "ClusterCommandCallback must not be null");
120105

121106
List<RedisClusterNode> nodes = new ArrayList<>(getClusterTopology().getActiveNodes());
122107

123108
RedisClusterNode arbitraryNode = nodes.get(new Random().nextInt(nodes.size()));
124109

125-
return executeCommandOnSingleNode(clusterCommand, arbitraryNode);
110+
return executeCommandOnSingleNode(commandCallback, arbitraryNode);
126111
}
127112

128113
/**
129114
* Run {@link ClusterCommandCallback} on given {@link RedisClusterNode}.
130115
*
131-
* @param clusterCommand must not be {@literal null}.
116+
* @param commandCallback must not be {@literal null}.
132117
* @param node must not be {@literal null}.
133118
* @return the {@link NodeResult} from the single, targeted {@link RedisClusterNode}.
134119
* @throws IllegalArgumentException in case no resource can be acquired for given node.
135120
*/
136-
public <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S, T> clusterCommand,
121+
public <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S, T> commandCallback,
137122
RedisClusterNode node) {
138123

139-
return executeCommandOnSingleNode(clusterCommand, node, 0);
124+
return executeCommandOnSingleNode(commandCallback, node, 0);
140125
}
141126

142-
private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S, T> clusterCommand,
127+
private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S, T> commandCallback,
143128
RedisClusterNode node, int redirectCount) {
144129

145-
Assert.notNull(clusterCommand, "ClusterCommandCallback must not be null");
130+
Assert.notNull(commandCallback, "ClusterCommandCallback must not be null");
146131
Assert.notNull(node, "RedisClusterNode must not be null");
147132

148133
if (redirectCount > this.maxRedirects) {
149134

150-
String message = String.format("Cannot follow Cluster Redirects over more than %s legs;"
151-
+ " Please consider increasing the number of redirects to follow; Current value is: %s.",
152-
redirectCount, this.maxRedirects);
153-
154-
throw new TooManyClusterRedirectionsException(message);
135+
throw new TooManyClusterRedirectionsException(String.format(
136+
"Cannot follow Cluster Redirects over more than %s legs; "
137+
+ "Consider increasing the number of redirects to follow; Current value is: %s.",
138+
redirectCount, this.maxRedirects));
155139
}
156140

157141
RedisClusterNode nodeToUse = lookupNode(node);
@@ -161,15 +145,14 @@ private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S
161145
Assert.notNull(client, "Could not acquire resource for node; Is your cluster info up to date");
162146

163147
try {
164-
return new NodeResult<>(node, clusterCommand.doInCluster(client));
148+
return new NodeResult<>(node, commandCallback.doInCluster(client));
165149
} catch (RuntimeException cause) {
166150

167151
RuntimeException translatedException = convertToDataAccessException(cause);
168152

169153
if (translatedException instanceof ClusterRedirectException clusterRedirectException) {
170-
return executeCommandOnSingleNode(clusterCommand, topologyProvider.getTopology()
171-
.lookup(clusterRedirectException.getTargetHost(), clusterRedirectException.getTargetPort()),
172-
redirectCount + 1);
154+
return executeCommandOnSingleNode(commandCallback, topologyProvider.getTopology().lookup(
155+
clusterRedirectException.getTargetHost(), clusterRedirectException.getTargetPort()), redirectCount + 1);
173156
} else {
174157
throw translatedException != null ? translatedException : cause;
175158
}
@@ -182,7 +165,8 @@ private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S
182165
* Lookup {@link RedisClusterNode node} from the {@link ClusterTopology topology}.
183166
*
184167
* @param node {@link RedisClusterNode node} to lookup; must not be {@literal null}.
185-
* @return the resolved {@link RedisClusterNode node} from the {@link ClusterTopology topology}; never {@literal null}.
168+
* @return the resolved {@link RedisClusterNode node} from the {@link ClusterTopology topology}; never
169+
* {@literal null}.
186170
* @throws IllegalArgumentException in case the node could not be resolved to a topology-known node
187171
*/
188172
private RedisClusterNode lookupNode(RedisClusterNode node) {
@@ -197,27 +181,27 @@ private RedisClusterNode lookupNode(RedisClusterNode node) {
197181
/**
198182
* Run {@link ClusterCommandCallback} on all reachable master nodes.
199183
*
200-
* @param clusterCommand must not be {@literal null}.
184+
* @param commandCallback must not be {@literal null}.
201185
* @return never {@literal null}.
202186
* @throws ClusterCommandExecutionFailureException if a failure occurs while executing the given
203187
* {@link ClusterCommandCallback command} on any given {@link RedisClusterNode node}.
204188
*/
205-
public <S, T> MultiNodeResult<T> executeCommandOnAllNodes(ClusterCommandCallback<S, T> clusterCommand) {
206-
return executeCommandAsyncOnNodes(clusterCommand, getClusterTopology().getActiveMasterNodes());
189+
public <S, T> MultiNodeResult<T> executeCommandOnAllNodes(ClusterCommandCallback<S, T> commandCallback) {
190+
return executeCommandAsyncOnNodes(commandCallback, getClusterTopology().getActiveMasterNodes());
207191
}
208192

209193
/**
210-
* @param clusterCommand must not be {@literal null}.
194+
* @param commandCallback must not be {@literal null}.
211195
* @param nodes must not be {@literal null}.
212196
* @return never {@literal null}.
213197
* @throws ClusterCommandExecutionFailureException if a failure occurs while executing the given
214198
* {@link ClusterCommandCallback command} on any given {@link RedisClusterNode node}.
215199
* @throws IllegalArgumentException in case the node could not be resolved to a topology-known node
216200
*/
217-
public <S, T> MultiNodeResult<T> executeCommandAsyncOnNodes(ClusterCommandCallback<S, T> clusterCommand,
201+
public <S, T> MultiNodeResult<T> executeCommandAsyncOnNodes(ClusterCommandCallback<S, T> commandCallback,
218202
Iterable<RedisClusterNode> nodes) {
219203

220-
Assert.notNull(clusterCommand, "Callback must not be null");
204+
Assert.notNull(commandCallback, "Callback must not be null");
221205
Assert.notNull(nodes, "Nodes must not be null");
222206

223207
ClusterTopology topology = this.topologyProvider.getTopology();
@@ -234,7 +218,7 @@ public <S, T> MultiNodeResult<T> executeCommandAsyncOnNodes(ClusterCommandCallba
234218
Map<NodeExecution, Future<NodeResult<T>>> futures = new LinkedHashMap<>();
235219

236220
for (RedisClusterNode node : resolvedRedisClusterNodes) {
237-
Callable<NodeResult<T>> nodeCommandExecution = () -> executeCommandOnSingleNode(clusterCommand, node);
221+
Callable<NodeResult<T>> nodeCommandExecution = () -> executeCommandOnSingleNode(commandCallback, node);
238222
futures.put(new NodeExecution(node), executor.submit(nodeCommandExecution));
239223
}
240224

@@ -243,26 +227,22 @@ public <S, T> MultiNodeResult<T> executeCommandAsyncOnNodes(ClusterCommandCallba
243227

244228
<T> MultiNodeResult<T> collectResults(Map<NodeExecution, Future<NodeResult<T>>> futures) {
245229

246-
Map<RedisClusterNode, Throwable> exceptions = new HashMap<>();
230+
NodeExceptionCollector exceptionCollector = new NodeExceptionCollector();
247231
MultiNodeResult<T> result = new MultiNodeResult<>();
248-
Set<String> safeguard = new HashSet<>();
232+
Object placeholder = new Object();
233+
Map<Future<NodeResult<T>>, Object> safeguard = new IdentityHashMap<>();
249234

250-
BiConsumer<NodeExecution, Throwable> exceptionHandler = getExceptionHandlerFunction(exceptions);
251-
252-
boolean done = false;
253-
254-
while (!done) {
255-
256-
done = true;
235+
for (;;) {
257236

237+
boolean timeout = false;
258238
for (Map.Entry<NodeExecution, Future<NodeResult<T>>> entry : futures.entrySet()) {
259239

260240
NodeExecution nodeExecution = entry.getKey();
261241
Future<NodeResult<T>> futureNodeResult = entry.getValue();
262-
String futureId = ObjectUtils.getIdentityHexString(futureNodeResult);
263242

264243
try {
265-
if (!safeguard.contains(futureId)) {
244+
245+
if (!safeguard.containsKey(futureNodeResult)) {
266246

267247
NodeResult<T> nodeResult = futureNodeResult.get(10L, TimeUnit.MICROSECONDS);
268248

@@ -272,39 +252,32 @@ <T> MultiNodeResult<T> collectResults(Map<NodeExecution, Future<NodeResult<T>>>
272252
result.add(nodeResult);
273253
}
274254

275-
safeguard.add(futureId);
255+
safeguard.put(futureNodeResult, placeholder);
276256
}
277257
} catch (ExecutionException exception) {
278-
safeguard.add(futureId);
279-
exceptionHandler.accept(nodeExecution, exception.getCause());
258+
safeguard.put(futureNodeResult, placeholder);
259+
exceptionCollector.addException(nodeExecution, exception.getCause());
280260
} catch (TimeoutException ignore) {
281-
done = false;
282-
} catch (InterruptedException cause) {
261+
timeout = true;
262+
} catch (InterruptedException exception) {
283263
Thread.currentThread().interrupt();
284-
exceptionHandler.accept(nodeExecution, cause);
264+
exceptionCollector.addException(nodeExecution, exception);
285265
break;
286266
}
287267
}
268+
269+
if (!timeout) {
270+
break;
271+
}
288272
}
289273

290-
if (!exceptions.isEmpty()) {
291-
throw new ClusterCommandExecutionFailureException(new ArrayList<>(exceptions.values()));
274+
if (exceptionCollector.hasExceptions()) {
275+
throw new ClusterCommandExecutionFailureException(exceptionCollector.getExceptions());
292276
}
293277

294278
return result;
295279
}
296280

297-
private BiConsumer<NodeExecution, Throwable> getExceptionHandlerFunction(Map<RedisClusterNode, Throwable> exceptions) {
298-
299-
return (nodeExecution, throwable) -> {
300-
301-
DataAccessException dataAccessException = convertToDataAccessException((Exception) throwable);
302-
Throwable resolvedException = dataAccessException != null ? dataAccessException : throwable;
303-
304-
exceptions.putIfAbsent(nodeExecution.getNode(), resolvedException);
305-
};
306-
}
307-
308281
/**
309282
* Run {@link MultiKeyClusterCommandCallback} with on a curated set of nodes serving one or more keys.
310283
*
@@ -331,8 +304,8 @@ public <S, T> MultiNodeResult<T> executeMultiKeyCommand(MultiKeyClusterCommandCa
331304

332305
if (entry.getKey().isMaster()) {
333306
for (PositionalKey key : entry.getValue()) {
334-
futures.put(new NodeExecution(entry.getKey(), key), this.executor.submit(() ->
335-
executeMultiKeyCommandOnSingleNode(commandCallback, entry.getKey(), key.getBytes())));
307+
futures.put(new NodeExecution(entry.getKey(), key), this.executor
308+
.submit(() -> executeMultiKeyCommandOnSingleNode(commandCallback, entry.getKey(), key.getBytes())));
336309
}
337310
}
338311
}
@@ -458,8 +431,8 @@ boolean isPositional() {
458431
}
459432

460433
/**
461-
* {@link NodeResult} encapsulates the actual {@link T value} returned by a {@link ClusterCommandCallback}
462-
* on a given {@link RedisClusterNode}.
434+
* {@link NodeResult} encapsulates the actual {@link T value} returned by a {@link ClusterCommandCallback} on a given
435+
* {@link RedisClusterNode}.
463436
*
464437
* @param <T> {@link Class Type} of the {@link Object value} returned in the result.
465438
* @author Christoph Strobl
@@ -468,9 +441,9 @@ boolean isPositional() {
468441
*/
469442
public static class NodeResult<T> {
470443

471-
private RedisClusterNode node;
472-
private ByteArrayWrapper key;
473-
private @Nullable T value;
444+
private final RedisClusterNode node;
445+
private final ByteArrayWrapper key;
446+
private final @Nullable T value;
474447

475448
/**
476449
* Create a new {@link NodeResult}.
@@ -551,9 +524,8 @@ public boolean equals(@Nullable Object obj) {
551524
return false;
552525
}
553526

554-
return ObjectUtils.nullSafeEquals(this.getNode(), that.getNode())
555-
&& Objects.equals(this.key, that.key)
556-
&& Objects.equals(this.getValue(), that.getValue());
527+
return ObjectUtils.nullSafeEquals(this.getNode(), that.getNode()) && Objects.equals(this.key, that.key)
528+
&& Objects.equals(this.getValue(), that.getValue());
557529
}
558530

559531
@Override
@@ -757,8 +729,7 @@ public boolean equals(@Nullable Object obj) {
757729
if (!(obj instanceof PositionalKey that))
758730
return false;
759731

760-
return this.getPosition() == that.getPosition()
761-
&& ObjectUtils.nullSafeEquals(this.getKey(), that.getKey());
732+
return this.getPosition() == that.getPosition() && ObjectUtils.nullSafeEquals(this.getKey(), that.getKey());
762733
}
763734

764735
@Override
@@ -836,4 +807,34 @@ public Iterator<PositionalKey> iterator() {
836807
return this.keys.iterator();
837808
}
838809
}
810+
811+
/**
812+
* Collector for exceptions. Applies translation of exceptions if possible.
813+
*/
814+
private class NodeExceptionCollector {
815+
816+
private final Map<RedisClusterNode, Throwable> exceptions = new HashMap<>();
817+
818+
/**
819+
* @return {@code true} if the collector contains at least one exception.
820+
*/
821+
public boolean hasExceptions() {
822+
return !exceptions.isEmpty();
823+
}
824+
825+
public void addException(NodeExecution execution, Throwable throwable) {
826+
827+
Throwable translated = throwable instanceof Exception e ? convertToDataAccessException(e) : throwable;
828+
Throwable resolvedException = translated != null ? translated : throwable;
829+
830+
exceptions.putIfAbsent(execution.getNode(), resolvedException);
831+
}
832+
833+
/**
834+
* @return the collected exceptions.
835+
*/
836+
public List<? extends Throwable> getExceptions() {
837+
return new ArrayList<>(exceptions.values());
838+
}
839+
}
839840
}

0 commit comments

Comments
 (0)