Skip to content

Commit 2a09dfa

Browse files
committedDec 19, 2023
retry on error 503 (DE-55, #530)
1 parent 17cfc2f commit 2a09dfa

File tree

4 files changed

+78
-29
lines changed

4 files changed

+78
-29
lines changed
 
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* DISCLAIMER
3+
*
4+
* Copyright 2017 ArangoDB GmbH, Cologne, Germany
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
*/
20+
21+
package com.arangodb.internal.net;
22+
23+
import com.arangodb.ArangoDBException;
24+
import com.arangodb.entity.ErrorEntity;
25+
26+
public class ArangoDBUnavailableException extends ArangoDBException {
27+
28+
public static ArangoDBUnavailableException from(final ErrorEntity errorEntity) {
29+
if (errorEntity == null || errorEntity.getCode() != 503 || errorEntity.getErrorNum() != 503) {
30+
throw new IllegalArgumentException();
31+
}
32+
return new ArangoDBUnavailableException(errorEntity);
33+
}
34+
35+
private ArangoDBUnavailableException(final ErrorEntity errorEntity) {
36+
super(errorEntity);
37+
}
38+
39+
}

‎core/src/main/java/com/arangodb/internal/net/Communication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ private CompletableFuture<InternalResponse> doExecuteAsync(
9898
rfuture
9999
);
100100
}
101+
} else if (errorEntityEx instanceof ArangoDBUnavailableException) {
102+
handleException(true, errorEntityEx, hostHandle, request, host, reqId, attemptCount, rfuture);
101103
} else if (errorEntityEx != null) {
102104
rfuture.completeExceptionally(errorEntityEx);
103105
} else {

‎core/src/main/java/com/arangodb/internal/util/ResponseUtils.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.arangodb.entity.ErrorEntity;
2525
import com.arangodb.internal.ArangoErrors;
2626
import com.arangodb.internal.net.ArangoDBRedirectException;
27+
import com.arangodb.internal.net.ArangoDBUnavailableException;
2728
import com.arangodb.internal.serde.InternalSerde;
2829
import com.arangodb.internal.InternalResponse;
2930

@@ -44,21 +45,24 @@ private ResponseUtils() {
4445

4546
public static ArangoDBException translateError(final InternalSerde util, final InternalResponse response) {
4647
final int responseCode = response.getResponseCode();
47-
if (responseCode >= ERROR_STATUS) {
48-
if (responseCode == ERROR_INTERNAL && response.containsMeta(HEADER_ENDPOINT)) {
49-
return new ArangoDBRedirectException(String.format("Response Code: %s", responseCode),
50-
response.getMeta(HEADER_ENDPOINT));
51-
} else if (response.getBody() != null) {
52-
final ErrorEntity errorEntity = util.deserialize(response.getBody(), ErrorEntity.class);
53-
ArangoDBException e = new ArangoDBException(errorEntity);
54-
if (ArangoErrors.QUEUE_TIME_VIOLATED.equals(e.getErrorNum())) {
55-
return ArangoDBException.of(new TimeoutException().initCause(e));
56-
}
57-
return e;
58-
} else {
59-
return new ArangoDBException(String.format("Response Code: %s", responseCode), responseCode);
48+
if (responseCode < ERROR_STATUS) {
49+
return null;
50+
}
51+
if (responseCode == ERROR_INTERNAL && response.containsMeta(HEADER_ENDPOINT)) {
52+
return new ArangoDBRedirectException(String.format("Response Code: %s", responseCode),
53+
response.getMeta(HEADER_ENDPOINT));
54+
}
55+
if (response.getBody() != null) {
56+
final ErrorEntity errorEntity = util.deserialize(response.getBody(), ErrorEntity.class);
57+
if (errorEntity.getCode() == ERROR_INTERNAL && errorEntity.getErrorNum() == ERROR_INTERNAL) {
58+
return ArangoDBUnavailableException.from(errorEntity);
59+
}
60+
ArangoDBException e = new ArangoDBException(errorEntity);
61+
if (ArangoErrors.QUEUE_TIME_VIOLATED.equals(e.getErrorNum())) {
62+
return ArangoDBException.of(new TimeoutException().initCause(e));
6063
}
64+
return e;
6165
}
62-
return null;
66+
return new ArangoDBException(String.format("Response Code: %s", responseCode), responseCode);
6367
}
6468
}

‎resilience-tests/src/test/java/resilience/http/MockTest.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package resilience.http;
22

3+
import ch.qos.logback.classic.Level;
34
import com.arangodb.ArangoDB;
4-
import com.arangodb.ArangoDBException;
55
import com.arangodb.Protocol;
6-
import resilience.SingleServerTest;
76
import org.junit.jupiter.api.AfterEach;
87
import org.junit.jupiter.api.BeforeEach;
98
import org.junit.jupiter.api.Test;
109
import org.mockserver.integration.ClientAndServer;
10+
import org.mockserver.matchers.Times;
11+
import resilience.SingleServerTest;
1112

1213
import java.util.concurrent.ExecutionException;
1314

1415
import static org.assertj.core.api.Assertions.assertThat;
15-
import static org.assertj.core.api.Assertions.catchThrowable;
1616
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
1717
import static org.mockserver.model.HttpRequest.request;
1818
import static org.mockserver.model.HttpResponse.response;
@@ -39,46 +39,50 @@ void after() {
3939
}
4040

4141
@Test
42-
void doTest() {
42+
void retryOn503() {
4343
arangoDB.getVersion();
4444

4545
mockServer
4646
.when(
4747
request()
4848
.withMethod("GET")
49-
.withPath("/.*/_api/version")
49+
.withPath("/.*/_api/version"),
50+
Times.exactly(2)
5051
)
5152
.respond(
5253
response()
5354
.withStatusCode(503)
5455
.withBody("{\"error\":true,\"errorNum\":503,\"errorMessage\":\"boom\",\"code\":503}")
5556
);
5657

57-
Throwable thrown = catchThrowable(arangoDB::getVersion);
58-
assertThat(thrown)
59-
.isInstanceOf(ArangoDBException.class)
60-
.hasMessageContaining("boom");
58+
logs.reset();
59+
arangoDB.getVersion();
60+
assertThat(logs.getLogs())
61+
.filteredOn(e -> e.getLevel().equals(Level.WARN))
62+
.anyMatch(e -> e.getFormattedMessage().contains("Could not connect to host"));
6163
}
6264

6365
@Test
64-
void doTestAsync() throws ExecutionException, InterruptedException {
66+
void retryOn503Async() throws ExecutionException, InterruptedException {
6567
arangoDB.async().getVersion().get();
6668

6769
mockServer
6870
.when(
6971
request()
7072
.withMethod("GET")
71-
.withPath("/.*/_api/version")
73+
.withPath("/.*/_api/version"),
74+
Times.exactly(2)
7275
)
7376
.respond(
7477
response()
7578
.withStatusCode(503)
7679
.withBody("{\"error\":true,\"errorNum\":503,\"errorMessage\":\"boom\",\"code\":503}")
7780
);
7881

79-
Throwable thrown = catchThrowable(() -> arangoDB.async().getVersion().get()).getCause();
80-
assertThat(thrown)
81-
.isInstanceOf(ArangoDBException.class)
82-
.hasMessageContaining("boom");
82+
logs.reset();
83+
arangoDB.async().getVersion().get();
84+
assertThat(logs.getLogs())
85+
.filteredOn(e -> e.getLevel().equals(Level.WARN))
86+
.anyMatch(e -> e.getFormattedMessage().contains("Could not connect to host"));
8387
}
8488
}

0 commit comments

Comments
 (0)
Please sign in to comment.