Skip to content

Commit 2526932

Browse files
committed
Add tests
1 parent 7cf1a7e commit 2526932

6 files changed

Lines changed: 118 additions & 12 deletions

File tree

lib/BitSet.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class BitSet {
3939

4040
BitSet(int32_t numBits) : words_((numBits / 64) + ((numBits % 64 == 0) ? 0 : 1)) { assert(numBits > 0); }
4141

42+
BitSet(Data&& words) : words_(std::move(words)), wordsInUse_(words_.size()) {}
43+
4244
// Support range loop like:
4345
// ```c++
4446
// BitSet bitSet(129);
@@ -55,6 +57,15 @@ class BitSet {
5557
*/
5658
bool isEmpty() const noexcept { return wordsInUse_ == 0; }
5759

60+
/**
61+
* Returns the value of the bit with the specific index. The value is {@code true} if the bit with the
62+
* index {@code bitIndex} is currently set in this {@code BitSet}; otherwise, the result is {@code false}.
63+
*
64+
* @param bitIndex the bit index
65+
* @return the value of the bit with the specified index
66+
*/
67+
bool get(int32_t bitIndex) const;
68+
5869
/**
5970
* Sets the bits from the specified {@code fromIndex} (inclusive) to the
6071
* specified {@code toIndex} (exclusive) to {@code true}.
@@ -164,6 +175,12 @@ class BitSet {
164175
}
165176
};
166177

178+
inline bool BitSet::get(int32_t bitIndex) const {
179+
assert(bitIndex >= 0);
180+
auto wordIndex_ = wordIndex(bitIndex);
181+
return (wordIndex_ < wordsInUse_) && ((words_[wordIndex_] & (1L << bitIndex)) != 0);
182+
}
183+
167184
inline void BitSet::set(int32_t fromIndex, int32_t toIndex) {
168185
assert(fromIndex < toIndex && fromIndex >= 0 && toIndex >= 0);
169186
if (fromIndex == toIndex) {

lib/ConsumerImpl.cc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,8 +506,13 @@ void ConsumerImpl::messageReceived(const ClientConnectionPtr& cnx, const proto::
506506
}
507507

508508
if (metadata.has_num_messages_in_batch()) {
509+
BitSet::Data words(msg.ack_set_size());
510+
for (int i = 0; i < words.size(); i++) {
511+
words[i] = msg.ack_set(i);
512+
}
513+
BitSet ackSet{std::move(words)};
509514
Lock lock(mutex_);
510-
numOfMessageReceived = receiveIndividualMessagesFromBatch(cnx, m, msg.redelivery_count());
515+
numOfMessageReceived = receiveIndividualMessagesFromBatch(cnx, m, ackSet, msg.redelivery_count());
511516
} else {
512517
// try convery key value data.
513518
m.impl_->convertPayloadToKeyValue(config_.getSchema());
@@ -622,7 +627,8 @@ void ConsumerImpl::notifyPendingReceivedCallback(Result result, Message& msg,
622627

623628
// Zero Queue size is not supported with Batch Messages
624629
uint32_t ConsumerImpl::receiveIndividualMessagesFromBatch(const ClientConnectionPtr& cnx,
625-
Message& batchedMessage, int redeliveryCount) {
630+
Message& batchedMessage, const BitSet& ackSet,
631+
int redeliveryCount) {
626632
auto batchSize = batchedMessage.impl_->metadata.num_messages_in_batch();
627633
LOG_DEBUG("Received Batch messages of size - " << batchSize
628634
<< " -- msgId: " << batchedMessage.getMessageId());
@@ -653,6 +659,13 @@ uint32_t ConsumerImpl::receiveIndividualMessagesFromBatch(const ClientConnection
653659
}
654660
}
655661

662+
if (!ackSet.isEmpty() && !ackSet.get(i)) {
663+
LOG_DEBUG(getName() << "Ignoring message from " << i
664+
<< "th message, which has been acknowledged");
665+
++skippedMessages;
666+
continue;
667+
}
668+
656669
executeNotifyCallback(msg);
657670
}
658671

lib/ConsumerImpl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ typedef std::shared_ptr<Backoff> BackoffPtr;
4848

4949
class AckGroupingTracker;
5050
using AckGroupingTrackerPtr = std::shared_ptr<AckGroupingTracker>;
51+
class BitSet;
5152
class ConsumerStatsBase;
5253
using ConsumerStatsBasePtr = std::shared_ptr<ConsumerStatsBase>;
5354
class UnAckedMessageTracker;
@@ -158,7 +159,7 @@ class ConsumerImpl : public ConsumerImplBase {
158159
void increaseAvailablePermits(const ClientConnectionPtr& currentCnx, int delta = 1);
159160
void drainIncomingMessageQueue(size_t count);
160161
uint32_t receiveIndividualMessagesFromBatch(const ClientConnectionPtr& cnx, Message& batchedMessage,
161-
int redeliveryCount);
162+
const BitSet& ackSet, int redeliveryCount);
162163
bool isPriorBatchIndex(int32_t idx);
163164
bool isPriorEntryIndex(int64_t idx);
164165
void brokerConsumerStatsListener(Result, BrokerConsumerStatsImpl, BrokerConsumerStatsCallback);

tests/AcknowledgeTest.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,45 @@ TEST_F(AcknowledgeTest, testBatchedMessageId) {
210210
ASSERT_EQ(consumers[3].getNumAcked(CommandAck_AckType_Cumulative), 1);
211211
}
212212

213+
TEST_F(AcknowledgeTest, testBatchIndexAck) {
214+
Client client(lookupUrl);
215+
const std::string topic = "test-batch-index-ack-" + unique_str();
216+
Producer producer;
217+
ASSERT_EQ(ResultOk, client.createProducer(
218+
topic,
219+
ProducerConfiguration().setBatchingMaxMessages(100).setBatchingMaxPublishDelayMs(
220+
3600 * 1000 /* 1h */),
221+
producer));
222+
std::vector<ConsumerWrapper> consumers{3};
223+
for (size_t i = 0; i < consumers.size(); i++) {
224+
consumers[i].initialize(client, topic, "sub-" + std::to_string(i), true /* enable batch index ack */);
225+
}
226+
constexpr int numMessages = 5;
227+
for (int i = 0; i < numMessages; i++) {
228+
producer.sendAsync(MessageBuilder().setContent("msg-" + std::to_string(i)).build(), nullptr);
229+
}
230+
producer.flush();
231+
for (int i = 0; i < consumers.size(); i++) {
232+
consumers[i].receiveAtMost(numMessages);
233+
if (i >= 0) {
234+
ASSERT_EQ(consumers[0].messageIdList(), consumers[i].messageIdList());
235+
}
236+
}
237+
auto msgIds = consumers[0].messageIdList();
238+
239+
consumers[0].acknowledgeAndRestart({0, 2, 4}, AckType::INDIVIDUAL);
240+
consumers[0].receiveAtMost(2);
241+
ASSERT_EQ(subMessageIdList(msgIds, {1, 3}), consumers[0].messageIdList());
242+
Message msg;
243+
ASSERT_EQ(ResultTimeout, consumers[0].receive(msg));
244+
245+
consumers[1].acknowledgeAndRestart({0, 3}, AckType::INDIVIDUAL_LIST);
246+
consumers[1].receiveAtMost(3);
247+
ASSERT_EQ(subMessageIdList(msgIds, {1, 2, 4}), consumers[1].messageIdList());
248+
249+
consumers[2].acknowledgeAndRestart({3}, AckType::CUMULATIVE);
250+
consumers[2].receiveAtMost(1);
251+
ASSERT_EQ(subMessageIdList(msgIds, {4}), consumers[2].messageIdList());
252+
}
253+
213254
INSTANTIATE_TEST_SUITE_P(BasicEndToEndTest, AcknowledgeTest, testing::Values(100, 0));

