[fix][broker] Debit un-acked messages only when the consumer is actually removed - #26422
Open
FlorentinDUBOIS wants to merge 1 commit into
Open
[fix][broker] Debit un-acked messages only when the consumer is actually removed#26422FlorentinDUBOIS wants to merge 1 commit into
FlorentinDUBOIS wants to merge 1 commit into
Conversation
…lly removed
### Motivation
`PersistentDispatcherMultipleConsumers#removeConsumer` debits the subscription's
un-acknowledged message count by the departing consumer's own count before it
establishes whether that consumer is still registered:
addUnAckedMessages(-consumer.getUnackedMessages());
if (consumerSet.removeAll(consumer) == 1) {
The `else` branch below is the defensive path added by apache#22270 for a consumer that is
still in `consumerList` but no longer in `consumerSet`, so that the topic can still be
unloaded. Reaching it means the consumer was already removed once, and the first
removal already debited its un-acknowledged messages. The unguarded debit therefore
subtracts them a second time and drives `totalUnackedMessages` negative.
That counter is what `maxUnackedMessagesOnSubscription` throttles on, and nothing
resets it while the dispatcher lives — `clearComponentsAfterRemovedAllConsumers()`
resets the available-permits aggregate but deliberately leaves it alone. A negative
value therefore silently disables the throttle for the lifetime of the dispatcher,
and since `addUnAckedMessages` also feeds the broker-wide counter, the drift is not
confined to one subscription.
`PersistentDispatcherMultipleConsumersClassic#removeConsumer` carries the identical
unguarded debit.
### Modifications
Move the debit inside the `consumerSet.removeAll(consumer) == 1` guard in both the
current and the classic dispatcher, so that only the removal which actually
unregisters the consumer accounts for it. The defensive branch needs no debit of its
own, for the same reason: the first removal already made it — a comment in that
branch now records the invariant.
### Verifying this change
Adds `SharedSubscriptionUnackedMessagesAccountingTest`, which leaves a consumer
holding ten un-acknowledged deliveries, removes it twice and requires the
subscription counter to end at zero — once against the current dispatcher and once
against the classic one behind the dynamic
`subscriptionSharedUseClassicPersistentImplementation` flag. Without this change
both end at -10.
This is broker-internal accounting: no public API, configuration or wire-protocol
change.
Signed-off-by: Florentin Dubois <florentin.dubois@clever.cloud>
10 tasks
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.
Relates to #26416. Carved out of #26417 following the review discussion there: the available-permits half of that PR is superseded by #26289, while this double-debit of the un-acknowledged message counter is an independent defect that neither master nor #26289 addresses, so it gets its own PR, review, and backport decision.
Motivation
PersistentDispatcherMultipleConsumers#removeConsumerdebits the subscription's un-acknowledged message count by the departing consumer's own count before it establishes whether that consumer is still registered:The
elsebranch below is the defensive path added by #22270 for a consumer that is still inconsumerListbut no longer inconsumerSet, so that the topic can still be unloaded. Reaching it means the consumer was already removed once, and the first removal already debited its un-acknowledged messages. The unguarded debit therefore subtracts them a second time and drivestotalUnackedMessagesnegative.That counter is what
maxUnackedMessagesOnSubscriptionthrottles on, and nothing resets it while the dispatcher lives —clearComponentsAfterRemovedAllConsumers()resets the available-permits aggregate but deliberately leaves it alone. A negative value therefore silently raises the effective limit for the lifetime of the dispatcher, and sinceaddUnAckedMessagesalso feeds the broker-wide counter, the drift is not confined to one subscription.PersistentDispatcherMultipleConsumersClassic#removeConsumer— the documented PIP-379 rollback path, selectable at runtime through the dynamicsubscriptionSharedUseClassicPersistentImplementationflag — carries the identical unguarded debit.Modifications
addUnAckedMessages(-consumer.getUnackedMessages())inside theconsumerSet.removeAll(consumer) == 1guard inPersistentDispatcherMultipleConsumers, so that only the removal which actually unregisters the consumer accounts for it. The defensive branch needs no debit of its own, for the same reason: the first removal already made it — a comment in that branch now records the invariant.PersistentDispatcherMultipleConsumersClassic.PersistentStickyKeyDispatcherMultipleConsumers(Key_Shared) inherits the fix throughsuper.removeConsumer.Verifying this change
This change added tests and can be verified as follows:
SharedSubscriptionUnackedMessagesAccountingTest#testRemovingSameConsumerTwiceDebitsUnackedMessagesOnce— carried over verbatim from [fix][broker] Debit only credited permits when removing a Shared consumer #26417: leaves a consumer holding ten un-acknowledged deliveries, removes it twice, and requires the subscription counter to end at zero. Fails on master (expected [0] but found [-10]), passes with this change.SharedSubscriptionUnackedMessagesAccountingTest#testRemovingSameConsumerTwiceDebitsUnackedMessagesOnceOnClassicDispatcher— the same probe against the classic dispatcher, flipped in for the duration of the test through the dynamic flag and restored afterwards. Fails on master with the same-10, passes with this change.Both tests were run red-first against the unmodified production code, then green with the fix applied.
branch-4.0andbranch-4.2carry the same unguarded debit in both dispatchers, so this should backport cleanly; the tests ride along for that purpose.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes