[fix][broker] Fix Key_Shared delivery stall when look-ahead triggers at the end of the topic - #26236
Merged
lhotari merged 2 commits intoJul 25, 2026
Conversation
…at the end of the topic When a replay read in the PIP-379 Key_Shared dispatcher got fully discarded and the cursor had no more entries, engaging look-ahead made the follow-up read skip the replay queue and the delayed delivery tracker while a normal read stayed parked at the end of the topic, stalling dispatch of replayable messages until an unrelated event triggered another read. Engage look-ahead only when the cursor has more entries to read. Fixes the flaky KeySharedSubscriptionTest.testContinueDispatchMessagesWhenMessageDelayed (apache#21554).
nodece
approved these changes
Jul 24, 2026
dao-jun
approved these changes
Jul 24, 2026
…oid repeated read-and-discard When out-of-order delivery is allowed, the replay queue doesn't track sticky key hashes, so the replay position filter cannot exclude messages for consumers without available permits. Skipping look-ahead at the end of the topic then made the dispatcher repeatedly re-read and discard the same undispatchable messages, caught by KeySharedSubscriptionTest.testRecentJoinedPosWillNotStuckOtherConsumer. Engage look-ahead unconditionally for out-of-order delivery, restoring the previous behavior for that mode, and apply the end-of-topic guard only when ordered delivery is required.
void-ptr974
approved these changes
Jul 25, 2026
sandeep-ctds
pushed a commit
to datastax/pulsar
that referenced
this pull request
Jul 31, 2026
…at the end of the topic (apache#26236) (cherry picked from commit 37ceb98) (cherry picked from commit f066cfe)
sandeep-ctds
pushed a commit
to datastax/pulsar
that referenced
this pull request
Jul 31, 2026
…at the end of the topic (apache#26236) (cherry picked from commit 37ceb98) (cherry picked from commit f066cfe)
sandeep-ctds
pushed a commit
to datastax/pulsar
that referenced
this pull request
Jul 31, 2026
…at the end of the topic (apache#26236) (cherry picked from commit 37ceb98) (cherry picked from commit f066cfe)
11 tasks
nodece
pushed a commit
to ascentstream/pulsar
that referenced
this pull request
Aug 28, 2026
…at the end of the topic (apache#26236) (cherry picked from commit 37ceb98) (cherry picked from commit f066cfe)
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.
Fixes #21554
Motivation
KeySharedSubscriptionTest.testContinueDispatchMessagesWhenMessageDelayedhas been flaky for a long time (#21554), failing with assertions such asexpected [80] but found [78]. Analysis of the test-report artifact of a recent CI failure (both attempts of the test failed in this run) and a local reproduction with DEBUG logging showed that this is a genuine dispatch stall in the PIP-379 Key_Shared dispatcher, not a test-side problem.The stall arises from the following sequence:
havePendingRead=true). This is the normal state for a caught-up subscription.getMessagesToReplayNowpulls them from the delayed delivery tracker in permit-limited batches, so some already-due messages remain in the tracker.InMemoryDelayedDeliveryTracker.updateTimerintentionally doesn't arm a timer for already-due messages, on the assumption that a subsequentreadMoreEntriescall will pull them.skipNextReplayToTriggerLookAhead=true.readMoreEntriescall consumes the flag and skips both the replay queue and the delayed-tracker pull — and because the cursor has no more entries, it just finds the parked pending read and gives up ("Cannot schedule next read until previous one is done"). The flag has consumed the only pending trigger."Skipping replay while awaiting previous read to complete"), acknowledgments don't advance the mark-delete position (the other consumer holds an unacked backlog, somarkDeletePositionMoveForwardnever fires), the delayed tracker has no timer armed, andcheckAndUnblockIfStuckdoesn't consider the dispatcher stuck because a (parked) pending read exists.Dispatch then stalls until an unrelated event triggers a read. In the test, that's the blocked consumer's flow request 30 seconds later — by which time the test has already given up on the first consumer, so the stalled messages hashed to it are delivered into a receiver queue nobody reads anymore and count as missing.
DEBUG-log excerpt from a local reproduction (1 of 20 runs, masked by the test retry analyzer) showing the stall onset — the 5-entry replay batch discarded and every remaining trigger dropped, followed by 30 seconds of silence:
Modifications
PersistentStickyKeyDispatcherMultipleConsumers.trySendMessagesToConsumersonly when the cursor has more entries to read. When the cursor is caught up there is nothing to look ahead at, so skipping the next replay could only cause the stall described above. With the change, the follow-up read replays the replay queue and pulls due messages from the delayed delivery tracker instead of parking a normal read at the end of the topic. TheReplayPositionFilterkeeps this loop-free for ordered delivery: once a discarded message's hash is recorded in the replay queue, subsequent replay selections exclude messages for consumers without available permits.KeySharedSubscriptionTest.testRecentJoinedPosWillNotStuckOtherConsumer[PIP379, allowOutOfOrder=true]in the first CI run of this PR (replay read count 12229 vs the asserted maximum of 200, reproduced locally with 32662).PersistentStickyKeyDispatcherMultipleConsumersTest.testLookAheadNotEngagedWhenCursorHasNoMoreEntries, that deterministically reproduces the stall shape at the dispatcher level. It fails without the production change and passes with it.Verifying this change
This change added tests and can be verified as follows:
PersistentStickyKeyDispatcherMultipleConsumersTest.testLookAheadNotEngagedWhenCursorHasNoMoreEntriesfails without the production change (the replayed message for the consumer with available permits is never dispatched) and passes with it.KeySharedSubscriptionTest.testContinueDispatchMessagesWhenMessageDelayedlocally withinvocationCount = 20for the PIP379 implementation: without the fix, 1 of 20 runs hit the stall (a ~70 s run where 5 of 80 messages were delivered only after a 30 s stall, masked by the retry analyzer); with the fix, 20 of 20 runs completed in ≤ 41 s with all 80 messages delivered.KeySharedSubscriptionTest.testRecentJoinedPosWillNotStuckOtherConsumerlocally: with the end-of-topic guard applied to out-of-order delivery as well, the[PIP379, true]variant hit a repeated read-and-discard loop (replay read count 32662); with look-ahead kept unconditional for out-of-order delivery, all four variants pass with replay read counts of 50/7/56/12. The fullKeySharedSubscriptionTestclass passes locally.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes