Skip to content

Commit d4c0549

Browse files
DATAGRAPH-1363 - Provide Predicate for retryable exceptions.
The `RetryExceptionPredicate` is is provided to help users checking for retryable exceptions, i.e. for use with Resilience4j.
1 parent 8502dac commit d4c0549

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2011-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.core.support;
17+
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
import java.util.HashSet;
21+
import java.util.Set;
22+
import java.util.function.Predicate;
23+
24+
import org.apiguardian.api.API;
25+
import org.neo4j.driver.exceptions.ServiceUnavailableException;
26+
import org.neo4j.driver.exceptions.SessionExpiredException;
27+
import org.neo4j.driver.exceptions.TransientException;
28+
import org.springframework.dao.TransientDataAccessResourceException;
29+
30+
/**
31+
* A predicate indicating {@literal true} for {@link Throwable throwables} that can be safely retried and {@literal false}
32+
* in any other case. This predicate can be used for example with Resilience4j.
33+
*
34+
* @author Michael J. Simons
35+
* @soundtrack The Kleptones - 24 Hours
36+
* @since 6.0
37+
*/
38+
@API(status = API.Status.STABLE, since = "6.0")
39+
public final class RetryExceptionPredicate implements Predicate<Throwable> {
40+
41+
private static final Set<String> RETRYABLE_ILLEGAL_STATE_MESSAGES = Collections.unmodifiableSet(new HashSet<>(
42+
Arrays.asList("Transaction must be open, but has already been closed.",
43+
"Session must be open, but has already been closed.")));
44+
45+
@Override
46+
public boolean test(Throwable throwable) {
47+
48+
if (throwable instanceof IllegalStateException) {
49+
String msg = throwable.getMessage();
50+
return RETRYABLE_ILLEGAL_STATE_MESSAGES.contains(msg);
51+
}
52+
53+
Throwable ex = throwable;
54+
if (throwable instanceof TransientDataAccessResourceException) {
55+
ex = throwable.getCause();
56+
}
57+
58+
if (ex instanceof TransientException) {
59+
String code = ((TransientException) ex).code();
60+
return !("Neo.TransientError.Transaction.Terminated".equals(code) ||
61+
"Neo.TransientError.Transaction.LockClientStopped".equals(code));
62+
} else {
63+
return ex instanceof SessionExpiredException || ex instanceof ServiceUnavailableException;
64+
}
65+
}
66+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2011-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.core.support;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.ValueSource;
23+
import org.junit.platform.commons.util.ReflectionUtils;
24+
import org.neo4j.driver.exceptions.ServiceUnavailableException;
25+
import org.neo4j.driver.exceptions.SessionExpiredException;
26+
import org.springframework.dao.TransientDataAccessResourceException;
27+
28+
/**
29+
* @author Michael J. Simons
30+
* @soundtrack Die Toten Hosen - Opium fürs Volk
31+
*/
32+
class RetryExceptionPredicateTest {
33+
34+
@ParameterizedTest
35+
@ValueSource(strings = { "Transaction must be open, but has already been closed.",
36+
"Session must be open, but has already been closed." })
37+
void shouldRetryOnSomeIllegalStateExceptions(String msg) {
38+
39+
RetryExceptionPredicate predicate = new RetryExceptionPredicate();
40+
assertThat(predicate.test(new IllegalStateException(msg))).isTrue();
41+
}
42+
43+
@Test
44+
void shouldNotRetryOnRandomIllegalStateExceptions() {
45+
46+
RetryExceptionPredicate predicate = new RetryExceptionPredicate();
47+
assertThat(predicate.test(new IllegalStateException())).isFalse();
48+
}
49+
50+
@ParameterizedTest
51+
@ValueSource(classes = { SessionExpiredException.class, ServiceUnavailableException.class })
52+
void shouldRetryOnObviousRetryableExceptions(Class<? extends Exception> typeOfException) {
53+
54+
RetryExceptionPredicate predicate = new RetryExceptionPredicate();
55+
assertThat(predicate.test(ReflectionUtils.newInstance(typeOfException, "msg"))).isTrue();
56+
}
57+
58+
@ParameterizedTest
59+
@ValueSource(classes = { TransientDataAccessResourceException.class, NullPointerException.class })
60+
void shouldNotRetryOnRandomExceptions() {
61+
62+
RetryExceptionPredicate predicate = new RetryExceptionPredicate();
63+
assertThat(predicate.test(new IllegalStateException())).isFalse();
64+
}
65+
66+
@Test
67+
void shouldExtractCause() {
68+
69+
TransientDataAccessResourceException ex = new TransientDataAccessResourceException("msg",
70+
new ServiceUnavailableException("msg"));
71+
72+
RetryExceptionPredicate predicate = new RetryExceptionPredicate();
73+
assertThat(predicate.test(ex)).isTrue();
74+
}
75+
}

0 commit comments

Comments
 (0)