|
23 | 23 | import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; |
24 | 24 | import static org.mockito.ArgumentMatchers.anyBoolean; |
25 | 25 | import static org.mockito.ArgumentMatchers.anyInt; |
| 26 | +import static org.mockito.ArgumentMatchers.nullable; |
26 | 27 | import static org.mockito.Mockito.any; |
27 | 28 | import static org.mockito.Mockito.doAnswer; |
28 | 29 | import static org.mockito.Mockito.eq; |
@@ -6026,6 +6027,89 @@ public void deleteFailed(ManagedLedgerException exception, Object ctx) { |
6026 | 6027 | assertEquals(properties.get(propertyKey), lastIndex - 1); |
6027 | 6028 | } |
6028 | 6029 |
|
| 6030 | + @Test |
| 6031 | + @SuppressWarnings("unchecked") |
| 6032 | + public void testCompactionCursorResetNeverLoseMarkDeleteProperties() throws Exception { |
| 6033 | + @Cleanup |
| 6034 | + ManagedLedgerImpl ledger = (ManagedLedgerImpl) factory.open( |
| 6035 | + "testCompactionCursorResetNeverLoseMarkDeleteProperties", |
| 6036 | + new ManagedLedgerConfig().setMaxEntriesPerLedger(10)); |
| 6037 | + ManagedCursorImpl cursor = (ManagedCursorImpl) ledger.openCursor("__compaction"); |
| 6038 | + ManagedCursorImpl spyCursor = spy(cursor); |
| 6039 | + ledger.getCursors().removeCursor(cursor.getName()); |
| 6040 | + ledger.getCursors().add(spyCursor, null); |
| 6041 | + |
| 6042 | + ledger.addEntry("entry-1".getBytes(Encoding)); |
| 6043 | + Position markDeletePosition = ledger.addEntry("entry-2".getBytes(Encoding)); |
| 6044 | + |
| 6045 | + String compactedLedgerProperty = "CompactedTopicLedger"; |
| 6046 | + Map<String, Long> properties = Map.of(compactedLedgerProperty, 123456L); |
| 6047 | + |
| 6048 | + CountDownLatch markDeleteEntered = new CountDownLatch(1); |
| 6049 | + CountDownLatch resetEntered = new CountDownLatch(1); |
| 6050 | + CountDownLatch markDeleteReturned = new CountDownLatch(1); |
| 6051 | + CountDownLatch markDeleteCompleted = new CountDownLatch(1); |
| 6052 | + CountDownLatch resetCompleted = new CountDownLatch(1); |
| 6053 | + |
| 6054 | + doAnswer(invocation -> { |
| 6055 | + Map<String, Long> invocationProperties = invocation.getArgument(1); |
| 6056 | + if (invocationProperties != null && invocationProperties.containsKey(compactedLedgerProperty)) { |
| 6057 | + // Hold the compaction mark-delete after it enters internalAsyncMarkDelete, but before its |
| 6058 | + // properties can update lastMarkDeleteEntry. |
| 6059 | + markDeleteEntered.countDown(); |
| 6060 | + assertTrue(resetEntered.await(5, TimeUnit.SECONDS)); |
| 6061 | + try { |
| 6062 | + return invocation.callRealMethod(); |
| 6063 | + } finally { |
| 6064 | + markDeleteReturned.countDown(); |
| 6065 | + } |
| 6066 | + } |
| 6067 | + |
| 6068 | + if (invocationProperties == null || invocationProperties.isEmpty()) { |
| 6069 | + // Let reset capture its properties argument first, then persist it only after the compaction |
| 6070 | + // mark-delete has completed the real internalAsyncMarkDelete call. |
| 6071 | + resetEntered.countDown(); |
| 6072 | + assertTrue(markDeleteReturned.await(5, TimeUnit.SECONDS)); |
| 6073 | + return invocation.callRealMethod(); |
| 6074 | + } |
| 6075 | + |
| 6076 | + return invocation.callRealMethod(); |
| 6077 | + }).when(spyCursor).internalAsyncMarkDelete(any(Position.class), nullable(Map.class), |
| 6078 | + any(MarkDeleteCallback.class), nullable(Object.class), nullable(Runnable.class)); |
| 6079 | + |
| 6080 | + // Start compaction mark-delete from another thread because the spy intentionally blocks it. |
| 6081 | + CompletableFuture.runAsync(() -> spyCursor.asyncMarkDelete( |
| 6082 | + markDeletePosition, properties, new MarkDeleteCallback() { |
| 6083 | + @Override |
| 6084 | + public void markDeleteComplete(Object ctx) { |
| 6085 | + markDeleteCompleted.countDown(); |
| 6086 | + } |
| 6087 | + |
| 6088 | + @Override |
| 6089 | + public void markDeleteFailed(ManagedLedgerException exception, Object ctx) { |
| 6090 | + } |
| 6091 | + }, null)); |
| 6092 | + |
| 6093 | + assertTrue(markDeleteEntered.await(5, TimeUnit.SECONDS)); |
| 6094 | + // Reset the compaction cursor while the previous mark-delete with properties is still in progress. |
| 6095 | + spyCursor.asyncResetCursor(markDeletePosition, false, new AsyncCallbacks.ResetCursorCallback() { |
| 6096 | + @Override |
| 6097 | + public void resetComplete(Object ctx) { |
| 6098 | + resetCompleted.countDown(); |
| 6099 | + } |
| 6100 | + |
| 6101 | + @Override |
| 6102 | + public void resetFailed(ManagedLedgerException exception, Object ctx) { |
| 6103 | + } |
| 6104 | + }); |
| 6105 | + |
| 6106 | + assertTrue(markDeleteCompleted.await(5, TimeUnit.SECONDS)); |
| 6107 | + assertTrue(resetCompleted.await(5, TimeUnit.SECONDS)); |
| 6108 | + |
| 6109 | + assertEquals(spyCursor.getMarkDeletedPosition(), markDeletePosition); |
| 6110 | + assertEquals(spyCursor.getProperties(), properties); |
| 6111 | + } |
| 6112 | + |
6029 | 6113 | class TestPulsarMockBookKeeper extends PulsarMockBookKeeper { |
6030 | 6114 | Map<Long, Integer> ledgerErrors = new HashMap<>(); |
6031 | 6115 |
|
|
0 commit comments