@@ -1350,6 +1350,97 @@ void Test_netloopback_throughput()
13501350 SteamNetworkingSockets ()->CloseConnection ( hClient, 0 , nullptr , false );
13511351}
13521352
1353+ void Test_send_buffer_full ()
1354+ {
1355+ TEST_Printf ( " ***************************************************\n " );
1356+ TEST_Printf ( " Test: SendMessages batch stop and retry on buffer full\n " );
1357+ TEST_Printf ( " ***************************************************\n " );
1358+
1359+ // Network loopback so we get real rate limiting
1360+ HSteamNetConnection hSender, hRecver;
1361+ assert ( SteamNetworkingSockets ()->CreateSocketPair ( &hSender, &hRecver, true , nullptr , nullptr ) );
1362+ SteamNetworkingSockets ()->SetConnectionName ( hSender, " sender" );
1363+ SteamNetworkingSockets ()->SetConnectionName ( hRecver, " recver" );
1364+
1365+ // Low send rate so the buffer stays full long enough to observe
1366+ const int k_nSendRate = 64 * 1024 ;
1367+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hSender, k_ESteamNetworkingConfig_SendRateMin, k_nSendRate );
1368+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hSender, k_ESteamNetworkingConfig_SendRateMax, k_nSendRate );
1369+
1370+ // Buffer fits one 100K message but not two, so msg[1] overflows and msg[2] is never attempted
1371+ const int k_cbMsg = 100 * 1024 ;
1372+ const int k_nSendBuffer = 160 * 1024 ;
1373+ SteamNetworkingUtils ()->SetConnectionConfigValueInt32 ( hSender, k_ESteamNetworkingConfig_SendBufferSize, k_nSendBuffer );
1374+
1375+ // Prepare three reliable messages
1376+ SteamNetworkingMessage_t *pMessages[3 ];
1377+ for ( int i = 0 ; i < 3 ; ++i )
1378+ {
1379+ pMessages[i] = SteamNetworkingUtils ()->AllocateMessage ( k_cbMsg );
1380+ pMessages[i]->m_conn = hSender;
1381+ pMessages[i]->m_nFlags = k_nSteamNetworkingSend_Reliable;
1382+ }
1383+
1384+ // Send all three at once. Pass bDeleteFailedMessages=false so the library
1385+ // leaves failed/not-attempted message pointers in place for retry.
1386+ int64 results[3 ] = {};
1387+ SteamNetworkingSockets ()->SendMessages ( 3 , pMessages, results, /* bDeleteFailedMessages=*/ false );
1388+
1389+ // First message fits in the 160K buffer: should succeed
1390+ assert ( results[0 ] > 0 );
1391+ assert ( pMessages[0 ] == nullptr ); // library took ownership
1392+
1393+ // Second message overflows the buffer: should fail
1394+ assert ( results[1 ] == -k_EResultLimitExceeded );
1395+ assert ( pMessages[1 ] != nullptr ); // we still own it; can retry
1396+
1397+ // Third message was never attempted because the second failed
1398+ assert ( results[2 ] == 0 );
1399+ assert ( pMessages[2 ] != nullptr ); // we still own it; can retry
1400+
1401+ TEST_Printf ( " Initial send: msg[0]=#%lld succeeded, msg[1]=LimitExceeded, msg[2]=not attempted.\n " , results[0 ] );
1402+
1403+ // Retry the two remaining messages one at a time. The buffer only holds one
1404+ // 100K message, so we must wait for space before each retry.
1405+ SteamNetworkingMessage_t *pRetry[2 ] = { pMessages[1 ], pMessages[2 ] };
1406+ for ( int i = 0 ; i < 2 ; ++i )
1407+ {
1408+ TEST_Printf ( " Waiting for buffer space (retry %d)...\n " , i );
1409+ SteamNetworkingMicroseconds usecDeadline = SteamNetworkingUtils ()->GetLocalTimestamp () + 10 * 1000000LL ;
1410+ for (;;)
1411+ {
1412+ TEST_PumpCallbacks ();
1413+
1414+ // Keep the receiver drained so the connection stays healthy
1415+ SteamNetworkingMessage_t *pRecvMsgs[16 ];
1416+ for (;;)
1417+ {
1418+ int nRecv = SteamNetworkingSockets ()->ReceiveMessagesOnConnection ( hRecver, pRecvMsgs, 16 );
1419+ if ( nRecv <= 0 ) break ;
1420+ for ( int j = 0 ; j < nRecv; ++j )
1421+ pRecvMsgs[j]->Release ();
1422+ if ( nRecv < 16 ) break ;
1423+ }
1424+
1425+ SteamNetConnectionRealTimeStatus_t status;
1426+ assert ( k_EResultOK == SteamNetworkingSockets ()->GetConnectionRealTimeStatus ( hSender, &status, 0 , nullptr ) );
1427+ if ( status.m_cbPendingReliable + k_cbMsg < k_nSendBuffer )
1428+ break ;
1429+
1430+ assert ( SteamNetworkingUtils ()->GetLocalTimestamp () < usecDeadline );
1431+ }
1432+
1433+ int64 retryResult = 0 ;
1434+ SteamNetworkingSockets ()->SendMessages ( 1 , &pRetry[i], &retryResult, /* bDeleteFailedMessages=*/ false );
1435+ assert ( retryResult > 0 );
1436+ assert ( pRetry[i] == nullptr );
1437+ TEST_Printf ( " Retry %d succeeded: queued as msg #%lld.\n " , i, retryResult );
1438+ }
1439+
1440+ SteamNetworkingSockets ()->CloseConnection ( hSender, 0 , nullptr , false );
1441+ SteamNetworkingSockets ()->CloseConnection ( hRecver, 0 , nullptr , false );
1442+ }
1443+
13531444int main ( int argc, const char **argv )
13541445{
13551446 typedef void (*FnTest)(void );
@@ -1367,15 +1458,16 @@ int main( int argc, const char **argv )
13671458 TEST (netloopback_throughput),
13681459 TEST (lane_quick_queueanddrain),
13691460 TEST (lane_quick_priority_and_background),
1370- TEST (pipe)
1461+ TEST (pipe),
1462+ TEST (send_buffer_full)
13711463 };
13721464
13731465 struct Suite_t {
13741466 const char *m_pszName;
13751467 std::vector< Test_t > m_vecTests;
13761468 };
13771469 static const Suite_t test_suites[] = {
1378- { " suite-quick" , { TEST (identity), TEST (quick), TEST (lane_quick_queueanddrain), TEST (netloopback_throughput), TEST (lane_quick_priority_and_background), TEST (pipe) } }
1470+ { " suite-quick" , { TEST (identity), TEST (quick), TEST (lane_quick_queueanddrain), TEST (netloopback_throughput), TEST (lane_quick_priority_and_background), TEST (pipe), TEST (send_buffer_full) } }
13791471 };
13801472
13811473 if ( argc < 2 )
0 commit comments