Conversation
DialogInner::transition sent the new state to subscribers before deciding whether to apply it, so ignored transitions were still notified: a second Terminated when two teardown paths race (a local BYE completing while the peer's BYE is handled), a Confirmed after Terminated when a mid-dialog request (INFO/UPDATE/REFER/...) is answered after a BYE and return_to_confirmed runs, and a WaitAck on an already confirmed dialog. Decide first, update the state, then send the notification while still holding the state lock, so every notification describes a transition that happened and notifications follow the order of state changes. Event-only variants (Updated/Notify/Info/Options) are sent as before. Adds an end-to-end test over UDP through DialogLayer: the peer sends an INFO, then a BYE before the application answers it; once the INFO is answered, no Confirmed may follow the Terminated notification.
tgeorge06
force-pushed
the
fix/no-notification-after-terminated
branch
from
September 27, 2026 14:51
1c62519 to
fb7eb5f
Compare
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.
Problem
DialogInner::transition(src/dialog/dialog.rs) sends the new state onstate_senderbefore it decides whether to apply it. When the transition is then ignored, subscribers have still been told about it:Terminated: two teardown paths can end the same dialog. For example, a local BYE completes while the peer's BYE is also handled. Each path callstransition(Terminated). The second call is ignored ("dialog already terminated") but has already been notified, so subscribers getTerminatedtwice. Anything that does teardown work onTerminated(call records, cleanup, releasing resources) runs twice.ConfirmedafterTerminated: since 4feaf95, INFO/UPDATE/REFER/MESSAGE/NOTIFY handlers callreturn_to_confirmedonce the request is answered. If a BYE lands while the application is still holding the INFO handle, the dialog terminates first. The latereturn_to_confirmedis then ignored but still notified, so subscribers seeTerminated → Confirmed, which cannot happen.WaitAckon a confirmed dialog: this transition is also ignored but still notified.The dialog's stored state was always correct. Only the notification stream was wrong.
Reproduction
src/dialog/tests/test_state_after_terminated.rsreproduces it end to end through the public API: a UAS built the usual way (incoming_transactions→get_or_create_server_invite/match_dialog→handle) over UDP; the peer sends INFO, then BYE before the application answers the INFO; once the application answers,mainnotifiesConfirmedafterTerminated:The unit tests in
src/dialog/tests/test_dialog_states.rspin each case and fail like this on currentmain:test_dialog_lifecycle_notifies_each_state_oncechecks that the normal lifecycle (Calling → Trying → Early → Confirmed → Terminated) still notifies each state exactly once and in order. It passes both before and after the change.Fix
In
transition, for state-changing variants:Sending under the lock means the order of notifications matches the order of state changes when two tasks transition at the same time. The channel is an unbounded tokio mpsc, so
sendnever blocks or runs user code. Holding the parking_lot mutex across it is safe.The event-only variants (
Updated,Notify,Info,Options) are unchanged. They are sent unconditionally and do not touch the stored state. They carry aTransactionHandlethe application must answer, so they are passed through as before.Diff: 8 lines changed in
dialog.rs, plus tests.Compatibility / risk
Terminated, or on aWaitAckfor an already-confirmed dialog, will no longer get it. Those events never described a real state change. No in-repo consumer (src/,examples/,bench_ua) relies on them.dialog.state()right after receiving a notification now sees the notified state. Before, it could still see the old one.Checks
cargo fmt --all -- --check: cleancargo build: okcargo test: 335 lib tests passed (5 new), 65 doc-tests passed, 0 failedcargo clippy --all-targets: no new warnings. The existingnever_looperror in lib tests is unchanged frommain.