@@ -1249,6 +1249,169 @@ func TestMeasureTimeWithPanic(t *testing.T) {
12491249 })
12501250}
12511251
1252+ // TestWaitLimContextCancellation tests the improved waitLim function for responsiveness to context cancellation
1253+ func TestWaitLimContextCancellation (t * testing.T ) {
1254+ tests := []struct {
1255+ name string
1256+ ctxTimeout time.Duration
1257+ expectError bool
1258+ expectQuickExit bool
1259+ }{
1260+ {
1261+ name : "immediate_cancellation" ,
1262+ ctxTimeout : 10 * time .Millisecond , // Slightly longer to ensure rate limiter blocks
1263+ expectError : true ,
1264+ expectQuickExit : true ,
1265+ },
1266+ {
1267+ name : "normal_operation" ,
1268+ ctxTimeout : 100 * time .Millisecond ,
1269+ expectError : false ,
1270+ expectQuickExit : false ,
1271+ },
1272+ }
1273+
1274+ for _ , tt := range tests {
1275+ t .Run (tt .name , func (t * testing.T ) {
1276+ ctx , cancel := context .WithTimeout (context .Background (), tt .ctxTimeout )
1277+ defer cancel ()
1278+
1279+ limiter := ratelimit .New (1 ) // Very slow rate to force blocking
1280+ if tt .expectError {
1281+ // Use up the token bucket to force waiting
1282+ limiter .Take ()
1283+ }
1284+ start := time .Now ()
1285+ err := waitLim (ctx , limiter )
1286+ elapsed := time .Since (start )
1287+
1288+ if tt .expectQuickExit && elapsed > 50 * time .Millisecond {
1289+ t .Errorf ("Expected quick exit but took %v" , elapsed )
1290+ }
1291+
1292+ if tt .expectError {
1293+ if err == nil {
1294+ t .Error ("Expected context cancellation error, got nil" )
1295+ }
1296+ } else if err != nil {
1297+ t .Errorf ("Expected no error, got %v" , err )
1298+ }
1299+ })
1300+ }
1301+ }
1302+
1303+ // TestConnectEphemeralContextCancellationLoop tests that the ephemeral connection loop responds to context cancellation
1304+ func TestConnectEphemeralContextCancellationLoop (t * testing.T ) {
1305+ // Use a mock server that never accepts connections to test cancellation behavior
1306+ listener , err := net .Listen ("tcp" , "127.0.0.1:0" )
1307+ if err != nil {
1308+ t .Fatalf ("Failed to create listener: %v" , err )
1309+ }
1310+ defer listener .Close ()
1311+
1312+ // Don't call Accept() so connections will timeout/fail
1313+ addr := listener .Addr ().String ()
1314+
1315+ client := NewClient (ClientConfig {
1316+ Protocol : "tcp" ,
1317+ ConnectFlavor : flavorEphemeral ,
1318+ Rate : 100 , // High rate to create many goroutines
1319+ Duration : 10 * time .Second , // Long duration
1320+ MessageBytes : 32 ,
1321+ })
1322+
1323+ // Cancel context after short time to test responsiveness
1324+ ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
1325+ defer cancel ()
1326+
1327+ start := time .Now ()
1328+ err = client .connectEphemeral (ctx , addr )
1329+ elapsed := time .Since (start )
1330+
1331+ // Should exit quickly due to context cancellation, not wait for full duration
1332+ if elapsed > 500 * time .Millisecond {
1333+ t .Errorf ("Expected quick exit due to context cancellation, but took %v" , elapsed )
1334+ }
1335+
1336+ // Should get some error (context cancellation or connection error), the key is that it exits quickly
1337+ if err == nil {
1338+ t .Error ("Expected some error due to context cancellation or connection issues" )
1339+ }
1340+ }
1341+
1342+ // TestConnectUDPContextCancellationLoop tests that the UDP connection loop responds to context cancellation
1343+ func TestConnectUDPContextCancellationLoop (t * testing.T ) {
1344+ // Use a non-routable address that should timeout rather than immediately fail
1345+ addr := "192.0.2.1:80" // Test network that should timeout
1346+
1347+ client := NewClient (ClientConfig {
1348+ Protocol : "udp" ,
1349+ Rate : 100 , // High rate to create many goroutines
1350+ Duration : 10 * time .Second , // Long duration
1351+ MessageBytes : 32 ,
1352+ })
1353+
1354+ // Cancel context after short time to test responsiveness
1355+ ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
1356+ defer cancel ()
1357+
1358+ start := time .Now ()
1359+ err := client .connectUDP (ctx , addr )
1360+ elapsed := time .Since (start )
1361+
1362+ // Should exit quickly due to context cancellation, not wait for full duration
1363+ if elapsed > 500 * time .Millisecond {
1364+ t .Errorf ("Expected quick exit due to context cancellation, but took %v" , elapsed )
1365+ }
1366+
1367+ // Should get context cancellation error or connection error, but exit quickly
1368+ if err == nil {
1369+ t .Error ("Expected some error due to context cancellation or connection issues" )
1370+ }
1371+
1372+ // The important thing is that it exits quickly, not the specific error type
1373+ // since UDP connection behavior can vary depending on network configuration
1374+ }
1375+
1376+ // TestConnectPersistentContextCancellationQuick tests that persistent connections respond to context cancellation quickly
1377+ func TestConnectPersistentContextCancellationQuick (t * testing.T ) {
1378+ // Use a mock server that never accepts connections
1379+ listener , err := net .Listen ("tcp" , "127.0.0.1:0" )
1380+ if err != nil {
1381+ t .Fatalf ("Failed to create listener: %v" , err )
1382+ }
1383+ defer listener .Close ()
1384+
1385+ addr := listener .Addr ().String ()
1386+
1387+ client := NewClient (ClientConfig {
1388+ Protocol : "tcp" ,
1389+ ConnectFlavor : flavorPersistent ,
1390+ Connections : 5 , // Multiple connections
1391+ Rate : 10 ,
1392+ Duration : 10 * time .Second , // Long duration
1393+ MessageBytes : 32 ,
1394+ })
1395+
1396+ // Cancel context after short time to test responsiveness
1397+ ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
1398+ defer cancel ()
1399+
1400+ start := time .Now ()
1401+ connErr := client .connectPersistent (ctx , addr )
1402+ elapsed := time .Since (start )
1403+
1404+ // Should exit quickly due to context cancellation
1405+ if elapsed > 500 * time .Millisecond {
1406+ t .Errorf ("Expected quick exit due to context cancellation, but took %v" , elapsed )
1407+ }
1408+
1409+ // Should get context cancellation or connection error
1410+ if connErr == nil {
1411+ t .Error ("Expected error due to context cancellation or connection failure" )
1412+ }
1413+ }
1414+
12521415func TestWaitLimWithSlowRateLimit (t * testing.T ) {
12531416 // Test with very slow rate limiter and context timeout
12541417 limiter := ratelimit .New (1 ) // 1 per second
@@ -1381,3 +1544,167 @@ func TestMetricsCleanupBetweenTests(t *testing.T) {
13811544 // Clean up
13821545 unregisterTimer (key , addr , false )
13831546}
1547+
1548+ // TestDialContextWithTimeout tests that DialContext respects context timeouts
1549+ func TestDialContextWithTimeout (t * testing.T ) {
1550+ // Use a non-routable address that will timeout
1551+ addr := "192.0.2.1:80" // Test network address that should timeout
1552+
1553+ // Very short timeout to ensure quick failure
1554+ ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Millisecond )
1555+ defer cancel ()
1556+
1557+ dialer := net.Dialer {}
1558+ start := time .Now ()
1559+ _ , err := dialer .DialContext (ctx , "tcp" , addr )
1560+ elapsed := time .Since (start )
1561+
1562+ if err == nil {
1563+ t .Error ("Expected timeout error, got nil" )
1564+ }
1565+
1566+ if elapsed > 100 * time .Millisecond {
1567+ t .Errorf ("Expected quick timeout, but took %v" , elapsed )
1568+ }
1569+
1570+ if ! strings .Contains (err .Error (), "context deadline exceeded" ) && ! strings .Contains (err .Error (), "timeout" ) {
1571+ t .Errorf ("Expected context deadline or timeout error, got %v" , err )
1572+ }
1573+ }
1574+
1575+ // TestConnectionDeadlineHandling tests that connection deadlines are properly set
1576+ func TestConnectionDeadlineHandling (t * testing.T ) {
1577+ // Create a mock server that accepts connections but doesn't respond
1578+ listener , err := net .Listen ("tcp" , "127.0.0.1:0" )
1579+ if err != nil {
1580+ t .Fatalf ("Failed to create listener: %v" , err )
1581+ }
1582+ defer listener .Close ()
1583+
1584+ // Accept connections but don't read/write
1585+ go func () {
1586+ for {
1587+ conn , err := listener .Accept ()
1588+ if err != nil {
1589+ return
1590+ }
1591+ // Keep connection open but don't read/write
1592+ time .Sleep (1 * time .Second )
1593+ conn .Close ()
1594+ }
1595+ }()
1596+
1597+ addr := listener .Addr ().String ()
1598+
1599+ // Create context with short deadline
1600+ ctx , cancel := context .WithTimeout (context .Background (), 50 * time .Millisecond )
1601+ defer cancel ()
1602+
1603+ dialer := net.Dialer {}
1604+ conn , err := dialer .DialContext (ctx , "tcp" , addr )
1605+ if err != nil {
1606+ t .Fatalf ("Failed to connect: %v" , err )
1607+ }
1608+ defer conn .Close ()
1609+
1610+ // Set deadline based on context
1611+ if deadline , ok := ctx .Deadline (); ok {
1612+ conn .SetDeadline (deadline )
1613+ }
1614+
1615+ // Try to read - should timeout quickly due to deadline
1616+ start := time .Now ()
1617+ buf := make ([]byte , 10 )
1618+ _ , err = conn .Read (buf )
1619+ elapsed := time .Since (start )
1620+
1621+ if err == nil {
1622+ t .Error ("Expected timeout error on read, got nil" )
1623+ }
1624+
1625+ if elapsed > 100 * time .Millisecond {
1626+ t .Errorf ("Expected quick timeout on read, but took %v" , elapsed )
1627+ }
1628+ }
1629+
1630+ // TestEphemeralLoopBreakOnCancellation tests that the ephemeral loop properly breaks on context cancellation
1631+ func TestEphemeralLoopBreakOnCancellation (t * testing.T ) {
1632+ // This test verifies the labeled break functionality works correctly
1633+ // Use a valid but unresponsive address
1634+ listener , err := net .Listen ("tcp" , "127.0.0.1:0" )
1635+ if err != nil {
1636+ t .Fatalf ("Failed to create listener: %v" , err )
1637+ }
1638+ listener .Close () // Close immediately so connections will fail quickly
1639+
1640+ addr := listener .Addr ().String ()
1641+
1642+ client := NewClient (ClientConfig {
1643+ Protocol : "tcp" ,
1644+ ConnectFlavor : flavorEphemeral ,
1645+ Rate : 1000 , // Very high rate
1646+ Duration : 30 * time .Second , // Long duration
1647+ MessageBytes : 32 ,
1648+ })
1649+
1650+ // Short context timeout
1651+ ctx , cancel := context .WithTimeout (context .Background (), 50 * time .Millisecond )
1652+ defer cancel ()
1653+
1654+ start := time .Now ()
1655+ _ = client .connectEphemeral (ctx , addr )
1656+ elapsed := time .Since (start )
1657+
1658+ // Should exit much faster than the 30-second duration due to context cancellation
1659+ if elapsed > 200 * time .Millisecond {
1660+ t .Errorf ("Expected loop to break quickly on context cancellation, but took %v" , elapsed )
1661+ }
1662+ }
1663+
1664+ // TestUDPLoopBreakOnCancellation tests that the UDP loop properly breaks on context cancellation
1665+ func TestUDPLoopBreakOnCancellation (t * testing.T ) {
1666+ // Use port 0 which should fail connections quickly
1667+ addr := "127.0.0.1:0"
1668+
1669+ client := NewClient (ClientConfig {
1670+ Protocol : "udp" ,
1671+ Rate : 1000 , // Very high rate
1672+ Duration : 30 * time .Second , // Long duration
1673+ MessageBytes : 32 ,
1674+ })
1675+
1676+ // Short context timeout
1677+ ctx , cancel := context .WithTimeout (context .Background (), 50 * time .Millisecond )
1678+ defer cancel ()
1679+
1680+ start := time .Now ()
1681+ _ = client .connectUDP (ctx , addr )
1682+ elapsed := time .Since (start )
1683+
1684+ // Should exit much faster than the 30-second duration due to context cancellation
1685+ if elapsed > 200 * time .Millisecond {
1686+ t .Errorf ("Expected UDP loop to break quickly on context cancellation, but took %v" , elapsed )
1687+ }
1688+ }
1689+
1690+ // TestWaitLimRateLimitingBehavior tests that waitLim properly rate limits while being responsive to cancellation
1691+ func TestWaitLimRateLimitingBehavior (t * testing.T ) {
1692+ limiter := ratelimit .New (5 ) // 5 per second
1693+ ctx := context .Background ()
1694+
1695+ // Take several tokens quickly
1696+ start := time .Now ()
1697+ for i := 0 ; i < 3 ; i ++ {
1698+ err := waitLim (ctx , limiter )
1699+ if err != nil {
1700+ t .Errorf ("Unexpected error in waitLim: %v" , err )
1701+ }
1702+ }
1703+ elapsed := time .Since (start )
1704+
1705+ // Should take at least some time due to rate limiting
1706+ expectedMinDuration := 400 * time .Millisecond // 3 tokens at 5/sec should take ~400ms
1707+ if elapsed < expectedMinDuration {
1708+ t .Errorf ("Expected rate limiting to take at least %v, but took %v" , expectedMinDuration , elapsed )
1709+ }
1710+ }
0 commit comments