Conversation
…tes (apache#289) resend() iterated unconfirmedPacketIds.values() with a for-each loop and called confirm(...) inside the loop for entries past maxResendTimes. confirm() removes entries from the same map through a different iterator, so the outer iterator throws ConcurrentModificationException once another entry remains after the dropped head. The exception escapes the scheduled task, so flush(true) and scheduleResend() never run: the retransmission chain dies and the session silently stops delivering messages. Collect the entries to drop and process them after the iteration. Regression test: resendDroppingHeadAbortsDropOfRemaining - two QoS1 messages, acknowledge neither (the existing partial-ack test leaves a single in-flight entry, so it can never trigger the CME). Control experiment: fails before the fix (1 dropped), passes after (2 dropped). Full TransientSessionHandlerTest (64) and PersistentSessionHandlerTest (23) pass.
Contributor
Author
|
Noted while preparing the regression test — pre-existing behaviour, not introduced by this PR:
|
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.
Baseline:
main @ eef5e3af(the PR's base commit).Summary
resend()iteratedunconfirmedPacketIds.values()with a for-each loop and calledconfirm(...)inside the loop for entries pastmaxResendTimes.confirm()removes entries from the same map through a different iterator, so the outer iterator throwsConcurrentModificationExceptiononce another entry remains after the dropped head.The exception escapes the scheduled task, so
flush(true)andscheduleResend()never run. The retransmission chain dies permanently: remaining in-flight entries are neither retransmitted nor dropped,unconfirmedPacketIdsstays non-empty,clientReceiveQuota()stays at 0, and the session silently stops delivering messages.Root cause
All five other
confirm()call sites (:1115,:1122,:1152,:1161,:1166) already defer throughctx.executor().execute(...)to avoid exactly this — only the one insideresend()is a direct call.Change
Collect the entries to drop into a local list and process them after the iteration completes.
Testing
Regression test
MQTT3TransientSessionHandlerTest.resendDroppingHeadAbortsDropOfRemaining:MaxResendTimes=1,ResendTimeoutSeconds=1,ReceivingMaximum=2; publish two QoS1 messages and acknowledge neither — the crucial difference from the existingqoS1ResendKeepsRunningAfterPartialAck, which acks the first message and therefore leaves a single in-flight entry whose iterator never has a next element, so it can never trigger the CME.Control experiment on the unfixed code: the assertion fails with
Wanted 2 times, But was 1 time(the loop aborts right after dropping the head). With the fix: passes. FullMQTT3TransientSessionHandlerTest(64 tests) andMQTT3PersistentSessionHandlerTest(23 tests) pass.Full report with reproduction details: #289.
Fixes #289