Skip to content

Commit f8353a0

Browse files
authored
Merge branch 'trunk' into renovate/org.redisson-redisson-3.x
2 parents a3feef3 + d84fb38 commit f8353a0

File tree

4 files changed

+136
-84
lines changed

4 files changed

+136
-84
lines changed

.github/workflows/stale.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
stale-issue-message: 'This issue is stale because it has been open 280 days with no activity. Remove stale label or comment or this will be closed in 14 days.'
2222
close-issue-message: 'This issue was closed because it has been stalled for 14 days with no activity.'
2323
stale-issue-label: 'I-stale'
24-
days-before-stale: 280
24+
days-before-stale: 180
2525
days-before-close: 14
2626
- uses: actions/stale@v9
2727
with:

java/src/org/openqa/selenium/remote/http/BUILD.bazel

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
load("@rules_jvm_external//:defs.bzl", "artifact")
21
load("//java:defs.bzl", "java_export")
32
load("//java:version.bzl", "SE_VERSION")
43

@@ -20,6 +19,5 @@ java_export(
2019
"//java:auto-service",
2120
"//java/src/org/openqa/selenium:core",
2221
"//java/src/org/openqa/selenium/json",
23-
artifact("dev.failsafe:failsafe"),
2422
],
2523
)

java/src/org/openqa/selenium/remote/http/RetryRequest.java

Lines changed: 45 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,89 +17,65 @@
1717

1818
package org.openqa.selenium.remote.http;
1919

20-
import static java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT;
2120
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
2221
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
23-
import static org.openqa.selenium.internal.Debug.getDebugLogLevel;
24-
import static org.openqa.selenium.remote.http.Contents.asJson;
2522

26-
import dev.failsafe.Failsafe;
27-
import dev.failsafe.Fallback;
28-
import dev.failsafe.RetryPolicy;
29-
import dev.failsafe.event.ExecutionAttemptedEvent;
30-
import dev.failsafe.function.CheckedFunction;
3123
import java.net.ConnectException;
32-
import java.util.Map;
24+
import java.util.logging.Level;
3325
import java.util.logging.Logger;
26+
import org.openqa.selenium.internal.Debug;
3427

3528
public class RetryRequest implements Filter {
3629

3730
private static final Logger LOG = Logger.getLogger(RetryRequest.class.getName());
31+
private static final Level LOG_LEVEL = Debug.getDebugLogLevel();
3832

39-
private static final Fallback<HttpResponse> fallback =
40-
Fallback.of(
41-
(CheckedFunction<ExecutionAttemptedEvent<? extends HttpResponse>, ? extends HttpResponse>)
42-
RetryRequest::getFallback);
43-
44-
// Retry on connection error.
45-
private static final RetryPolicy<HttpResponse> connectionFailurePolicy =
46-
RetryPolicy.<HttpResponse>builder()
47-
.handleIf(failure -> failure.getCause() instanceof ConnectException)
48-
.withMaxRetries(3)
49-
.onRetry(
50-
e ->
51-
LOG.log(
52-
getDebugLogLevel(),
53-
"Connection failure #{0}. Retrying.",
54-
e.getAttemptCount()))
55-
.build();
56-
57-
// Retry if server is unavailable or an internal server error occurs without response body.
58-
private static final RetryPolicy<HttpResponse> serverErrorPolicy =
59-
RetryPolicy.<HttpResponse>builder()
60-
.handleResultIf(
61-
response ->
62-
response.getStatus() == HTTP_INTERNAL_ERROR
63-
&& Integer.parseInt((response).getHeader(HttpHeader.ContentLength.getName()))
64-
== 0)
65-
.handleResultIf(response -> (response).getStatus() == HTTP_UNAVAILABLE)
66-
.withMaxRetries(2)
67-
.onRetry(
68-
e ->
69-
LOG.log(
70-
getDebugLogLevel(),
71-
"Failure due to server error #{0}. Retrying.",
72-
e.getAttemptCount()))
73-
.build();
33+
private static final int RETRIES_ON_CONNECTION_FAILURE = 3;
34+
private static final int RETRIES_ON_SERVER_ERROR = 2;
35+
private static final int NEEDED_ATTEMPTS =
36+
Math.max(RETRIES_ON_CONNECTION_FAILURE, RETRIES_ON_SERVER_ERROR) + 1;
7437

7538
@Override
7639
public HttpHandler apply(HttpHandler next) {
77-
return req ->
78-
Failsafe.with(fallback)
79-
.compose(serverErrorPolicy)
80-
.compose(connectionFailurePolicy)
81-
.get(() -> next.execute(req));
82-
}
40+
return req -> {
41+
// start to preform the request in a loop, to allow retries
42+
for (int i = 0; i < NEEDED_ATTEMPTS; i++) {
43+
HttpResponse response;
44+
45+
try {
46+
response = next.execute(req);
47+
} catch (RuntimeException ex) {
48+
// detect a connection failure we would like to retry
49+
boolean isConnectionFailure = ex.getCause() instanceof ConnectException;
50+
51+
// must be a connection failure and check whether we have retries left for this
52+
if (isConnectionFailure && i < RETRIES_ON_CONNECTION_FAILURE) {
53+
LOG.log(LOG_LEVEL, "Retry #" + (i + 1) + " on ConnectException", ex);
54+
continue;
55+
}
8356

84-
private static HttpResponse getFallback(
85-
ExecutionAttemptedEvent<? extends HttpResponse> executionAttemptedEvent) throws Exception {
86-
if (executionAttemptedEvent.getLastException() != null) {
87-
Exception exception = (Exception) executionAttemptedEvent.getLastException();
88-
if (exception.getCause() instanceof ConnectException) {
89-
return new HttpResponse()
90-
.setStatus(HTTP_CLIENT_TIMEOUT)
91-
.setContent(asJson(Map.of("value", Map.of("message", "Connection failure"))));
92-
} else throw exception;
93-
} else if (executionAttemptedEvent.getLastResult() != null) {
94-
HttpResponse response = executionAttemptedEvent.getLastResult();
95-
if ((response.getStatus() == HTTP_INTERNAL_ERROR
96-
&& Integer.parseInt(response.getHeader(HttpHeader.ContentLength.getName())) == 0)
97-
|| response.getStatus() == HTTP_UNAVAILABLE) {
98-
return new HttpResponse()
99-
.setStatus(response.getStatus())
100-
.setContent(asJson(Map.of("value", Map.of("message", "Internal server error"))));
57+
// not a connection failure or retries exceeded, rethrow and let the caller handle this
58+
throw ex;
59+
}
60+
61+
// detect a server error we would like to retry
62+
boolean isServerError =
63+
(response.getStatus() == HTTP_INTERNAL_ERROR && response.getContent().length() == 0)
64+
|| response.getStatus() == HTTP_UNAVAILABLE;
65+
66+
// must be a server error and check whether we have retries left for this
67+
if (isServerError && i < RETRIES_ON_SERVER_ERROR) {
68+
LOG.log(LOG_LEVEL, "Retry #" + (i + 1) + " on ServerError: " + response.getStatus());
69+
continue;
70+
}
71+
72+
// not a server error or retries exceeded, return the result to the caller
73+
return response;
10174
}
102-
}
103-
return executionAttemptedEvent.getLastResult();
75+
76+
// This should not be reachable, we either retry or fail before. The only way to get here
77+
// is to set the static final int fields above to inconsistent values.
78+
throw new IllegalStateException("Effective unreachable code reached, check constants.");
79+
};
10480
}
10581
}

java/test/org/openqa/selenium/remote/http/RetryRequestTest.java

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package org.openqa.selenium.remote.http;
1919

20-
import static java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT;
2120
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
2221
import static java.net.HttpURLConnection.HTTP_OK;
2322
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
@@ -26,6 +25,9 @@
2625
import static org.openqa.selenium.remote.http.HttpMethod.GET;
2726

2827
import com.google.common.collect.ImmutableMap;
28+
import java.io.IOException;
29+
import java.io.UncheckedIOException;
30+
import java.net.ConnectException;
2931
import java.net.MalformedURLException;
3032
import java.net.URI;
3133
import java.time.Duration;
@@ -37,6 +39,7 @@
3739
import java.util.concurrent.Executors;
3840
import java.util.concurrent.Future;
3941
import java.util.concurrent.atomic.AtomicInteger;
42+
import java.util.concurrent.atomic.AtomicReference;
4043
import org.junit.jupiter.api.Assertions;
4144
import org.junit.jupiter.api.BeforeEach;
4245
import org.junit.jupiter.api.Test;
@@ -73,6 +76,29 @@ void canThrowUnexpectedException() {
7376
UnsupportedOperationException.class, () -> handler.execute(new HttpRequest(GET, "/")));
7477
}
7578

