|
27 | 27 | import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitState.isValidTransition;
|
28 | 28 | import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitStateChannelImpl.MSG_COMPRESSION_TYPE;
|
29 | 29 | import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitStateData.state;
|
| 30 | +import static org.mockito.Mockito.doAnswer; |
| 31 | +import static org.mockito.Mockito.reset; |
| 32 | +import static org.mockito.Mockito.spy; |
30 | 33 | import static org.testng.Assert.assertEquals;
|
31 | 34 | import static org.testng.Assert.assertNotNull;
|
32 | 35 | import static org.testng.Assert.assertNull;
|
|
49 | 52 | import java.util.concurrent.Semaphore;
|
50 | 53 | import java.util.concurrent.TimeUnit;
|
51 | 54 | import java.util.concurrent.atomic.AtomicBoolean;
|
| 55 | +import java.util.concurrent.atomic.AtomicInteger; |
52 | 56 | import java.util.stream.Collectors;
|
53 | 57 | import org.apache.bookkeeper.client.BookKeeper;
|
54 | 58 | import org.apache.commons.lang.reflect.FieldUtils;
|
|
69 | 73 | import org.apache.pulsar.client.api.Reader;
|
70 | 74 | import org.apache.pulsar.client.api.Schema;
|
71 | 75 | import org.apache.pulsar.client.api.SubscriptionInitialPosition;
|
| 76 | +import org.apache.pulsar.client.impl.ReaderImpl; |
72 | 77 | import org.apache.pulsar.common.policies.data.ClusterData;
|
73 | 78 | import org.apache.pulsar.common.policies.data.PersistentTopicInternalStats;
|
74 | 79 | import org.apache.pulsar.common.policies.data.RetentionPolicies;
|
@@ -628,6 +633,80 @@ public void testSlowTableviewAfterCompaction() throws Exception {
|
628 | 633 |
|
629 | 634 | }
|
630 | 635 |
|
| 636 | + @Test |
| 637 | + public void testSlowReceiveTableviewAfterCompaction() throws Exception { |
| 638 | + String topic = "persistent://my-property/use/my-ns/my-topic1"; |
| 639 | + String strategyClassName = "topicCompactionStrategyClassName"; |
| 640 | + |
| 641 | + pulsarClient.newConsumer(schema) |
| 642 | + .topic(topic) |
| 643 | + .subscriptionName("sub1") |
| 644 | + .readCompacted(true) |
| 645 | + .subscribe().close(); |
| 646 | + |
| 647 | + var tv = pulsar.getClient().newTableViewBuilder(schema) |
| 648 | + .topic(topic) |
| 649 | + .subscriptionName("slowTV") |
| 650 | + .loadConf(Map.of( |
| 651 | + strategyClassName, |
| 652 | + ServiceUnitStateCompactionStrategy.class.getName())) |
| 653 | + .create(); |
| 654 | + |
| 655 | + // Configure retention to ensue data is retained for reader |
| 656 | + admin.namespaces().setRetention("my-property/use/my-ns", |
| 657 | + new RetentionPolicies(-1, -1)); |
| 658 | + |
| 659 | + Producer<ServiceUnitStateData> producer = pulsarClient.newProducer(schema) |
| 660 | + .topic(topic) |
| 661 | + .compressionType(MSG_COMPRESSION_TYPE) |
| 662 | + .enableBatching(true) |
| 663 | + .messageRoutingMode(MessageRoutingMode.SinglePartition) |
| 664 | + .create(); |
| 665 | + |
| 666 | + StrategicTwoPhaseCompactor compactor |
| 667 | + = new StrategicTwoPhaseCompactor(conf, pulsarClient, bk, compactionScheduler); |
| 668 | + |
| 669 | + var reader = ((CompletableFuture<ReaderImpl<ServiceUnitStateData>>) FieldUtils |
| 670 | + .readDeclaredField(tv, "reader", true)).get(); |
| 671 | + var consumer = spy(reader.getConsumer()); |
| 672 | + FieldUtils.writeDeclaredField(reader, "consumer", consumer, true); |
| 673 | + String bundle = "bundle1"; |
| 674 | + final AtomicInteger versionId = new AtomicInteger(0); |
| 675 | + final AtomicInteger cnt = new AtomicInteger(1); |
| 676 | + int msgAddCount = 1000; // has to be big enough to cover compacted cursor fast-forward. |
| 677 | + doAnswer(invocationOnMock -> { |
| 678 | + if (cnt.decrementAndGet() == 0) { |
| 679 | + var msg = consumer.receiveAsync(); |
| 680 | + for (int i = 0; i < msgAddCount; i++) { |
| 681 | + producer.newMessage().key(bundle).value( |
| 682 | + new ServiceUnitStateData(Owned, "broker" + versionId.incrementAndGet(), true, |
| 683 | + versionId.get())).send(); |
| 684 | + } |
| 685 | + compactor.compact(topic, strategy).join(); |
| 686 | + return msg; |
| 687 | + } |
| 688 | + // Call the real method |
| 689 | + reset(consumer); |
| 690 | + return consumer.receiveAsync(); |
| 691 | + }).when(consumer).receiveAsync(); |
| 692 | + producer.newMessage().key(bundle).value( |
| 693 | + new ServiceUnitStateData(Owned, "broker", true, |
| 694 | + versionId.incrementAndGet())).send(); |
| 695 | + producer.newMessage().key(bundle).value( |
| 696 | + new ServiceUnitStateData(Owned, "broker" + versionId.incrementAndGet(), true, |
| 697 | + versionId.get())).send(); |
| 698 | + Awaitility.await().atMost(10, TimeUnit.SECONDS).untilAsserted( |
| 699 | + () -> { |
| 700 | + var val = tv.get(bundle); |
| 701 | + assertNotNull(val); |
| 702 | + assertEquals(val.dstBroker(), "broker" + versionId.get()); |
| 703 | + } |
| 704 | + ); |
| 705 | + |
| 706 | + producer.close(); |
| 707 | + tv.close(); |
| 708 | + } |
| 709 | + |
631 | 710 | @Test
|
632 | 711 | public void testBrokerRestartAfterCompaction() throws Exception {
|
633 | 712 | String topic = "persistent://my-property/use/my-ns/my-topic1";
|
|
0 commit comments