Skip to content

Commit 93c249f

Browse files
ATLAS-5317: Retry purge batches only on PermanentLockingException.
Simplify PurgeBatchExecutorTest without sun.misc.Unsafe. Refresh embedded Solr edismax qf fields in test-tools solrconfig.xml.
1 parent a99bbe5 commit 93c249f

3 files changed

Lines changed: 38 additions & 76 deletions

File tree

repository/src/main/java/org/apache/atlas/services/PurgeBatchExecutor.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.slf4j.Logger;
2727
import org.slf4j.LoggerFactory;
2828

29-
import java.util.Arrays;
3029
import java.util.Collections;
3130
import java.util.HashSet;
3231
import java.util.Set;
@@ -39,19 +38,15 @@ public class PurgeBatchExecutor {
3938
private static final int BASE_BACKOFF_MS = 500;
4039

4140
/**
42-
* Fully-qualified class names treated as retryable lock or backend conflicts during purge batch
43-
* execution. Names are matched against the throwable cause chain to avoid a compile-time dependency
44-
* on JanusGraph or Berkeley JE types in the service layer.
41+
* Fully-qualified class names treated as retryable lock conflicts during purge batch execution.
42+
* Names are matched against the throwable cause chain to avoid a compile-time dependency on
43+
* JanusGraph types in the service layer.
4544
* <p>
46-
* Design default: {@code PermanentLockingException}. Berkeley JE lock timeouts/deadlocks and
47-
* {@code PermanentBackendException} are included for the embedded Berkeley backend.
45+
* Design default: {@code PermanentLockingException} (see ATLAS-5317 retry strategy).
4846
*/
4947
static final Set<String> RETRYABLE_LOCK_CONFLICT_EXCEPTION_CLASS_NAMES = Collections.unmodifiableSet(
50-
new HashSet<>(Arrays.asList(
51-
"org.janusgraph.diskstorage.locking.PermanentLockingException",
52-
"com.sleepycat.je.LockTimeoutException",
53-
"com.sleepycat.je.DeadlockException",
54-
"org.janusgraph.diskstorage.PermanentBackendException")));
48+
new HashSet<>(Collections.singletonList(
49+
"org.janusgraph.diskstorage.locking.PermanentLockingException")));
5550

5651
private final AtlasEntityStore entityStore;
5752

@@ -69,7 +64,7 @@ public EntityMutationResponse executeBatch(Set<String> batch) throws AtlasBaseEx
6964

