Skip to content

Commit f539edb

Browse files
committed
ICE client: fix retrigger
Sometimes the RFC algorithm wants you to immediately 'retrigger' a candidate check. The previous code was deleting the previous request and creating a new one. But this means that any reply that comes in cannot get matched up, because the new request will get a new transaction ID. In general, I think any time we cancel a request and throw it away, it is probably a mistake, because the reply that comes back contains useful information, and if we throw away the request, we won't be able to match it up by transaction ID and remember what we were doing, so we lose that information. This can be catastrophic. The Mac CI was failing because of a timing issue where each side would 'retrigger' its request just before the reply to the previous request arrived. All of the responses were getting dropped because they could not be matched up by transaction ID. The new code just resets the retransmission timer, causing us to send it immediately and also resets the exponential backoff schedule. Also, simplify some logic and tweak formatting.
1 parent 85be74f commit f539edb

2 files changed

Lines changed: 53 additions & 25 deletions

File tree

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_ice_client.cpp

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,12 @@ void CSteamNetworkingSocketsSTUNRequest::Cancel()
895895
delete this;
896896
}
897897

898+
void CSteamNetworkingSocketsSTUNRequest::RetriggerNow()
899+
{
900+
m_nRetryCount = 0;
901+
SetNextThinkTimeASAP();
902+
}
903+
898904
void CSteamNetworkingSocketsSTUNRequest::Think( SteamNetworkingMicroseconds usecNow )
899905
{
900906
SteamNetworkingGlobalLock::AssertHeldByCurrentThread( "CSteamNetworkingSocketsSTUNRequest::Think" );
@@ -1627,50 +1633,67 @@ void CSteamNetworkingICESession::OnPacketReceived( const RecvPktInfo_t &info, IC
16271633
// An incoming request proves the remote side can reach us, so retry immediately
16281634
// rather than waiting for the normal schedule or a retransmit timeout.
16291635
// Skip when USE-CANDIDATE is set — that path handles its own triggered check below.
1630-
if ( !FindAttributeOfType( vecAttrs.Base(), vecAttrs.Count(), k_nSTUN_Attr_UseCandidate )
1631-
&& pThisPair->m_nState != kICECandidatePairState_Succeeded )
1632-
{
1636+
if (
1637+
pThisPair->m_nState != kICECandidatePairState_Succeeded
1638+
&& !FindAttributeOfType( vecAttrs.Base(), vecAttrs.Count(), k_nSTUN_Attr_UseCandidate )
1639+
) {
16331640
if ( pThisPair->m_pPeerRequest != nullptr )
16341641
{
1635-
// InProgress: cancel the existing request so we don't have two in flight.
1636-
pThisPair->m_pPeerRequest->Cancel();
1637-
pThisPair->m_pPeerRequest = nullptr;
1642+
Assert( pThisPair->m_nState == kICECandidatePairState_InProgress );
1643+
1644+
// Retrigger the existing in-flight request rather than canceling it.
1645+
// Canceling would assign a new transaction ID, orphaning any response
1646+
// already in flight for the old ID.
1647+
pThisPair->m_pPeerRequest->RetriggerNow();
1648+
}
1649+
else
1650+
{
1651+
Assert( pThisPair->m_nState != kICECandidatePairState_InProgress );
1652+
pThisPair->m_nState = kICECandidatePairState_Waiting;
1653+
if ( !has_element( m_vecTriggeredCheckQueue, pThisPair ) )
1654+
m_vecTriggeredCheckQueue.push_back( pThisPair );
16381655
}
1639-
pThisPair->m_nState = kICECandidatePairState_Waiting;
1640-
if ( !has_element( m_vecTriggeredCheckQueue, pThisPair ) )
1641-
m_vecTriggeredCheckQueue.push_back( pThisPair );
16421656
}
16431657

16441658
if ( FindAttributeOfType( vecAttrs.Base(), vecAttrs.Count(), k_nSTUN_Attr_UseCandidate ) )
16451659
{
1646-
if ( pThisPair->m_nState == kICECandidatePairState_Succeeded
1647-
&& ( m_pSelectedCandidatePair == nullptr || m_pSelectedCandidatePair == pThisPair ) )
1648-
{
1660+
if (
1661+
pThisPair->m_nState == kICECandidatePairState_Succeeded
1662+
&& ( m_pSelectedCandidatePair == nullptr || m_pSelectedCandidatePair == pThisPair )
1663+
) {
16491664
SetSelectedCandidatePair( pThisPair );
16501665
}
16511666
else if ( m_pSelectedCandidatePair == nullptr )
16521667
{
1653-
bool bAlreadyHaveANomination = ( m_pSelectedCandidatePair != nullptr );
1668+
bool bAlreadyHaveANomination = false;
16541669
for ( ICECandidatePair *pOtherPair : m_vecCandidatePairs )
16551670
{
1656-
if ( pOtherPair->m_bNominated == true
1657-
&& ( pOtherPair->m_nState == kICECandidatePairState_InProgress || pOtherPair->m_nState == kICECandidatePairState_Waiting ) )
1671+
if (
1672+
pOtherPair->m_bNominated
1673+
&& ( pOtherPair->m_nState == kICECandidatePairState_InProgress || pOtherPair->m_nState == kICECandidatePairState_Waiting )
1674+
) {
16581675
bAlreadyHaveANomination = true;
1659-
}
1660-
1661-
// Do we already have a valid triggered check in flight?
1662-
if ( pThisPair->m_pPeerRequest != nullptr )
1663-
{
1664-
pThisPair->m_pPeerRequest->Cancel();
1665-
pThisPair->m_pPeerRequest = nullptr;
1666-
pThisPair->m_nState = kICECandidatePairState_Waiting;
1676+
}
16671677
}
16681678

16691679
if ( !bAlreadyHaveANomination )
16701680
{
1671-
pThisPair->m_nState = kICECandidatePairState_Waiting;
16721681
pThisPair->m_bNominated = true;
1673-
m_vecTriggeredCheckQueue.push_back( pThisPair );
1682+
if ( pThisPair->m_pPeerRequest != nullptr )
1683+
{
1684+
Assert( pThisPair->m_nState == kICECandidatePairState_InProgress );
1685+
1686+
// The in-flight request will call SetSelectedCandidatePair when it
1687+
// succeeds; m_bNominated is now set so the callback will handle it.
1688+
// Retrigger rather than cancel so the existing transaction ID is preserved.
1689+
pThisPair->m_pPeerRequest->RetriggerNow();
1690+
}
1691+
else
1692+
{
1693+
Assert( pThisPair->m_nState != kICECandidatePairState_InProgress );
1694+
pThisPair->m_nState = kICECandidatePairState_Waiting;
1695+
m_vecTriggeredCheckQueue.push_back( pThisPair );
1696+
}
16741697
}
16751698
}
16761699
}

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_ice_client.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ namespace SteamNetworkingSocketsLib {
222222
void Queue( uint32 nMessageType, int nEncoding, SteamNetworkingIPAddr remoteAddr, RecvSTUNPacketCallback_t cb, STUNAttribute *pExtraAttrs = nullptr, int nExtraAttrs = 0 );
223223
void Cancel();
224224

225+
// Immediately retransmit and reset the exponential backoff schedule, as if
226+
// the request were freshly queued. The transaction ID is preserved, so any
227+
// response already in flight will still be matched and processed.
228+
void RetriggerNow();
229+
225230
// Handle an incoming STUN reply that has already been matched to this request
226231
// by transaction ID. Verifies message integrity, fires the callback, then
227232
// deletes this request.

0 commit comments

Comments
 (0)