79+
@Test
80+
void noUnexpectedRetry() {
81+
AtomicInteger count = new AtomicInteger();
82+
HttpHandler handler =
83+
new RetryRequest()
84+
.andFinally(
85+
(HttpRequest request) -> {
86+
if (count.getAndIncrement() == 0) {
87+
throw new StackOverflowError("Testing");
88+
} else {
89+
throw new UncheckedIOException("More testing", new IOException());
90+
}
91+
});
92+
93+
Assertions.assertThrows(
94+
StackOverflowError.class, () -> handler.execute(new HttpRequest(GET, "/")));
95+
Assertions.assertEquals(1, count.get());
96+
97+
Assertions.assertThrows(
98+
UncheckedIOException.class, () -> handler.execute(new HttpRequest(GET, "/")));
99+
Assertions.assertEquals(2, count.get());
100+
}
101+
76102
@Test
77103
void canReturnAppropriateFallbackResponse() {
78104
HttpHandler handler1 =
@@ -106,17 +132,23 @@ void canReturnAppropriateFallbackResponseWithMultipleThreads()
106132
new RetryRequest()
107133
.andFinally((HttpRequest request) -> new HttpResponse().setStatus(HTTP_UNAVAILABLE));
108134

109-
ExecutorService executorService = Executors.newFixedThreadPool(2);
135+
ExecutorService executorService = Executors.newFixedThreadPool(3);
110136
List<Callable<HttpResponse>> tasks = new ArrayList<>();
111137

112-
tasks.add(() -> client.execute(connectionTimeoutRequest));
113-
tasks.add(() -> handler2.execute(new HttpRequest(GET, "/")));
138+
for (int i = 0; i < 1024; i++) {
139+
tasks.add(() -> client.execute(connectionTimeoutRequest));
140+
tasks.add(() -> handler2.execute(new HttpRequest(GET, "/")));
141+
}
114142

115143
List<Future<HttpResponse>> results = executorService.invokeAll(tasks);
116144

117-
Assertions.assertEquals(HTTP_CLIENT_TIMEOUT, results.get(0).get().getStatus());
145+
for (int i = 0; i < 1024; i++) {
146+
int offset = i * 2;
147+
Assertions.assertThrows(ExecutionException.class, () -> results.get(offset).get());
148+
Assertions.assertEquals(HTTP_UNAVAILABLE, results.get(offset + 1).get().getStatus());
149+
}
118150

119-
Assertions.assertEquals(HTTP_UNAVAILABLE, results.get(1).get().getStatus());
151+
executorService.shutdown();
120152
}
121153

122154
@Test
@@ -266,13 +298,59 @@ void shouldGetTheErrorResponseOnServerUnavailableError() {
266298

267299
@Test
268300
void shouldBeAbleToRetryARequestOnConnectionFailure() {
269-
AppServer server = new NettyAppServer(req -> new HttpResponse());
301+
AtomicInteger count = new AtomicInteger(0);
302+
HttpHandler handler =
303+
new RetryRequest()
304+
.andFinally(
305+
(HttpRequest request) -> {
306+
if (count.getAndIncrement() < 2) {
307+
throw new UncheckedIOException(new ConnectException());
308+
} else {
309+
return new HttpResponse();
310+
}
311+
});
270312

271-
URI uri = URI.create(server.whereIs("/"));
272-
HttpRequest request =
273-
new HttpRequest(GET, String.format(REQUEST_PATH, uri.getHost(), uri.getPort()));
313+
HttpRequest request = new HttpRequest(GET, "/");
314+
HttpResponse response = handler.execute(request);
274315

275-
HttpResponse response = client.execute(request);
276-
assertThat(response).extracting(HttpResponse::getStatus).isEqualTo(HTTP_CLIENT_TIMEOUT);
316+
assertThat(response).extracting(HttpResponse::getStatus).isEqualTo(HTTP_OK);
317+
assertThat(count.get()).isEqualTo(3);
318+
}
319+
320+
@Test
321+
void shouldRethrowOnConnectFailure() {
322+
AtomicInteger count = new AtomicInteger(0);
323+
AtomicReference<UncheckedIOException> lastThrown = new AtomicReference<>();
324+
HttpHandler handler =
325+
new RetryRequest()
326+
.andFinally(
327+
(HttpRequest request) -> {
328+
count.getAndIncrement();
329+
lastThrown.set(new UncheckedIOException(new ConnectException()));
330+
throw lastThrown.get();
331+
});
332+
333+
UncheckedIOException thrown =
334+
Assertions.assertThrows(
335+
UncheckedIOException.class, () -> handler.execute(new HttpRequest(GET, "/")));
336+
assertThat(thrown).isSameAs(lastThrown.get());
337+
assertThat(count.get()).isEqualTo(4);
338+
}
339+
340+
@Test
341+
void shouldDeliverUnmodifiedServerErrors() {
342+
AtomicInteger count = new AtomicInteger(0);
343+
AtomicReference<HttpResponse> lastResponse = new AtomicReference<>();
344+
HttpHandler handler =
345+
new RetryRequest()
346+
.andFinally(
347+
(HttpRequest request) -> {
348+
count.getAndIncrement();
349+
lastResponse.set(new HttpResponse().setStatus(500));
350+
return lastResponse.get();
351+
});
352+
353+
assertThat(handler.execute(new HttpRequest(GET, "/"))).isSameAs(lastResponse.get());
354+
assertThat(count.get()).isEqualTo(3);
277355
}
278356
}

0 commit comments

Comments
 (0)