-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[draft] [fix] [client] fix ack failed when consumer is reconnecting #21928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -231,29 +231,31 @@ private CompletableFuture<Void> addAcknowledgment(MessageIdAdv msgId, | |||||
case Individual: | ||||||
return addIndividualAcknowledgment(msgId, | ||||||
batchMessageId, | ||||||
__ -> doIndividualAck(__, properties), | ||||||
__ -> doIndividualBatchAck(__, properties)); | ||||||
__ -> doIndividualAck(__, properties, false), | ||||||
__ -> doIndividualBatchAck(__, properties, false)); | ||||||
case Cumulative: | ||||||
if (batchMessageId != null) { | ||||||
consumer.onAcknowledgeCumulative(batchMessageId, null); | ||||||
} else { | ||||||
consumer.onAcknowledgeCumulative(msgId, null); | ||||||
} | ||||||
if (batchMessageId == null || MessageIdAdvUtils.acknowledge(batchMessageId, false)) { | ||||||
return doCumulativeAck(msgId, properties, null); | ||||||
return doCumulativeAck(msgId, properties, null, false); | ||||||
} else if (batchIndexAckEnabled) { | ||||||
return doCumulativeBatchIndexAck(batchMessageId, properties); | ||||||
return doCumulativeBatchIndexAck(batchMessageId, properties, false); | ||||||
} else { | ||||||
doCumulativeAck(MessageIdAdvUtils.prevMessageId(batchMessageId), properties, null); | ||||||
doCumulativeAck(MessageIdAdvUtils.prevMessageId(batchMessageId), properties, null, false); | ||||||
return CompletableFuture.completedFuture(null); | ||||||
} | ||||||
default: | ||||||
throw new IllegalStateException("Unknown AckType: " + ackType); | ||||||
} | ||||||
} | ||||||
|
||||||
private CompletableFuture<Void> doIndividualAck(MessageIdAdv messageId, Map<String, Long> properties) { | ||||||
if (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty())) { | ||||||
private CompletableFuture<Void> doIndividualAck(MessageIdAdv messageId, Map<String, Long> properties, | ||||||
boolean queueDueToConnecting) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method invocations like You should add a new method like private CompletableFuture<Void> queueIndividualAck(MessageIdAdv messageId) {
Optional<Lock> readLock = acquireReadLock();
try {
doIndividualAckAsync(messageId);
return readLock.map(__ -> currentIndividualAckFuture).orElse(CompletableFuture.completedFuture(null));
} finally {
readLock.ifPresent(Lock::unlock);
if (pendingIndividualAcks.size() >= maxAckGroupSize) {
flush();
}
}
}
private CompletableFuture<Void> doIndividualAck(MessageIdAdv messageId, Map<String, Long> properties) {
if (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty())) {
// We cannot group acks if the delay is 0 or when there are properties attached to it. Fortunately that's an
// uncommon condition since it's only used for the compaction subscription.
return doImmediateAck(messageId, AckType.Individual, properties, null);
} else {
return queueIndividualAck(messageId);
}
} Then you don't have to add private CompletableFuture<Void> doImmediateAck(MessageIdAdv msgId, AckType ackType, Map<String, Long> properties,
BitSetRecyclable bitSet) {
ClientCnx cnx = consumer.getClientCnx();
if (cnx == null && consumer.getState() == HandlerState.State.Connecting) {
if (ackType == AckType.Cumulative) {
return queueCumulativeAck(msgId);
} else {
return queueIndividualAck(msgId);
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I will change the implementation of this PR as #21928 (comment) |
||||||
if (!queueDueToConnecting | ||||||
&& (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty()))) { | ||||||
Comment on lines
+257
to
+258
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will it break the behavior of
If the consumer is reconnecting but the ack has properties. We will also group the acks which is not expected? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @codelipenghui Yes. But this behavior is only added for the pulsar/pulsar-broker/src/main/java/org/apache/pulsar/compaction/TwoPhaseCompactor.java Lines 229 to 230 in 55520bd
Currently, if From my perspective, we should also queue the ACK requests and flush them after connected. @poorbarcode There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agress with @BewareMyPower I am trying to split this PR into the following.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Marked current PR as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's reasonable. BTW, the C++ client is already implemented in such way that:
Though the naming is not good (but consistent with other impl classes in the library) |
||||||
// We cannot group acks if the delay is 0 or when there are properties attached to it. Fortunately that's an | ||||||
// uncommon condition since it's only used for the compaction subscription. | ||||||
return doImmediateAck(messageId, AckType.Individual, properties, null); | ||||||
|
@@ -279,8 +281,10 @@ private CompletableFuture<Void> doIndividualAckAsync(MessageIdAdv messageId) { | |||||
} | ||||||
|
||||||
private CompletableFuture<Void> doIndividualBatchAck(MessageIdAdv batchMessageId, | ||||||
Map<String, Long> properties) { | ||||||
if (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty())) { | ||||||
Map<String, Long> properties, | ||||||
boolean queueDueToConnecting) { | ||||||
if (!queueDueToConnecting | ||||||
&& (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty()))) { | ||||||
return doImmediateBatchIndexAck(batchMessageId, batchMessageId.getBatchIndex(), | ||||||
batchMessageId.getBatchSize(), AckType.Individual, properties); | ||||||
} else { | ||||||
|
@@ -302,9 +306,10 @@ private CompletableFuture<Void> doIndividualBatchAck(MessageIdAdv batchMessageId | |||||
} | ||||||
|
||||||
private CompletableFuture<Void> doCumulativeAck(MessageIdAdv messageId, Map<String, Long> properties, | ||||||
BitSetRecyclable bitSet) { | ||||||
BitSetRecyclable bitSet, boolean queueDueToConnecting) { | ||||||
consumer.getStats().incrementNumAcksSent(consumer.getUnAckedMessageTracker().removeMessagesTill(messageId)); | ||||||
if (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty())) { | ||||||
if (!queueDueToConnecting | ||||||
&& (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty()))) { | ||||||
// We cannot group acks if the delay is 0 or when there are properties attached to it. Fortunately that's an | ||||||
// uncommon condition since it's only used for the compaction subscription. | ||||||
return doImmediateAck(messageId, AckType.Cumulative, properties, bitSet); | ||||||
|
@@ -342,22 +347,31 @@ private void doCumulativeAckAsync(MessageIdAdv msgId, BitSetRecyclable bitSet) { | |||||
} | ||||||
|
||||||
private CompletableFuture<Void> doCumulativeBatchIndexAck(MessageIdAdv batchMessageId, | ||||||
Map<String, Long> properties) { | ||||||
if (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty())) { | ||||||
Map<String, Long> properties, | ||||||
boolean queueDueToConnecting) { | ||||||
if (!queueDueToConnecting | ||||||
&& (acknowledgementGroupTimeMicros == 0 || (properties != null && !properties.isEmpty()))) { | ||||||
return doImmediateBatchIndexAck(batchMessageId, batchMessageId.getBatchIndex(), | ||||||
batchMessageId.getBatchSize(), AckType.Cumulative, properties); | ||||||
} else { | ||||||
BitSetRecyclable bitSet = BitSetRecyclable.create(); | ||||||
bitSet.set(0, batchMessageId.getBatchSize()); | ||||||
bitSet.clear(0, batchMessageId.getBatchIndex() + 1); | ||||||
return doCumulativeAck(batchMessageId, null, bitSet); | ||||||
return doCumulativeAck(batchMessageId, null, bitSet, false); | ||||||
} | ||||||
} | ||||||
|
||||||
private CompletableFuture<Void> doImmediateAck(MessageIdAdv msgId, AckType ackType, Map<String, Long> properties, | ||||||
BitSetRecyclable bitSet) { | ||||||
ClientCnx cnx = consumer.getClientCnx(); | ||||||
|
||||||
if (cnx == null && consumer.getState() == HandlerState.State.Connecting) { | ||||||
if (ackType == AckType.Cumulative) { | ||||||
return doCumulativeAck(msgId, properties, bitSet, true); | ||||||
} else { | ||||||
return doIndividualAck(msgId, properties, true); | ||||||
} | ||||||
} | ||||||
if (cnx == null) { | ||||||
return FutureUtil.failedFuture(new PulsarClientException | ||||||
.ConnectException("Consumer connect fail! consumer state:" + consumer.getState())); | ||||||
|
@@ -369,6 +383,14 @@ private CompletableFuture<Void> doImmediateBatchIndexAck(MessageIdAdv msgId, int | |||||
AckType ackType, Map<String, Long> properties) { | ||||||
ClientCnx cnx = consumer.getClientCnx(); | ||||||
|
||||||
if (cnx == null && consumer.getState() == HandlerState.State.Connecting) { | ||||||
if (ackType == AckType.Cumulative) { | ||||||
return doCumulativeBatchIndexAck(msgId, properties, true); | ||||||
} else { | ||||||
return doIndividualBatchAck(msgId, properties, true); | ||||||
} | ||||||
} | ||||||
|
||||||
if (cnx == null) { | ||||||
return FutureUtil.failedFuture(new PulsarClientException | ||||||
.ConnectException("Consumer connect fail! consumer state:" + consumer.getState())); | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The client should be closed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed