Skip to content

Commit 510cdcf

Browse files
authored
Flakiness in LB2SolrClientTest.testTwoServers (#3937)
1 parent 31ac7ae commit 510cdcf

2 files changed

Lines changed: 98 additions & 3 deletions

File tree

solr/solrj/src/java/org/apache/solr/common/util/RetryUtil.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,67 @@
2525
import org.slf4j.Logger;
2626
import org.slf4j.LoggerFactory;
2727

28+
/** Utility class for retrying operations with various strategies. */
2829
public class RetryUtil {
2930
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
3031

32+
/**
33+
* Interface for commands that can be retried and may throw exceptions.
34+
*
35+
* <p>Implementations should define the operation to be executed in the {@link #execute()} method.
36+
*/
3137
public interface RetryCmd {
3238
void execute() throws Exception;
3339
}
3440

41+
/**
42+
* Interface for commands that return a boolean result indicating success or failure.
43+
*
44+
* <p>Implementations should return {@code true} when the operation succeeds, or {@code false}
45+
* when it should be retried.
46+
*/
3547
public interface BooleanRetryCmd {
3648
boolean execute();
3749
}
3850

51+
/**
52+
* Retries a command when a specific exception type occurs, until successful or timeout is
53+
* reached.
54+
*
55+
* <p>This is a convenience method that delegates to {@link #retryOnException(Set, long, long,
56+
* RetryCmd)} with a singleton set containing the specified exception class.
57+
*
58+
* @param clazz the exception class to retry on
59+
* @param timeoutms maximum time to retry in milliseconds
60+
* @param intervalms wait interval between retries in milliseconds
61+
* @param cmd the command to execute
62+
* @throws Exception if the command fails with a different exception, or if timeout is reached
63+
* @throws InterruptedException if the thread is interrupted while sleeping between retries
64+
*/
3965
public static void retryOnException(
4066
Class<? extends Exception> clazz, long timeoutms, long intervalms, RetryCmd cmd)
4167
throws Exception {
4268
retryOnException(Collections.singleton(clazz), timeoutms, intervalms, cmd);
4369
}
4470

71+
/**
72+
* Retries a command when any of the specified exception types occur, until successful or timeout
73+
* is reached.
74+
*
75+
* <p>The command is executed repeatedly until it succeeds (completes without throwing an
76+
* exception) or the timeout is reached. If an exception matching one of the specified classes is
77+
* thrown and there is time remaining, the method will sleep for {@code intervalms} milliseconds
78+
* before retrying. If a different exception is thrown, or if the timeout is reached, the
79+
* exception is rethrown.
80+
*
81+
* @param classes set of exception classes to retry on
82+
* @param timeoutms maximum time to retry in milliseconds
83+
* @param intervalms wait interval between retries in milliseconds
84+
* @param cmd the command to execute
85+
* @throws Exception if the command fails with an exception not in the specified set, or if
86+
* timeout is reached
87+
* @throws InterruptedException if the thread is interrupted while sleeping between retries
88+
*/
4589
public static void retryOnException(
4690
Set<Class<? extends Exception>> classes, long timeoutms, long intervalms, RetryCmd cmd)
4791
throws Exception {
@@ -74,6 +118,22 @@ private static boolean isInstanceOf(Set<Class<? extends Exception>> classes, Thr
74118
return false;
75119
}
76120

121+
/**
122+
* Retries a command until it returns {@code true} or the maximum number of retries is reached.
123+
*
124+
* <p>The command is executed up to {@code retries} times. After each failed attempt (when the
125+
* command returns {@code false}), the method pauses for the specified duration before retrying.
126+
* If all retries are exhausted without success, a {@link SolrException} is thrown with the
127+
* provided error message.
128+
*
129+
* @param errorMessage the error message to use if all retries are exhausted
130+
* @param retries maximum number of retry attempts
131+
* @param pauseTime duration to pause between retries
132+
* @param pauseUnit time unit for the pause duration
133+
* @param cmd the command to execute
134+
* @throws InterruptedException if the thread is interrupted while sleeping between retries
135+
* @throws SolrException if all retries are exhausted without success
136+
*/
77137
public static void retryUntil(
78138
String errorMessage, int retries, long pauseTime, TimeUnit pauseUnit, BooleanRetryCmd cmd)
79139
throws InterruptedException {
@@ -84,12 +144,32 @@ public static void retryUntil(
84144
throw new SolrException(ErrorCode.SERVER_ERROR, errorMessage);
85145
}
86146

147+
/**
148+
* Retries a command until it returns {@code true} or the timeout is reached.
149+
*
150+
* <p>The command is executed repeatedly until it returns {@code true} or the timeout expires. If
151+
* the command returns {@code false} and there is time remaining, the method sleeps for {@code
152+
* intervalms} milliseconds before retrying. If the timeout is reached before the command
153+
* succeeds, a {@link SolrException} is thrown.
154+
*
155+
* @param timeoutms maximum time to retry in milliseconds
156+
* @param intervalms wait interval between retries in milliseconds
157+
* @param cmd the command to execute
158+
* @throws SolrException if no success within timeout
159+
*/
87160
public static void retryOnBoolean(long timeoutms, long intervalms, BooleanRetryCmd cmd) {
88161
long timeout =
89162
System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeoutms, TimeUnit.MILLISECONDS);
90163
while (true) {
91164
boolean resp = cmd.execute();
92165
if (!resp && System.nanoTime() < timeout) {
166+
try {
167+
//noinspection BusyWait
168+
Thread.sleep(intervalms);
169+
} catch (InterruptedException e) {
170+
Thread.currentThread().interrupt();
171+
throw new SolrException(ErrorCode.SERVER_ERROR, "Interrupted while retrying operation");
172+
}
93173
continue;
94174
} else if (System.nanoTime() >= timeout) {
95175
throw new SolrException(ErrorCode.SERVER_ERROR, "Timed out while retrying operation");

solr/solrj/src/test/org/apache/solr/client/solrj/impl/LB2SolrClientTest.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.solr.client.solrj.response.QueryResponse;
3636
import org.apache.solr.client.solrj.response.SolrResponseBase;
3737
import org.apache.solr.common.SolrInputDocument;
38+
import org.apache.solr.common.util.RetryUtil;
3839
import org.apache.solr.embedded.JettyConfig;
3940
import org.apache.solr.embedded.JettySolrRunner;
4041
import org.apache.solr.util.LogLevel;
@@ -196,9 +197,23 @@ public void testTwoServers() throws Exception {
196197
solr[1].jetty = null;
197198
startJettyAndWaitForAliveCheckQuery(solr[0]);
198199

199-
resp = h.lbClient.query(solrQuery);
200-
name = resp.getResults().get(0).getFieldValue("name").toString();
201-
assertEquals("solr/collection10", name);
200+
RetryUtil.retryOnBoolean(
201+
5000,
202+
500,
203+
() -> {
204+
try {
205+
return ("solr/collection10"
206+
.equals(
207+
h.lbClient
208+
.query(solrQuery)
209+
.getResults()
210+
.get(0)
211+
.getFieldValue("name")
212+
.toString()));
213+
} catch (Exception e) {
214+
return false;
215+
}
216+
});
202217
}
203218
}
204219

0 commit comments

Comments
 (0)