|
26 | 26 | import static org.mockito.ArgumentMatchers.anyMap; |
27 | 27 | import static org.mockito.ArgumentMatchers.anyString; |
28 | 28 | import static org.mockito.ArgumentMatchers.eq; |
| 29 | +import static org.mockito.ArgumentMatchers.nullable; |
29 | 30 | import static org.mockito.Mockito.atLeast; |
30 | 31 | import static org.mockito.Mockito.doAnswer; |
31 | 32 | import static org.mockito.Mockito.doNothing; |
@@ -5213,4 +5214,65 @@ public void addFailed(ManagedLedgerException exception, Object ctx) { |
5213 | 5214 | // Verify properties are preserved after cursor reset |
5214 | 5215 | assertEquals(cursor.getProperties(), expectedProperties); |
5215 | 5216 | } |
| 5217 | + |
| 5218 | + @Test |
| 5219 | + @SuppressWarnings("unchecked") |
| 5220 | + public void testAdvanceCursorsIfNecessaryNeverLoseMarkDeleteProperties() throws Exception { |
| 5221 | + ManagedLedgerConfig config = new ManagedLedgerConfig(); |
| 5222 | + config.setMaxEntriesPerLedger(1); |
| 5223 | + config.setRetentionTime(0, TimeUnit.SECONDS); |
| 5224 | + config.setRetentionSizeInMB(0); |
| 5225 | + |
| 5226 | + @Cleanup |
| 5227 | + ManagedLedgerImpl ledger = |
| 5228 | + (ManagedLedgerImpl) factory.open("testAdvanceCursorsIfNecessaryNeverLoseMarkDeleteProperties", config); |
| 5229 | + @Cleanup |
| 5230 | + ManagedCursorImpl durableCursor = (ManagedCursorImpl) ledger.openCursor("durableCursor1"); |
| 5231 | + @Cleanup |
| 5232 | + NonDurableCursorImpl realNonDurableCursor = |
| 5233 | + (NonDurableCursorImpl) ledger.newNonDurableCursor(PositionFactory.EARLIEST); |
| 5234 | + NonDurableCursorImpl nonDurableCursor = spy(realNonDurableCursor); |
| 5235 | + |
| 5236 | + ledger.getCursors().removeCursor(realNonDurableCursor.getName()); |
| 5237 | + ledger.getCursors().add(nonDurableCursor, null); |
| 5238 | + |
| 5239 | + CountDownLatch advanceCursorsMarkDeleteEnteredLatch = new CountDownLatch(1); |
| 5240 | + CountDownLatch nonDurableCursorsMarkDeleteCompletedLatch = new CountDownLatch(1); |
| 5241 | + CountDownLatch advanceCursorsMarkDeleteCompletedLatch = new CountDownLatch(1); |
| 5242 | + |
| 5243 | + doAnswer(invocation -> { |
| 5244 | + Map<String, Long> invocationProperties = invocation.getArgument(1); |
| 5245 | + // Pause the advanceCursorsIfNecessary mark-delete so the nonDurableCursor markDelete() can complete first. |
| 5246 | + if (invocationProperties == null || invocationProperties.isEmpty()) { |
| 5247 | + advanceCursorsMarkDeleteEnteredLatch.countDown(); |
| 5248 | + assertTrue(nonDurableCursorsMarkDeleteCompletedLatch.await(5, TimeUnit.SECONDS)); |
| 5249 | + try { |
| 5250 | + return invocation.callRealMethod(); |
| 5251 | + } finally { |
| 5252 | + advanceCursorsMarkDeleteCompletedLatch.countDown(); |
| 5253 | + } |
| 5254 | + } |
| 5255 | + |
| 5256 | + return invocation.callRealMethod(); |
| 5257 | + }).when(nonDurableCursor) |
| 5258 | + .internalAsyncMarkDelete(any(Position.class), nullable(Map.class), any(MarkDeleteCallback.class), |
| 5259 | + nullable(Object.class), nullable(Runnable.class)); |
| 5260 | + |
| 5261 | + ledger.addEntry("entry-1".getBytes(Encoding)); |
| 5262 | + Position pos2 = ledger.addEntry("entry-2".getBytes(Encoding)); |
| 5263 | + |
| 5264 | + // Mark-delete the durable cursor to trigger trimming, which advances non-durable cursors. |
| 5265 | + durableCursor.markDelete(pos2); |
| 5266 | + assertTrue(advanceCursorsMarkDeleteEnteredLatch.await(5, TimeUnit.SECONDS)); |
| 5267 | + |
| 5268 | + String propertyKey = "test-property"; |
| 5269 | + Map<String, Long> properties = new HashMap<>(); |
| 5270 | + properties.put(propertyKey, 1L); |
| 5271 | + nonDurableCursor.markDelete(pos2, properties); |
| 5272 | + nonDurableCursorsMarkDeleteCompletedLatch.countDown(); |
| 5273 | + |
| 5274 | + assertTrue(advanceCursorsMarkDeleteCompletedLatch.await(5, TimeUnit.SECONDS)); |
| 5275 | + assertEquals(nonDurableCursor.getMarkDeletedPosition(), pos2); |
| 5276 | + assertEquals(nonDurableCursor.getProperties(), properties); |
| 5277 | + } |
5216 | 5278 | } |
0 commit comments