[fix][client] Scalable topics: stream consumer must not acknowledge on close or topic-removal#26172
Open
merlimat wants to merge 1 commit into
Open
[fix][client] Scalable topics: stream consumer must not acknowledge on close or topic-removal#26172merlimat wants to merge 1 commit into
merlimat wants to merge 1 commit into
Conversation
…n close or topic-removal The namespace stream consumer's per-topic close path (MultiTopicStreamConsumer.closeTopic) flushed a cumulative ack up to latestDeliveredPerTopicSegment for the topic before closing its per-topic consumer. That map tracks the latest message pumped into the client-side multiplexed queue (the prefetch frontier), not what the application received via receive() — it is the only site that reads the live map instead of a per-message frozen snapshot. As a result, closing the consumer — or any topic leaving the matching set — acknowledged every message prefetched into the mux, including messages the application never received. On a crash or close right after, those messages are silently lost: an at-least-once violation. Measured with two topics and 12 messages produced, closing after receiving only one (with acknowledgeCumulative never called) dropped the other 11. Acknowledgment on a stream consumer is cumulative and always explicit. Drop the flush entirely: close() and topic-removal detach the per-topic consumer without acknowledging; anything delivered-but-unacked is redelivered on the next attach (at-least-once). A cumulative ack whose vector references a since-removed topic skips that topic's slice. The single-topic ScalableStreamConsumer has no such flush and is unaffected. The explicit acknowledgeCumulative position vector is a frozen enqueue-time snapshot and, because the multiplexed queue is FIFO, is exact — it never covers unreceived messages. Assisted-by: Claude Code (Opus 4.8)
lhotari
approved these changes
Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The namespace (multi-topic) v5 stream consumer violates at-least-once on close.
MultiTopicStreamConsumer.closeTopic()flushes a cumulative ack up tolatestDeliveredPerTopicSegmentfor each topic before closing its per-topic consumer. That map tracks the latest message pumped into the client-side multiplexed queue — the prefetch frontier — not what the application actually received viareceive(). It is the only site that reads the live map instead of a per-message frozen snapshot.So closing the consumer — or any topic leaving the matching set — acknowledges every message prefetched into the mux, including messages the application never received. On a crash or clean close right after, those messages are silently lost. Measured with two 1-segment topics and 12 messages produced: receiving only one message, then closing (with
acknowledgeCumulativenever called), silently acks the other 11 — they are neither received by the consumer nor redelivered to a re-subscribe.The single-topic
ScalableStreamConsumerhas no such flush and is unaffected. The explicitacknowledgeCumulativeposition vector is a frozen enqueue-time snapshot and, because the multiplexed queue is FIFO, is exact — it never covers unreceived messages. Only the close/removal flush is wrong.Modifications
MultiTopicStreamConsumer.closeTopic(). Acknowledgment on a stream consumer is cumulative and always explicit:close()and a topic leaving the matching set now detach the per-topic consumer without acknowledging. Anything delivered-but-unacked is redelivered on the next attach (at-least-once). The per-topic delivery-tracking entry is still removed (memory hygiene).Verifying this change
This change added tests and can be verified as follows:
V5MultiTopicStreamConsumerTest.closeWithoutAckDoesNotAcknowledgeAnything: produce to two topics, receive every message but acknowledge nothing, close, then assert at the broker that the full backlog remains — i.e. nothing was acknowledged on close. Verified to fail before this change (backlog drops to 0) and pass after.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes