You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by yu...@apache.org on 2023/09/28 15:45:46 UTC

[pulsar] branch master updated: [fix] [client] fix reader.hasMessageAvailable return false when incoming queue is not empty (#21259)

This is an automated email from the ASF dual-hosted git repository.

yubiao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git


The following commit(s) were added to refs/heads/master by this push:
     new 6d82b09128f [fix] [client] fix reader.hasMessageAvailable return false when incoming queue is not empty (#21259)
6d82b09128f is described below

commit 6d82b09128f46fdcb27021560d773fac15d66a48
Author: fengyubiao <yu...@streamnative.io>
AuthorDate: Thu Sep 28 23:45:39 2023 +0800

    [fix] [client] fix reader.hasMessageAvailable return false when incoming queue is not empty (#21259)
    
    ### Motivation
    
    Reproduce steps:
    - Create a reader.
    - Reader pulls messages into `incoming queue`, do not call `reader.readNext` now.
    - Trim ledger task will delete the ledgers, then there is no in the topic.
    - Now, you can get messages if you call `reader.readNext`, but the method `reader.hasMessageAvailable` return `false`
    
    Note: the similar issue of `MultiTopicsConsumerImpl` has been fixed by https://github.com/apache/pulsar/pull/13332, current PR only trying to fix the issue of `ConsumerImpl`.
    
    ### Modifications
    
    Make `reader.hasMessageAvailable` return `true` when `incoming queue` is not empty.
---
 .../client/api/NonDurableSubscriptionTest.java     | 87 ++++++++++++++++++++++
 .../apache/pulsar/client/impl/ConsumerImpl.java    |  4 +
 2 files changed, 91 insertions(+)

diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/client/api/NonDurableSubscriptionTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/client/api/NonDurableSubscriptionTest.java
index 6375f79bfbb..b9e3e6dcebe 100644
--- a/pulsar-broker/src/test/java/org/apache/pulsar/client/api/NonDurableSubscriptionTest.java
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/client/api/NonDurableSubscriptionTest.java
@@ -19,17 +19,29 @@
 package org.apache.pulsar.client.api;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import java.lang.reflect.Method;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import lombok.Cleanup;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.client.LedgerHandle;
+import org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl;
+import org.apache.pulsar.broker.BrokerTestUtil;
 import org.apache.pulsar.broker.PulsarService;
 import org.apache.pulsar.broker.service.BrokerService;
 import org.apache.pulsar.broker.service.PulsarChannelInitializer;
 import org.apache.pulsar.broker.service.ServerCnx;
+import org.apache.pulsar.broker.service.persistent.PersistentTopic;
 import org.apache.pulsar.client.impl.ConsumerImpl;
+import org.apache.pulsar.client.impl.MessageIdImpl;
 import org.apache.pulsar.common.api.proto.CommandFlow;
+import org.awaitility.Awaitility;
+import org.awaitility.reflect.WhiteboxImpl;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
@@ -254,4 +266,79 @@ public class NonDurableSubscriptionTest  extends ProducerConsumerBase {
 
         assertEquals(numFlow.get(), numPartitions);
     }
+
+    private void trimLedgers(final String tpName) {
+        // Wait for topic loading.
+        org.awaitility.Awaitility.await().untilAsserted(() -> {
+            PersistentTopic persistentTopic =
+                    (PersistentTopic) pulsar.getBrokerService().getTopic(tpName, false).join().get();
+            assertNotNull(persistentTopic);
+        });
+        PersistentTopic persistentTopic =
+                (PersistentTopic) pulsar.getBrokerService().getTopic(tpName, false).join().get();
+        ManagedLedgerImpl ml = (ManagedLedgerImpl) persistentTopic.getManagedLedger();
+        CompletableFuture<Void> trimLedgersTask = new CompletableFuture<>();
+        ml.trimConsumedLedgersInBackground(trimLedgersTask);
+        trimLedgersTask.join();
+    }
+
+    private void switchLedgerManually(final String tpName) throws Exception {
+        Method ledgerClosed =
+                ManagedLedgerImpl.class.getDeclaredMethod("ledgerClosed", new Class[]{LedgerHandle.class});
+        Method createLedgerAfterClosed =
+                ManagedLedgerImpl.class.getDeclaredMethod("createLedgerAfterClosed", new Class[0]);
+        ledgerClosed.setAccessible(true);
+        createLedgerAfterClosed.setAccessible(true);
+
+        // Wait for topic create.
+        org.awaitility.Awaitility.await().untilAsserted(() -> {
+            PersistentTopic persistentTopic =
+                    (PersistentTopic) pulsar.getBrokerService().getTopic(tpName, false).join().get();
+            assertNotNull(persistentTopic);
+        });
+
+        // Switch ledger.
+        PersistentTopic persistentTopic =
+                (PersistentTopic) pulsar.getBrokerService().getTopic(tpName, false).join().get();
+        ManagedLedgerImpl ml = (ManagedLedgerImpl) persistentTopic.getManagedLedger();
+        LedgerHandle currentLedger1 = WhiteboxImpl.getInternalState(ml, "currentLedger");
+        ledgerClosed.invoke(ml, new Object[]{currentLedger1});
+        createLedgerAfterClosed.invoke(ml, new Object[0]);
+        Awaitility.await().untilAsserted(() -> {
+            LedgerHandle currentLedger2 = WhiteboxImpl.getInternalState(ml, "currentLedger");
+            assertNotEquals(currentLedger1.getId(), currentLedger2.getId());
+        });
+    }
+
+    @Test
+    public void testTrimLedgerIfNoDurableCursor() throws Exception {
+        final String nonDurableCursor = "non-durable-cursor";
+        final String topicName = BrokerTestUtil.newUniqueName("persistent://public/default/tp");
+        Reader<String> reader = pulsarClient.newReader(Schema.STRING).topic(topicName).receiverQueueSize(1)
+                .subscriptionName(nonDurableCursor).startMessageId(MessageIdImpl.earliest).create();
+        Producer<String> producer = pulsarClient.newProducer(Schema.STRING).topic(topicName).create();
+        MessageIdImpl msgSent = (MessageIdImpl) producer.send("1");
+
+        // Trigger switch ledger.
+        // Trigger a trim ledgers task, and verify trim ledgers successful.
+        switchLedgerManually(topicName);
+        trimLedgers(topicName);
+
+        // Since there is one message in the incoming queue, so the method "reader.hasMessageAvailable" should return
+        // true.
+        boolean hasMessageAvailable = reader.hasMessageAvailable();
+        Message<String> msgReceived = reader.readNext(2, TimeUnit.SECONDS);
+        if (msgReceived == null) {
+            assertFalse(hasMessageAvailable);
+        } else {
+            log.info("receive msg: {}", msgReceived.getValue());
+            assertTrue(hasMessageAvailable);
+            assertEquals(msgReceived.getValue(), "1");
+        }
+
+        // cleanup.
+        reader.close();
+        producer.close();
+        admin.topics().delete(topicName);
+    }
 }
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/ConsumerImpl.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/ConsumerImpl.java
index aa3340c6078..ded6a546c24 100644
--- a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/ConsumerImpl.java
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/ConsumerImpl.java
@@ -2335,6 +2335,10 @@ public class ConsumerImpl<T> extends ConsumerBase<T> implements ConnectionHandle
     public CompletableFuture<Boolean> hasMessageAvailableAsync() {
         final CompletableFuture<Boolean> booleanFuture = new CompletableFuture<>();
 
+        if (incomingMessages != null && !incomingMessages.isEmpty()) {
+            return CompletableFuture.completedFuture(true);
+        }
+
         // we haven't read yet. use startMessageId for comparison
         if (lastDequeuedMessageId == MessageId.earliest) {
             // if we are starting from latest, we should seek to the actual last message first.