Skip to content

fix(dialog): end the INVITE of an aborted do_invite_async task - #205

Merged
yeoleobun merged 3 commits into
restsend:mainfrom
tgeorge06:fix/do-invite-async-drop-guard
Oct 12, 2026
Merged

yeoleobun merged 3 commits into
restsend:mainfrom
tgeorge06:fix/do-invite-async-drop-guard

Conversation

@tgeorge06

Copy link
Copy Markdown
Contributor

Fixes #204.

Problem

DialogLayer::do_invite_async drives the INVITE in a spawned task (src/dialog/invitation.rs:786-787 at current main, 6fd9414) that holds no drop guard. Aborting that task before the final response drops the INVITE client transaction: no CANCEL, a 2xx is neither ACKed nor ended with a BYE, the dialog stays in Calling or Early, and its early entry stays in the dialog layer. do_invite has handled the same drop correctly since #171.

Expected

The same as a dropped do_invite future. RFC 3261 §9.1:

If no provisional response has been received, the CANCEL request MUST NOT be sent; rather, the client MUST wait for the arrival of a provisional response before sending the request.

and a 2xx that the CANCEL did not prevent is ACKed and ended with a BYE (§9.1, §15).

Fix

The spawned task now holds the same DialogGuardForUnconfirmed that do_invite uses (invitation.rs:186-339), set up exactly as in do_invite: the guard owns the transaction while process_invite runs, is disarmed (dialog = None) when it returns, and the transaction is taken back for the Accepted-state drainer. On abort the guard's existing Drop runs: CANCEL at once in Trying/Early, on the first provisional in Calling, ACK + BYE for a 2xx, layer entry removed. +19 / -2 lines in src/dialog/invitation.rs; the rest of the +81 / -2 is tests.

Unchanged on purpose:

  • A task that runs to completion behaves as before: the early key is removed before the guard drops (which then finds no entry and returns), and a 2xx registered under its confirmed id is neither removed nor hung up.
  • Dropping the JoinHandle without abort() still detaches the task, which keeps running. On platform-embassy there is no abort(), so nothing changes there.
  • Transaction::receive is not cancel-safe: an abort that lands while receive() is processing a 2xx can still lose it, exactly as for a dropped do_invite future today. That is a separate, shared issue.

Contract / coverage

do_invite_async task aborted before the final response:

aborted in peer then sends before after
Calling 180 nothing CANCEL; 487 ACKed
Calling 200 nothing (no ACK, no BYE) ACK + BYE
Early (none needed) nothing CANCEL at once; 487 ACKed
any early entry left in the layer removed

Tests

src/dialog/tests/test_cancel_2xx_race.rs, against a raw UDP peer: test_aborted_invite_task_in_calling_is_cancelled_after_the_180, test_aborted_invite_task_in_calling_2xx_is_acked_and_byed, test_aborted_invite_task_in_early_is_cancelled. Each also asserts the layer is empty afterwards. On main (6fd9414) all three fail: timeout waiting for CANCEL (both CANCEL cases) and timeout waiting for ACK (the 2xx case).

Checks

yeoleobun added a commit to tgeorge06/rsipstack that referenced this pull request Oct 12, 2026
Create the INVITE cleanup guard before spawning so an abort before the first poll removes the early dialog without sending a request.

Run the existing CANCEL operation independently until its response or transaction timeout, rather than dropping it after two seconds or an INVITE final response. Keep waiting for the INVITE final response and ACK/BYE cleanup.

Add regressions for immediate abort and CANCEL retransmission beyond the old deadline and after an INVITE final response. All three fail on the original PR; 426 library tests and the platform-embassy compile check pass with this fix.

Refs restsend#204, restsend#205.
tgeorge06 and others added 2 commits October 12, 2026 20:47
do_invite_async drove the INVITE in a spawned task with no drop guard.
Aborting that task before the final response dropped the transaction and
left the early dialog registered in the layer: no CANCEL, a 2xx was
neither ACKed nor ended with a BYE, and the dialog stayed in Calling or
Early.

The task now holds the same DialogGuardForUnconfirmed as do_invite, so an
aborted task ends the INVITE like a dropped do_invite future: CANCEL at
once in Trying or Early, on the first provisional in Calling (RFC 3261
section 9.1), ACK + BYE for a 2xx, and the layer entry is removed.
Create the INVITE cleanup guard before spawning so an abort before the first poll removes the early dialog without sending a request.

Run the existing CANCEL operation independently until its response or transaction timeout, rather than dropping it after two seconds or an INVITE final response. Keep waiting for the INVITE final response and ACK/BYE cleanup.

Add regressions for immediate abort and CANCEL retransmission beyond the old deadline and after an INVITE final response. All three fail on the original PR; 426 library tests and the platform-embassy compile check pass with this fix.

Refs restsend#204, restsend#205.
@yeoleobun
yeoleobun force-pushed the fix/do-invite-async-drop-guard branch from 02dcae9 to d1ebf28 Compare October 12, 2026 12:48
@yeoleobun yeoleobun closed this Oct 12, 2026
@yeoleobun yeoleobun reopened this Oct 12, 2026
@yeoleobun
yeoleobun merged commit 117869d into restsend:main Oct 12, 2026
3 checks passed
yeoleobun added a commit that referenced this pull request Oct 12, 2026
Create the INVITE cleanup guard before spawning so an abort before the first poll removes the early dialog without sending a request.

Run the existing CANCEL operation independently until its response or transaction timeout, rather than dropping it after two seconds or an INVITE final response. Keep waiting for the INVITE final response and ACK/BYE cleanup.

Add regressions for immediate abort and CANCEL retransmission beyond the old deadline and after an INVITE final response. All three fail on the original PR; 426 library tests and the platform-embassy compile check pass with this fix.

Refs #204, #205.
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.

do_invite_async: an aborted task leaves its INVITE unended and its early dialog registered

2 participants