Conversation
The transport layer caches stream connections (TCP/TLS/WS) by remote address but never removed one when its serve loop ended, and nothing cancelled its token. A connection the peer closed (a carrier or proxy idle-closing the flow) was therefore handed out by lookup() for every later request to that peer, and dialog flow affinity kept choosing it for in-dialog requests. Since send() logs write errors and the transaction has no Timer A on a reliable transport, each such request sat until Timer B/F and then got a local 408: 32 s per request, for every request to that peer, for good. When a stream connection's serve loop exits, cancel its token and drop it from the cache (only if the cached entry is that same connection). When a write on a stream connection fails, retire it the same way and report the failure to the TU as a local 503 (RFC 3261 §17.1.4, §8.1.3.1) instead of waiting for the timeout. Later requests then open a new connection. UDP and channel connections are unchanged.
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 #160.
Problem
TransportLayercaches stream connections (TCP/TLS/WS) by remote address. When a connection's serve loop ends (the peer closed it, e.g. a carrier or proxy idle-closing the flow), the connection stays in the cache and its cancel token is never cancelled. So:lookup()keeps returning the closed connection for every later request to that peer;resolve_affinity_connection, which skips flows whose token is cancelled) keeps choosing it for in-dialog requests;Transaction::send()only logs the write error and, because a reliable transport arms no Timer A, the request is never retransmitted or sent on a new connection. It waits for Timer B/F and gets a local 408: 32 s per request, for every request to that peer, until the application callsdel_connectionitself.Spec
Fix
serve_connection: when a stream connection's serve loop exits, cancel its token (flow affinity then skips it, as its comment intends) and remove it from the cache, before awaitingclose(). Removal is by identity (Arc::ptr_eq), so a newer connection cached for the same peer is kept.Transaction::send()/ Timer A resend: when the write on a stream connection fails, retire the connection the same way and report a local 503 to the TU, instead of waiting for Timer B/F. The transaction does not re-send the request itself (the write may have partly gone out); the next request opens a new connection.SipConnection::is_stream,SipConnection::is_same_stream,TlsConnection::ptr_eq,TransportLayer::retire_connection).Contract / coverage
add_connection→serve_connection, andis_stream/is_same_streamcover all three (TLS client and server sides). The tests use TCP.resolve_affinity_connectionfalls back to normal resolution or the Via dial-back instead of re-selecting it (tested: the token is cancelled when the peer closes the flow).make_responsefrom the request, through the transaction's normal response path). For an INVITE the transaction then handles it like any non-2xx final response.Tests
New
src/transaction/tests/test_stream_reconnect.rs(raw TCP peer; the first two use onlyTransaction::new_client/send/receive):test_request_after_peer_closed_stream_uses_new_connection: the peer closes the connection after the first OPTIONS; the flow is marked terminated, and the next OPTIONS goes out on a new connection and is answered.test_send_failure_on_stream_is_reported_at_once: OPTIONS and INVITE on a connection whose writes fail get a 503 at once, and that connection is retired.test_retire_keeps_newer_connection_to_same_peer: retiring an old connection keeps a newer one cached for the same peer; a retired connection is not returned bylookup.Checks (on
main@ 3ea35cd)src/change reverted and the two public-API tests kept (the third uses the new helpers and does not compile without them),cargo test --lib test_stream_reconnect→ 2 failed (a flow the peer closed must be marked terminated: Elapsed(());a failed OPTIONS write on a stream connection must be reported to the TU, not left to time out, left: None).cargo test→ 335 lib + 65 doc passed, 0 failed.rustfmt --checkclean on the changed files. The new tests passed 10 repeated runs.Out of scope