You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
newBegin was computed after the resend loop by checking appMessageJustSent, but that flag was reset to false at the top of every iteration — so it only reflected whether the last message in the range was an app message. If the range ended with one or more admin messages after a resent app message, newBegin stayed at beginSeqNo instead of advancing past the last resent app message, producing a wrong NextExpectedMsgSeqNum or a spurious sequence reset.
A second related issue existed in the post-loop code: when a trailing admin gap-fill (generateSequenceReset) was emitted for a non-zero begin, newBegin was not advanced past it, causing the subsequent generateSequenceResetIfNeeded call to overlap the same sequence range. This affected both the enableNextExpectedMsgSeqNum=false branch and the enableNextExpectedMsgSeqNum=true branch — in the latter, an else guard caused the trailing gap-fill SequenceReset to be omitted entirely rather than emitted with a wrong sequence number.
Move int newBegin = beginSeqNo declaration to before the loop
Update newBegin = msgSeqNum + 1 in-place at the point where an app message is resent (alongside the existing begin = 0)
Remove the now-redundant post-loop appMessageJustSent flag and its conditional block
Unify the enableNextExpectedMsgSeqNum and else post-loop branches: remove the else guard in the enableNextExpectedMsgSeqNum branch so generateSequenceResetIfNeeded is always called, and advance newBegin = msgSeqNum + 1 after any trailing admin gap-fill in both branches
SessionTest.java
Add regression test resendMessages_whenLastRetrievedMessageIsAdmin_finalSequenceResetShouldNotOverlapResentAppMessages covering the scenario where the last retrieved persisted message is an admin message (Heartbeat) with enableNextExpectedMsgSeqNum=false
Add regression test resendMessages_whenLastRetrievedMessageIsAdmin_finalSequenceResetShouldNotOverlapResentAppMessages_withEnableNextExpectedMsgSeqNum covering the same scenario with enableNextExpectedMsgSeqNum=true
Both tests share a common doResendMessages_whenLastRetrievedMessageIsAdmin_scenario(boolean) driver
// Before: only correct if the last message in the range was an app messageintnewBegin = beginSeqNo;
if (appMessageJustSent) { // false if loop ended on admin msgnewBegin = msgSeqNum + 1;
}
// After: updated at the exact point each app message is resent,// and again after any trailing admin gap-fill; branches unifiedbegin = 0;
newBegin = msgSeqNum + 1; // always reflects last resent app msg// post-loop (same for both enableNextExpectedMsgSeqNum values):if (begin != 0) {
generateSequenceReset(receivedMessage, begin, msgSeqNum + 1);
newBegin = msgSeqNum + 1; // advance past the trailing admin gap-fill
}
generateSequenceResetIfNeeded(receivedMessage, newBegin, endSeqNo, msgSeqNum);
chrjohn
changed the title
fix: correctly track newBegin when resend range ends with admin messages
correctly track begin seqnum when resend range ends with admin messages
Jun 30, 2026
@copilot please copy the unit test from #1194 to this branch.
Done — the regression test from PR #1194 has been added (commit test: add regression test for issue #344 and fix newBegin after trailing admin gap-fill).
Running it also exposed that the original fix was incomplete: after the post-loop generateSequenceReset(begin, msgSeqNum+1) that covers a trailing admin gap, newBegin wasn't advanced, causing generateSequenceResetIfNeeded to overlap the same range. Added newBegin = msgSeqNum + 1 after that call to fix it. All 74 SessionTest tests pass.
@copilot
please copy the unit test from #1194 to this branch. The test has been updated.
Done. The updated test from PR #1194 has been copied (commit fix+test: unify post-loop branch and add enableNextExpectedMsgSeqNum regression test).
This also required a companion fix in Session.java: the enableNextExpectedMsgSeqNum=true post-loop branch had an else guard that silently skipped generateSequenceResetIfNeeded whenever begin != 0, so the trailing gap-fill SequenceReset was never emitted in that case. Removing the else and adding newBegin = msgSeqNum + 1 (same as the else-branch) makes both paths identical and the new withEnableNextExpectedMsgSeqNum test now passes alongside the existing one. All 75 SessionTest tests pass.
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
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.
newBeginwas computed after the resend loop by checkingappMessageJustSent, but that flag was reset tofalseat the top of every iteration — so it only reflected whether the last message in the range was an app message. If the range ended with one or more admin messages after a resent app message,newBeginstayed atbeginSeqNoinstead of advancing past the last resent app message, producing a wrongNextExpectedMsgSeqNumor a spurious sequence reset.A second related issue existed in the post-loop code: when a trailing admin gap-fill (
generateSequenceReset) was emitted for a non-zerobegin,newBeginwas not advanced past it, causing the subsequentgenerateSequenceResetIfNeededcall to overlap the same sequence range. This affected both theenableNextExpectedMsgSeqNum=falsebranch and theenableNextExpectedMsgSeqNum=truebranch — in the latter, anelseguard caused the trailing gap-fillSequenceResetto be omitted entirely rather than emitted with a wrong sequence number.Changes
Session.java—resendMessages/ ResendRequest handling loopint newBegin = beginSeqNodeclaration to before the loopnewBegin = msgSeqNum + 1in-place at the point where an app message is resent (alongside the existingbegin = 0)appMessageJustSentflag and its conditional blockenableNextExpectedMsgSeqNumand else post-loop branches: remove theelseguard in theenableNextExpectedMsgSeqNumbranch sogenerateSequenceResetIfNeededis always called, and advancenewBegin = msgSeqNum + 1after any trailing admin gap-fill in both branchesSessionTest.javaresendMessages_whenLastRetrievedMessageIsAdmin_finalSequenceResetShouldNotOverlapResentAppMessagescovering the scenario where the last retrieved persisted message is an admin message (Heartbeat) withenableNextExpectedMsgSeqNum=falseresendMessages_whenLastRetrievedMessageIsAdmin_finalSequenceResetShouldNotOverlapResentAppMessages_withEnableNextExpectedMsgSeqNumcovering the same scenario withenableNextExpectedMsgSeqNum=truedoResendMessages_whenLastRetrievedMessageIsAdmin_scenario(boolean)driver