Repository navigation
Conversation
When the `do_invite` future is dropped mid-INVITE, `DialogGuardForUnconfirmed` finds the dialog by removing it from the `DialogLayer` and did nothing when it was not there. An application that called `remove_dialog` before dropping the future (for example on its own hangup) therefore got no `Terminated`, no CANCEL after a provisional, and no BYE for a 2xx that answered the abandoned INVITE, leaving the callee in a session. The guard now keeps the INVITE's dialog and falls back to it when the layer entry is gone, until `process_invite` returns. A dialog that is still registered is handled exactly as before.
shenjinti
added a commit
that referenced
this pull request
Oct 7, 2026
Follow-up to #183. The removed-dialog variant of the drop guard is only exercised with a crossing 2xx in the InviteOkFirst ordering. Two gaps: - A CANCEL that wins the race (487 to the INVITE) with the dialog already removed: run_cancel_answered_487 now takes a provisional code and a remove flag, so test_removed_cancel_answered_487_sends_no_bye covers Early (180) and Trying (100) — CANCEL, ACK of the 487, no BYE, exactly one Terminated(UacCancel), never Confirmed. The pre-existing test gains the same Terminated/Confirmed assertions. - The CancelOkFirst wire ordering (200 to the CANCEL before the 2xx) with the dialog removed: test_removed_dialog_2xx_after_the_cancel_response_is_acked_and_byed.
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 #182.
Problem
DialogGuardForUnconfirmed::drop(invitation.rs:192) finds the dialog by removing it from theDialogLayerand returns when it is not there (line 194). An application that calledremove_dialogfor the pending call before dropping thedo_invitefuture therefore gets no CANCEL, noTerminated, and no ACK / BYE for a 2xx that answers the abandoned INVITE (#162, #171 do all of that only for a dialog still in the layer).remove_dialog(dialog_layer.rs:509-513) only drops the registry entry and cancels the dialog'scancel_token; it does not end the INVITE.Spec
Fix
The guard keeps a clone of the INVITE's dialog (
dialog: Option<InviteDialog>).do_inviteclears it as soon asprocess_invitereturns. Indrop:Some(Dialog::Invite(..))is ended,Some(_)of another kind returns);Calling:Terminated(UacCancel)now, CANCEL after the first provisional, ACK + BYE for a 2xx;Trying/Early: CANCEL, ACK + BYE for a crossing 2xx,Terminated(UacCancel));process_invitereturned: nothing to do, as before (do_invitehandles the outcome).The rest of
dropis unchanged. The diff is +14 / -6 insrc/dialog/invitation.rs.Unchanged on purpose:
Arc.do_invite_asynchas no drop guard and is not touched.do_invite, a 2xx still completesdo_invite, which registers the confirmed dialog again under its new id (invitation.rs:713) and returns it to the caller. That is the caller's own call and is not changed here.Contract / coverage
What dropping the
do_invitefuture does, by dialog state, when the application already removed the dialog withremove_dialog:CallingTerminatedTerminated(UacCancel)at once, CANCEL after the 180, 487 ACKedCallingTerminatedTerminated(UacCancel), ACK, BYECallingTerminatedTerminated(UacCancel), ACK, no BYETrying(100)TerminatedTerminated(UacCancel)Early(180)TerminatedTerminated(UacCancel)In every row the call never reports
Confirmedand reportsTerminatedonce (DialogInner::transitiondrops anything after the firstTerminated, #148). Each row is a test below.Tests
src/dialog/tests/test_cancel_2xx_race.rs.run_crossing_2xxandrun_dropped_before_provisionalget aremoveflag: when set, the test removes the dialog withDialogLayer::remove_dialogunder the id theCallingstate reported, asserts the layer is empty, then drops thedo_invitefuture. The existing tests run withremove = falseand are otherwise unchanged;run_crossing_2xxalso takes the provisional (180 for the existing tests).test_removed_dialog_2xx_crossing_the_cancel_is_acked_and_byed: after a 180 and after a 100, a 200 crossing the CANCEL is ACKed and BYE'd in the dialog it established, a retransmitted 200 is re-ACKed with no second BYE, oneTerminated(UacCancel), noConfirmed.test_removed_before_provisional_is_cancelled_after_the_180,test_removed_before_provisional_2xx_is_acked_and_byed,test_removed_before_provisional_final_failure_is_acked_only: dropped inCalling;Terminated(UacCancel)at once, nothing sent before a provisional, then CANCEL (180), ACK + BYE (200) or ACK only (486).On
main(3bdd74c) all four fail, the others pass:("state channel closed": no
Terminatedwas reported before the dialog went away. The 100 variant alone fails the same way, at the CANCEL.)Checks
cargo test --features bench: 402 lib tests passed, 0 failed (398 onmainplus the four new ones), and 65 doc tests passed. Plaincargo testpasses too. Thetest_cancel_2xx_racetests passed 20 runs in a row.cargo check --no-default-features --features platform-embassy: the same output as onmain.cargo clippy --features bench --all-targets: the same warnings as onmain, none in the changed code. (Onmainit stops at aclippy::never_looperror insrc/dialog/tests/test_refer_notify.rs:98, unrelated to this PR; with-A clippy::never_loopthe warnings are the same as onmain.)rustfmt --checkon the changed files: clean.