7065
/**
7166
* Returns {@code true} when {@code throwable} or any of its causes matches a known retryable
72-
* lock or backend conflict type.
67+
* lock conflict type.
7368
*/
7469
static boolean isRetryableLockConflict(Throwable throwable) {
7570
if (throwable == null) {

repository/src/test/java/org/apache/atlas/services/PurgeBatchExecutorTest.java

Lines changed: 30 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
import org.apache.atlas.exception.AtlasBaseException;
2424
import org.apache.atlas.model.instance.EntityMutationResponse;
2525
import org.apache.atlas.repository.store.graph.AtlasEntityStore;
26+
import org.janusgraph.diskstorage.PermanentBackendException;
2627
import org.janusgraph.diskstorage.locking.PermanentLockingException;
2728
import org.mockito.MockedStatic;
28-
import org.testng.annotations.DataProvider;
2929
import org.testng.annotations.Test;
3030

3131
import java.util.Collections;
@@ -45,16 +45,6 @@
4545
public class PurgeBatchExecutorTest {
4646
private static final Set<String> BATCH = Collections.singleton("guid1");
4747

48-
@DataProvider(name = "retryableLockConflictExceptionClassNames")
49-
public Object[][] retryableLockConflictExceptionClassNames() {
50-
return new Object[][] {
51-
{"org.janusgraph.diskstorage.locking.PermanentLockingException"},
52-
{"com.sleepycat.je.LockTimeoutException"},
53-
{"com.sleepycat.je.DeadlockException"},
54-
{"org.janusgraph.diskstorage.PermanentBackendException"}
55-
};
56-
}
57-
5848
@Test
5949
public void testExecuteBatchSuccess() throws Exception {
6050
AtlasEntityStore mockStore = mock(AtlasEntityStore.class);
@@ -79,30 +69,33 @@ public void testIsRetryableLockConflictReturnsFalseForNonRetryableException() {
7969
}
8070

8171
@Test
82-
public void testIsRetryableLockConflictMatchesWrappedCause() {
72+
public void testIsRetryableLockConflictReturnsFalseForPermanentBackendException() {
73+
PermanentBackendException backendException = new PermanentBackendException("backend failure");
74+
75+
assertFalse(PurgeBatchExecutor.isRetryableLockConflict(backendException));
76+
}
77+
78+
@Test
79+
public void testIsRetryableLockConflictMatchesPermanentLockingException() {
8380
PermanentLockingException ple = new PermanentLockingException("lock conflict");
84-
RuntimeException wrapped = new RuntimeException(new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, ple));
8581

86-
assertTrue(PurgeBatchExecutor.isRetryableLockConflict(wrapped));
82+
assertTrue(PurgeBatchExecutor.isRetryableLockConflict(ple));
8783
}
8884

89-
@Test(dataProvider = "retryableLockConflictExceptionClassNames")
90-
public void testIsRetryableLockConflictMatchesKnownTypes(String className) throws Exception {
91-
Exception conflict = newExceptionByClassName(className, "lock conflict");
85+
@Test
86+
public void testIsRetryableLockConflictMatchesWrappedCause() {
87+
PermanentLockingException ple = new PermanentLockingException("lock conflict");
88+
RuntimeException wrapped = new RuntimeException(new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, ple));
9289

93-
assertTrue(PurgeBatchExecutor.RETRYABLE_LOCK_CONFLICT_EXCEPTION_CLASS_NAMES.contains(className));
94-
assertTrue(PurgeBatchExecutor.isRetryableLockConflict(conflict));
95-
// Use message+cause form: RuntimeException(Throwable) calls cause.toString(), which NPEs on
96-
// partially-initialized Berkeley JE DatabaseException instances created for this test.
97-
assertTrue(PurgeBatchExecutor.isRetryableLockConflict(wrapWithCause(conflict)));
90+
assertTrue(PurgeBatchExecutor.isRetryableLockConflict(wrapped));
9891
}
9992

10093
@Test
10194
public void testExecuteBatchClearsCachesBeforeRetry() throws Exception {
10295
AtlasEntityStore mockStore = mock(AtlasEntityStore.class);
10396
EntityMutationResponse mockResponse = new EntityMutationResponse();
104-
PermanentLockingException ple = new PermanentLockingException("lock conflict");
105-
AtlasBaseException wrappedException = new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, ple);
97+
PermanentLockingException ple = new PermanentLockingException("lock conflict");
98+
AtlasBaseException wrappedException = new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, ple);
10699

107100
when(mockStore.purgeEntitiesInBatch(BATCH))
108101
.thenThrow(wrappedException)
@@ -149,24 +142,6 @@ public void testExecuteBatchRetryOnPermanentLockingException() throws Exception
149142
assertTrue(duration >= 1000, "Expected backoff delays but finished in " + duration + " ms");
150143
}
151144

152-
@Test(dataProvider = "retryableLockConflictExceptionClassNames")
153-
public void testExecuteBatchRetriesOnKnownLockConflictTypes(String className) throws Exception {
154-
AtlasEntityStore mockStore = mock(AtlasEntityStore.class);
155-
EntityMutationResponse mockResponse = new EntityMutationResponse();
156-
Exception conflict = newExceptionByClassName(className, "lock conflict");
157-
AtlasBaseException wrappedException = new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, conflict);
158-
159-
when(mockStore.purgeEntitiesInBatch(BATCH))
160-
.thenThrow(wrappedException)
161-
.thenReturn(mockResponse);
162-
163-
PurgeBatchExecutor executor = new PurgeBatchExecutor(mockStore);
164-
EntityMutationResponse response = executor.executeBatch(BATCH);
165-
166-
assertEquals(response, mockResponse);
167-
verify(mockStore, times(2)).purgeEntitiesInBatch(BATCH);
168-
}
169-
170145
@Test
171146
public void testExecuteBatchFailsAfterMaxLockingConflicts() throws Exception {
172147
AtlasEntityStore mockStore = mock(AtlasEntityStore.class);
@@ -199,27 +174,19 @@ public void testExecuteBatchNoRetryOnNonLockingException() throws Exception {
199174
verify(mockStore, times(1)).purgeEntitiesInBatch(BATCH);
200175
}
201176

202-
private static RuntimeException wrapWithCause(Throwable cause) {
203-
return new RuntimeException("wrapped", cause);
204-
}
177+
@Test
178+
public void testExecuteBatchNoRetryOnPermanentBackendException() throws Exception {
179+
AtlasEntityStore mockStore = mock(AtlasEntityStore.class);
180+
PermanentBackendException backendException = new PermanentBackendException("backend failure");
181+
AtlasBaseException wrappedException = new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, backendException);
205182

206-
private static Exception newExceptionByClassName(String className, String message) throws Exception {
207-
try {
208-
Class<?> clazz = Class.forName(className);
209-
try {
210-
return (Exception) clazz.getConstructor(String.class).newInstance(message);
211-
} catch (NoSuchMethodException e) {
212-
try {
213-
return (Exception) clazz.getConstructor().newInstance();
214-
} catch (NoSuchMethodException e2) {
215-
java.lang.reflect.Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
216-
f.setAccessible(true);
217-
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) f.get(null);
218-
return (Exception) unsafe.allocateInstance(clazz);
219-
}
220-
}
221-
} catch (ClassNotFoundException e) {
222-
throw new org.testng.SkipException("Required exception class not on classpath: " + className);
223-
}
183+
when(mockStore.purgeEntitiesInBatch(BATCH)).thenThrow(wrappedException);
184+
185+
PurgeBatchExecutor executor = new PurgeBatchExecutor(mockStore);
186+
187+
AtlasBaseException ex = expectThrows(AtlasBaseException.class, () -> executor.executeBatch(BATCH));
188+
189+
assertEquals(ex.getAtlasErrorCode(), AtlasErrorCode.INTERNAL_ERROR);
190+
verify(mockStore, times(1)).purgeEntitiesInBatch(BATCH);
224191
}
225192
}