tests/BitSetTest.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ TEST(BitSetTest, testSet) {
6262
// range contains one word
6363
bitSet.set(3, 29);
6464
ASSERT_EQ(toLongVector(bitSet), std::vector<uint64_t>{0x1ffffff8});
65+
for (int i = 0; i < 64 * 5 + 1; i++) {
66+
if (i >= 3 && i < 29) {
67+
ASSERT_TRUE(bitSet.get(i));
68+
} else {
69+
ASSERT_FALSE(bitSet.get(i));
70+
}
71+
}
6572

6673
// range contains multiple words
6774
bitSet.set(64 * 2 + 11, 64 * 4 + 19);

tests/ConsumerWrapper.h

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,26 @@ enum class AckType
3535
CUMULATIVE
3636
};
3737

38+
inline MessageIdList subMessageIdList(const MessageIdList& messageIdList,
39+
const std::vector<size_t>& indexes) {
40+
std::vector<MessageId> subMessageIdList;
41+
for (size_t index : indexes) {
42+
subMessageIdList.emplace_back(messageIdList.at(index));
43+
}
44+
return subMessageIdList;
45+
}
46+
3847
class ConsumerWrapper {
3948
public:
40-
void initialize(Client& client, const std::string& topic, const std::string& subscription) {
49+
void initialize(Client& client, const std::string& topic, const std::string& subscription,
50+
bool enableBatchIndexAck = false) {
51+
client_ = &client;
52+
topic_ = topic;
53+
subscription_ = subscription;
4154
// Enable the stats for cumulative ack
42-
ConsumerConfiguration conf;
43-
conf.setUnAckedMessagesTimeoutMs(10000);
44-
ASSERT_EQ(ResultOk, client.subscribe(topic, subscription, conf, consumer_));
55+
conf_.setUnAckedMessagesTimeoutMs(10000);
56+
conf_.setBatchIndexAckEnabled(enableBatchIndexAck);
57+
ASSERT_EQ(ResultOk, client_->subscribe(topic_, subscription_, conf_, consumer_));
4558
}
4659

4760
const std::vector<MessageId>& messageIdList() const noexcept { return messageIdList_; }
@@ -58,11 +71,8 @@ class ConsumerWrapper {
5871

5972
unsigned long getNumAcked(CommandAck_AckType ackType) const;
6073

61-
void acknowledgeAndRedeliver(const std::vector<size_t>& indexes, AckType ackType) {
62-
std::vector<MessageId> msgIds;
63-
for (size_t index : indexes) {
64-
msgIds.emplace_back(messageIdList_.at(index));
65-
}
74+
void acknowledge(const std::vector<size_t>& indexes, AckType ackType) {
75+
auto msgIds = subMessageIdList(messageIdList_, indexes);
6676
if (ackType == AckType::INDIVIDUAL_LIST) {
6777
consumer_.acknowledge(msgIds);
6878
} else {
@@ -76,10 +86,27 @@ class ConsumerWrapper {
7686
}
7787
// Wait until the acknowledge command is sent
7888
std::this_thread::sleep_for(std::chrono::milliseconds(100));
89+
}
90+
91+
void acknowledgeAndRedeliver(const std::vector<size_t>& indexes, AckType ackType) {
92+
acknowledge(indexes, ackType);
7993
consumer_.redeliverUnacknowledgedMessages();
8094
}
8195

96+
// NOTE: Currently Pulsar broker doesn't support redelivery with batch index ACK well, so here we verify
97+
// the acknowledgment by restarting the consumer.
98+
void acknowledgeAndRestart(const std::vector<size_t>& indexes, AckType ackType) {
99+
acknowledge(indexes, ackType);
100+
messageIdList_.clear();
101+
consumer_.close();
102+
ASSERT_EQ(ResultOk, client_->subscribe(topic_, subscription_, conf_, consumer_));
103+
}
104+
82105
private:
106+
Client* client_;
107+
std::string topic_;
108+
std::string subscription_;
109+
ConsumerConfiguration conf_;
83110
Consumer consumer_;
84111
std::vector<MessageId> messageIdList_;
85112
};

0 commit comments

Comments
 (0)