@@ -77,7 +77,7 @@ struct SFakePeer
7777 m_flUnreliableMsgDelay = 0 .0f ;
7878 m_hSteamNetConnection = k_HSteamNetConnection_Invalid;
7979 m_bIsConnected = false ;
80- m_cbSendBuffer = 384 * 1024 ;
80+ m_cbSendBuffer = 2048 * 1024 ;
8181 memset ( &m_realtimeStatus, 0 , sizeof (m_realtimeStatus) );
8282 m_flSendRate = 0 .0f ;
8383 m_flRecvRate = 0 .0f ;
@@ -536,6 +536,11 @@ static void Test_Connection( ETestConnectionMode eMode, const SteamNetworkingIPA
536536 {
537537 Test ( 128000 , 10 , 50 , 2 , 50 ); // Low bandwidth, high packet loss
538538 Test ( 1000000 , 5 , 10 , 1 , 10 ); // Medium bandwidth, still pretty bad packet loss
539+
540+ // Zero loss so acks flow freely (stop_waiting tracks within one RTT of
541+ // current packet number). Heavy reorder builds reliable stream fragments
542+ // without the retransmission backlog that keeps stop_waiting far behind.
543+ TestNetworkConditions ( 2000000 , 0 , 50 , 30 , 40 , false , eMode );
539544 }
540545 else
541546 {
@@ -1086,6 +1091,113 @@ void Test_lane_quick_priority_and_background()
10861091
10871092}
10881093
1094+ // Coverage/stress test for a stalled receiver: the application stops draining
1095+ // its receive queue while the peer keeps blasting reliable data, under loss and
1096+ // reorder. This drives a code path almost nothing else in the suite reaches.
1097+ //
1098+ // Simulates an app stall (e.g. loading assets from disk) while the server is
1099+ // blasting reliable state data. The client never calls ReceiveMessagesOnConnection,
1100+ // so its application receive queue (RecvBufferMessages) fills up. Once full,
1101+ // ReceivedMessage() returns false, which propagates to bInhibitMarkReceived in the
1102+ // SNP decoder: the packet is processed (TrackProcessSequencedPacket advances
1103+ // m_nMaxRecvPktNum) but SNP_RecordReceivedPktNum is skipped, so the packet is not
1104+ // entered into the gap map. Meanwhile the client still sends acks back and the
1105+ // server keeps advancing its stop_waiting. Layering loss and reorder on the
1106+ // server->client path keeps the gap map churning throughout.
1107+ void Test_recv_buf_full ()
1108+ {
1109+ TEST_Printf ( " ***************************************************\n " );
1110+ TEST_Printf ( " Test: recv buffer full / stalled receiver under loss+reorder\n " );
1111+ TEST_Printf ( " ***************************************************\n " );
1112+
1113+ // Network loopback so packets go through the full SNP encode/decode path.
1114+ HSteamNetConnection hServer, hClient;
1115+ assert ( SteamNetworkingSockets ()->CreateSocketPair ( &hServer, &hClient, true , nullptr , nullptr ) );
1116+ SteamNetworkingSockets ()->SetConnectionName ( hServer, " Server" );
1117+ SteamNetworkingSockets ()->SetConnectionName ( hClient, " Client" );
1118+
1119+ // Small application receive queue on the client. After this many messages
1120+ // are queued without the app draining them, ReceivedMessage() returns false,
1121+ // which propagates to bInhibitMarkReceived in the SNP decoder.
1122+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hClient,
1123+ k_ESteamNetworkingConfig_RecvBufferMessages, 32 );
1124+
1125+ // Moderate send rate -- enough to fill the queue quickly but not so fast
1126+ // that the service thread starves the client's keepalive sending.
1127+ const int k_nSendRate = 256 * 1024 ;
1128+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hServer,
1129+ k_ESteamNetworkingConfig_SendRateMin, k_nSendRate );
1130+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hServer,
1131+ k_ESteamNetworkingConfig_SendRateMax, k_nSendRate );
1132+
1133+ // Simulate real-world network conditions: packet loss and reordering on
1134+ // the server->client path. This creates non-trivial gap map state on the
1135+ // client before and during the buffer-full condition, which may interact
1136+ // with the stop_waiting/sentinel relationship differently.
1137+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hClient,
1138+ k_ESteamNetworkingConfig_FakePacketLoss_Recv, 5 );
1139+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hClient,
1140+ k_ESteamNetworkingConfig_FakePacketReorder_Recv, 10 );
1141+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hClient,
1142+ k_ESteamNetworkingConfig_FakePacketReorder_Time, 20 );
1143+
1144+ // Let the connection settle so all handshake traffic is complete and acks
1145+ // have drained before the burst begins.
1146+ for ( int i = 0 ; i < 20 ; ++i )
1147+ {
1148+ TEST_PumpCallbacks ();
1149+ std::this_thread::sleep_for ( std::chrono::milliseconds ( 10 ) );
1150+ }
1151+
1152+ // Queue up a large initial burst of reliable data -- far more than the
1153+ // client's RecvBufferMessages limit. The server drains this at k_nSendRate.
1154+ static const char kData [ 1000 ] = {};
1155+ int nSent = 0 ;
1156+ for ( int i = 0 ; i < 2000 ; ++i )
1157+ {
1158+ EResult r = SteamNetworkingSockets ()->SendMessageToConnection (
1159+ hServer, kData , sizeof (kData ), k_nSteamNetworkingSend_Reliable, nullptr );
1160+ if ( r == k_EResultOK )
1161+ ++nSent;
1162+ }
1163+ TEST_Printf ( " Queued %d messages (~%d KB) for server to send\n " , nSent, nSent * (int )sizeof (kData ) / 1024 );
1164+
1165+ // Continue sending from both sides for several seconds while the client
1166+ // never drains its receive queue. The client sends small messages back so
1167+ // its outgoing packets carry acks, giving the server frequent opportunities
1168+ // to advance its stop_waiting. The server keeps queuing small messages to
1169+ // stay active. The client never calls ReceiveMessagesOnConnection.
1170+ static const char kTiny [1 ] = {};
1171+ for ( int i = 0 ; i < 60 ; ++i )
1172+ {
1173+ TEST_PumpCallbacks ();
1174+ std::this_thread::sleep_for ( std::chrono::milliseconds ( 100 ) );
1175+
1176+ SteamNetworkingSockets ()->SendMessageToConnection (
1177+ hServer, kTiny , sizeof (kTiny ), k_nSteamNetworkingSend_Unreliable, nullptr );
1178+ SteamNetworkingSockets ()->SendMessageToConnection (
1179+ hClient, kTiny , sizeof (kTiny ), k_nSteamNetworkingSend_Unreliable, nullptr );
1180+
1181+ SteamNetConnectionInfo_t infoClient, infoServer;
1182+ bool bGotClient = SteamNetworkingSockets ()->GetConnectionInfo ( hClient, &infoClient );
1183+ SteamNetworkingSockets ()->GetConnectionInfo ( hServer, &infoServer );
1184+
1185+ if ( !bGotClient || infoClient.m_eState != k_ESteamNetworkingConnectionState_Connected )
1186+ {
1187+ TEST_Printf ( " *** Connection dropped (iter %d) -- stalled receiver was not handled ***\n " , i );
1188+ TEST_Printf ( " Client: state=%d reason=%d '%s'\n " ,
1189+ (int )infoClient.m_eState , (int )infoClient.m_eEndReason , infoClient.m_szEndDebug );
1190+ TEST_Printf ( " Server: state=%d reason=%d '%s'\n " ,
1191+ (int )infoServer.m_eState , (int )infoServer.m_eEndReason , infoServer.m_szEndDebug );
1192+ assert ( false );
1193+ }
1194+ }
1195+
1196+ TEST_Printf ( " Connection survived with full receive queue -- OK\n " );
1197+ SteamNetworkingSockets ()->CloseConnection ( hServer, 0 , nullptr , false );
1198+ SteamNetworkingSockets ()->CloseConnection ( hClient, 0 , nullptr , false );
1199+ }
1200+
10891201void Test_pipe ()
10901202{
10911203 TEST_Printf ( " ***************************************************\n " );
@@ -1459,15 +1571,16 @@ int main( int argc, const char **argv )
14591571 TEST (lane_quick_queueanddrain),
14601572 TEST (lane_quick_priority_and_background),
14611573 TEST (pipe),
1462- TEST (send_buffer_full)
1574+ TEST (send_buffer_full),
1575+ TEST (recv_buf_full)
14631576 };
14641577
14651578 struct Suite_t {
14661579 const char *m_pszName;
14671580 std::vector< Test_t > m_vecTests;
14681581 };
14691582 static const Suite_t test_suites[] = {
1470- { " suite-quick" , { TEST (identity), TEST (quick), TEST (lane_quick_queueanddrain), TEST (lane_quick_priority_and_background), TEST (pipe), TEST (send_buffer_full) } }
1583+ { " suite-quick" , { TEST (identity), TEST (quick), TEST (lane_quick_queueanddrain), TEST (lane_quick_priority_and_background), TEST (pipe), TEST (send_buffer_full), TEST (recv_buf_full) } }
14711584 };
14721585
14731586 if ( argc < 2 )
0 commit comments