test-tools/src/main/resources/solr/core-template/solrconfig.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@
445445
-->
446446
<lst name="defaults">
447447
<str name="defType">edismax</str>
448-
<str name="qf">35x_t 5j9_t 7wl_t a9x_t but_t dfp_l f0l_t i6d_l iyt_l jr9_t kjp_s lc5_t m4l_s mx1_t ohx_t xz9_i 1151_t 12px_t 14at_l 15vp_t 1891_t 19tx_t 1bet_t 1czp_t 1ekl_t 1gxx_t 1iit_l 1k3p_t 1lol_t 1o1x_t 1qf9_t 1ssl_t 1v5x_t 1wqt_t 1z45_t 20p1_t 2rk5_l 2t51_l 50xx_t 5dl1_s 5c05_s 59mt_s 581x_s 5b7p_t 5j45_t 5f5x_t 5gqt_l 5hj9_t 5nut_t 5m9x_t 5pfp_t 5wjp_t 622t_t 66th_t 64g5_t 658l_t 63np_t 6d51_t 6ccl_t 6k91_l 6ltx_l 6dxh_t 6jgl_t 6uit_l 6w3p_l 6sxx_t 6rd1_t 6xol_t 6net_t 6o79_t 6ps5_t 77yd_t 78qt_l 74sl_t 71mt_l 737p_l 6yh1_t 76dh_t 7i85_t 7j0l_t 7klh_t 7abp_t 7m6d_t 7hfp_t 7jt1_t 7ldx_t 7fut_t ac5h_t a491_t adqd_t a9s5_t acxx_t abd1_t an7p_t afb9_t apl1_t akud_t amf9_t aosl_t az2d_t ar5x_t awp1_t ay9x_t fcat_t f56t_t f9xh_t fbid_t f8cl_t f3lx_l f6rp_t f951_t f4ed_l fdvp_l fimd_t fjet_t fd39_l feo5_t ffgl_i fhtx_t fmkl_t fls5_t fnd1_t foxx_i fsw5_t fuh1_t fyf9_t fz7p_i fwud_t g0sl_t g005_t g5j9_t g7wl_t g35x_t g6bp_t g4qt_t g1l1_l g745_t g2dh_l hwqt_l htl1_t hv5x_l i0p1_l i1hh_t i4n9_i i70l_t i32d_t j9qd_t jll1_l jn5x_i jg1x_t jnyd_i joqt_i jpj9_f jvut_d jv2d_l kumd_t l05h_l l3b9_t l6h1_l l81x_t l4w5_t l9mt_l lb7p_t</str>
448+
<str name="qf">35x_t 5j9_t 7wl_t a9x_t but_t dfp_l f0l_t i6d_l iyt_l jr9_t kjp_s lc5_t m4l_s mx1_t ohx_t xz9_i 1151_t 12px_t 14at_l 15vp_t 1891_t 19tx_t 1bet_t 1czp_t 1ekl_t 1gxx_t 1iit_l 1k3p_t 1lol_t 1o1x_t 1qf9_t 1ssl_t 1v5x_t 1wqt_t 1z45_t 20p1_t 2rk5_l 2t51_l 581x_t 5kp1_s 5j45_s 5gqt_s 5f5x_s 5ibp_t 5q85_t 5m9x_t 5nut_l 5on9_t 5uyt_t 5tdx_t 5wjp_t 63np_t 696t_t 6dxh_t 6bk5_t 6ccl_t 6arp_t 6k91_t 6jgl_t 6rd1_l 6sxx_l 6l1h_t 6qkl_t 71mt_l 737p_l 701x_t 6yh1_t 74sl_t 6uit_t 6vb9_t 6ww5_t 7f2d_t 7fut_l 7bwl_t 78qt_l 7abp_l 7hfp_t 75l1_t 7dhh_t 7shx_t 7tad_t 7uv9_t 7klh_t 7wg5_t 7rph_t 7u2t_t 7vnp_t 7q4l_t amf9_t aeit_t ao05_t ak1x_t an7p_t almt_t axhh_t apl1_t azut_t av45_t awp1_t az2d_t b9c5_t b1fp_t b6yt_t b8jp_t fmkl_t ffgl_t fk79_t fls5_t fimd_t fdvp_l fh1h_t fjet_t feo5_l fo5h_l fsw5_t ftol_t fnd1_l foxx_t fpqd_i fs3p_t fwud_t fw1x_t fxmt_t fz7p_i g35x_t g4qt_t g8p1_t g9hh_i g745_t gb2d_t ga9x_t gft1_t gi6d_t gdfp_t gglh_t gf0l_t gbut_l ghdx_t gcn9_l i70l_l i3ut_t i5fp_l iayt_l ibr9_t iex1_i ihad_t idc5_t jk05_t jvut_l jxfp_i jqbp_t jy85_i jz0l_i jzt1_f k64l_d k5c5_l</str>
449449
<str name="hl.fl">*</str>
450450
<bool name="hl.requireFieldMatch">true</bool>
451451
<bool name="lowercaseOperators">true</bool>

0 commit comments

Comments
 (0)