Skip to content

Cover the decline path in the teardown drain and the log - #29

Merged
PetrShtuka merged 1 commit into
mainfrom
petrshtuka/decline-teardown-observer
Aug 25, 2026
Merged

Cover the decline path in the teardown drain and the log#29
PetrShtuka merged 1 commit into
mainfrom
petrshtuka/decline-teardown-observer

Conversation

@PetrShtuka

Copy link
Copy Markdown
Owner

Reported from the field against 0.6.0: a never-answered call declined with
endCall(uuid:) leaves the PBX ringing, with no drain log and no transaction
log at all. The report's diagnosis was right on both counts.

The drain missed this path. It waited on pjsua_call_get_count(), whose
documentation says it includes "calls that are no longer active but still in the
process of hanging up". That describes a BYE. For a decline, PJSUA disconnects
the invite session as soon as the final response is sent and releases the call
slot, so the count is already zero while the 603 is unacknowledged — the drain
returned instantly and the account was deleted underneath the response. Now
asserted by a test rather than reasoned about.

The observability missed it for the same reason, and the suggested fix could
not have worked: sip_module.h says on_tsx_state is called "when this module
is acting as transaction user for the specified transaction", which for an
INVITE is the invite session, not an application module. What every registered
module does see is on_tx_response and on_rx_request, so the observer watches
the final response leaving and the ACK arriving, registered ahead of the
transaction layer because that is where the ACK to a non-2xx is absorbed.

The new send line carries the destination — 603 sent to <host>:<port> for Call-ID … — which is what separates "nothing was sent" from "sent there and
lost" without a packet capture.

Test. CallWaveDeclineTeardownTests drives a real INVITE at the engine's own
UDP transport over loopback, declines it, and asserts the 603 comes back on the
socket, that it is held until acknowledged, and that the ACK releases it. It also
pins pjsua_call_get_count() == 0 at that moment, so if PJSUA ever changes, the
extra tracking can go.

Note for the reporter: this test shows the 603 does reach the wire, so the
account-deletion race is unlikely to be the whole story behind the field
failure. See the PR discussion for what to capture next.

The 0.6.0 drain waited on pjsua_call_get_count(), whose documentation says it
includes "calls that are no longer active but still in the process of hanging
up". That is true of a BYE and false of a decline: PJSUA disconnects the invite
session the moment a final response is sent and releases the call slot, so the
count is back to zero while the 603 has not been acknowledged. logout() drained
nothing and deleted the account immediately — the exact behaviour 0.6.0 claimed
to fix. The doc comment asserting otherwise was extrapolated from the
pjsua_call_hangup docs, which are about BYE, and never checked.

Measured rather than argued this time: the new test declines a real INVITE over
loopback UDP and asserts pjsua_call_get_count() == 0 while the 603 is
outstanding.

Final responses are now tracked by Call-ID and CSeq from the transport hand-off
until the ACK, and the drain waits on that alongside PJSUA's count. Entries
expire after SIP timer H so one PBX that never ACKs cannot make every later
teardown wait the full timeout.

The observability moves for the same reason. on_call_tsx_state never fired for a
declined call — PJSUA has nothing left to report it against. on_tsx_state cannot
replace it either: sip_module.h says it reaches only the module "acting as
transaction user", which for an INVITE is the invite session, never an
application module. What every registered module does see is the response going
out and the request coming in, so the observer watches those, registered ahead
of the transaction layer because that is where the ACK to a non-2xx is absorbed.

The send line now carries the destination, which is the thing a host could
otherwise only get from a packet capture: it separates "nothing was sent" from
"it was sent to that address and lost".

The end-to-end test is the point of this change. It proves, without a PBX, that
the 603 reaches the wire, that it is held until acknowledged, and that the ACK
releases it.
@PetrShtuka
PetrShtuka merged commit a82f6b5 into main Aug 25, 2026
4 checks passed
@PetrShtuka
PetrShtuka deleted the petrshtuka/decline-teardown-observer branch September 1, 2026 18:03
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