Skip to content

fix(transaction): end the server Accepted state on the matching ACK (review follow-up to #164) - #169

Merged
shenjinti merged 1 commit into
mainfrom
rfc6026-lifecycle-fix
Oct 6, 2026
Merged

shenjinti merged 1 commit into
mainfrom
rfc6026-lifecycle-fix

Conversation

@shenjinti

Copy link
Copy Markdown
Contributor

Follow-up review of #164 (RFC 6026 adoption).

Bug found: after a server dialog was confirmed by the ACK, the Accepted transaction stayed in endpoint.transactions until Timer L (64*T1) because nothing drains it once the dialog's receive loop breaks. The process_timer fallback detaches with detach(key, None), which does not clean waiting_ack — so every confirmed call leaked a waiting_ack entry for the process lifetime, on top of the 32s table residency.

Fix: a matching ACK ends the Accepted state immediately. Retransmitted ACKs and late INVITE retransmissions are still absorbed below the TU via finished_transactions (identical to the pre-#164 post-ACK behavior). handle_reinvite now tracks acked explicitly so the no-ACK timeout path cannot fire on a confirmed re-INVITE. Also drops a provably-dead defensive branch.

Verification: new test_accepted_lifecycle suite asserts server tables are clean immediately after the ACK and the client Accepted transaction detaches exactly at Timer M (with the ACK cached); 377 lib + 65 doc tests green across repeated runs, fmt clean, clippy at the main baseline.

Follow-up review of #164 found that a confirmed server dialog left the
Accepted transaction parked in the endpoint's table until Timer L
(64*T1): nobody drains it after the dialog's receive loop breaks, and
the process_timer fallback detaches with detach(key, None), which does
not clean waiting_ack — so every confirmed call also leaked a
waiting_ack entry for the process lifetime.

- a matching ACK now ends the Accepted state immediately (Terminated):
  Timer L/G are cancelled, waiting_ack is removed, and the 2xx stays
  cached in finished_transactions so retransmitted ACKs and late INVITE
  retransmissions are absorbed below the TU (the pre-#164 post-ACK
  behavior)
- handle_reinvite (InviteDialog + ServerInviteDialog) tracks 'acked'
  explicitly and passes answered_2xx && !acked to end_session_without_ack,
  so the timeout path cannot fire on a confirmed re-INVITE now that the
  ACK ends the transaction
- drop the dead 2xx-suppression guard in the Completed Timer G arm
  (server 2xx never routes to Completed)
- new test_accepted_lifecycle suite: server tables are clean right after
  the ACK; the client Accepted transaction detaches exactly at Timer M
  with the ACK kept cached
@shenjinti
shenjinti merged commit 52ac300 into main Oct 6, 2026
3 checks passed
shenjinti added a commit that referenced this pull request Oct 6, 2026
Ships this round on top of 0.7.0: proxy-mode auto_ack_2xx (#146/#172),
ACK CSeq routing (#155/#170/#172), digest auth_username (#153), dead
stream retirement (#161), remote-ack getter (#157), UAS ACK timeout →
BYE (#149/#169), RFC 6026 Accepted state + Timer L/M with documented
deviations (#164/#169), CANCEL/2xx race handling (#162/#171), in-dialog
Via transport (#163), flow-reuse and ACK hardening tests.
shenjinti pushed a commit that referenced this pull request Oct 7, 2026
… ends it

Since #169 the matching ACK terminates an Accepted server INVITE
transaction, and cleanup() took last_response before the dialog built
DialogState::Confirmed from it. Confirmed carried Response::default()
(no CSeq) for the initial INVITE and every re-INVITE, so a TU could not
tell which INVITE it confirms.

The server INVITE transaction now keeps last_response and hands a copy
to finished_transactions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant