This is an automated email from the ASF dual-hosted git repository.

hubcio pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 0b290b00c ci(csharp): auto-retry flaky integration tests (#3160)
0b290b00c is described below

commit 0b290b00c5b1469fb3f776682901dfbd816da285
Author: Krishna Vishal <[email protected]>
AuthorDate: Fri Apr 24 15:22:45 2026 +0530

    ci(csharp): auto-retry flaky integration tests (#3160)
---
 .github/actions/csharp-dotnet/pre-merge/action.yml |  2 +-
 foreign/csharp/Directory.Packages.props            |  1 +
 .../Fixtures/IggyClusterFixture.cs                 | 70 +++++++++++++++-------
 .../Iggy_SDK.Tests.Integration.csproj              |  1 +
 4 files changed, 52 insertions(+), 22 deletions(-)

diff --git a/.github/actions/csharp-dotnet/pre-merge/action.yml 
b/.github/actions/csharp-dotnet/pre-merge/action.yml
index 2e7272503..ac50a072b 100644
--- a/.github/actions/csharp-dotnet/pre-merge/action.yml
+++ b/.github/actions/csharp-dotnet/pre-merge/action.yml
@@ -98,7 +98,7 @@ runs:
           --coverage-output-format cobertura \
           --coverage-output coverage.cobertura.xml \
           --results-directory ./reports \
-          -- --report-trx
+          -- --report-trx --retry-failed-tests 3
       shell: bash
 
     - name: Collect container logs
diff --git a/foreign/csharp/Directory.Packages.props 
b/foreign/csharp/Directory.Packages.props
index 0d927b969..b2dee5c2a 100644
--- a/foreign/csharp/Directory.Packages.props
+++ b/foreign/csharp/Directory.Packages.props
@@ -10,6 +10,7 @@
         <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
         <PackageVersion Include="Microsoft.SourceLink.GitHub" 
Version="10.0.102" />
         <PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" 
Version="18.3.2" />
+        <PackageVersion Include="Microsoft.Testing.Extensions.Retry" 
Version="2.2.1" />
         <PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" 
Version="2.0.2" />
         <PackageVersion Include="Moq" Version="4.20.72" />
         <PackageVersion Include="Reqnroll.xunit.v3" Version="3.3.3" />
diff --git 
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs 
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs
index 5e6f8eabe..73c33edea 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs
@@ -29,10 +29,21 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
     private const string LeaderAlias = "iggy-leader";
     private const string FollowerAlias = "iggy-follower";
 
-    // TcpListeners held open until just before the containers are started so 
that the
-    // OS keeps the chosen ports reserved across the whole fixture setup. 
Parallel test
-    // hosts (net8.0 + net10.0) would otherwise race on the gap between 
picking the port
-    // and docker binding it.
+    // Split the host-port pool by .NET major version so parallel `dotnet test`
+    // processes (net8.0 + net10.0) can never pick the same port and race 
docker's
+    // allocator. The ranges sit below Linux's default ephemeral range 
(32768+), so
+    // the kernel's auto-allocation won't steal from us either.
+    //   net8.0  - 30800..30899
+    //   net10.0 - 31000..31099
+    // Future TFMs slot in without overlap (e.g. net12.0 - 31200..31299).
+    private const ushort PortRangeSize = 100;
+    private static readonly ushort BasePort = (ushort)(30000 + 
Environment.Version.Major * 100);
+    private static readonly ushort EndPort = (ushort)(BasePort + 
PortRangeSize);
+
+    // Listeners only need to outlive the eight ReservePort() calls in the
+    // constructor so we don't pick the same port twice within one fixture.
+    // Partitioned ranges already guarantee sibling processes can't race us, so
+    // we can release them as soon as picking is done.
     private readonly List<TcpListener> _portReservations = [];
     private readonly IContainer _followerContainer;
     private readonly ushort _followerHttpPort;
@@ -57,14 +68,21 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
 
     public IggyClusterFixture()
     {
-        _leaderTcpPort = ReservePort();
-        _leaderHttpPort = ReservePort();
-        _leaderQuicPort = ReservePort();
-        _leaderWsPort = ReservePort();
-        _followerTcpPort = ReservePort();
-        _followerHttpPort = ReservePort();
-        _followerQuicPort = ReservePort();
-        _followerWsPort = ReservePort();
+        try
+        {
+            _leaderTcpPort = ReservePort();
+            _leaderHttpPort = ReservePort();
+            _leaderQuicPort = ReservePort();
+            _leaderWsPort = ReservePort();
+            _followerTcpPort = ReservePort();
+            _followerHttpPort = ReservePort();
+            _followerQuicPort = ReservePort();
+            _followerWsPort = ReservePort();
+        }
+        finally
+        {
+            ReleaseReservedPorts();
+        }
 
         _network = new NetworkBuilder()
             .WithName($"iggy-cluster-{Guid.NewGuid():N}")
@@ -140,7 +158,6 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
 
     public async ValueTask DisposeAsync()
     {
-        ReleaseReservedPorts();
         await SaveContainerLogsAsync(_leaderContainer, "leader");
         await SaveContainerLogsAsync(_followerContainer, "follower");
         await _followerContainer.StopAsync();
@@ -151,10 +168,6 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
     public async Task InitializeAsync()
     {
         await _network.CreateAsync();
-        // Release the reservations at the last possible moment so the window 
between
-        // giving the port back to the OS and docker re-binding it is as small 
as we can
-        // make it.
-        ReleaseReservedPorts();
         await Task.WhenAll(_leaderContainer.StartAsync(), 
_followerContainer.StartAsync());
     }
 
@@ -170,10 +183,25 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
 
     private ushort ReservePort()
     {
-        var listener = new TcpListener(IPAddress.Loopback, 0);
-        listener.Start();
-        _portReservations.Add(listener);
-        return (ushort)((IPEndPoint)listener.LocalEndpoint).Port;
+        for (ushort candidate = BasePort; candidate < EndPort; candidate++)
+        {
+            try
+            {
+                var listener = new TcpListener(IPAddress.Loopback, candidate);
+                listener.Start();
+                _portReservations.Add(listener);
+                return candidate;
+            }
+            catch (SocketException)
+            {
+                // Port is held by a previous ReservePort() in this fixture
+                // (the common case) or by something else on the host; keep
+                // walking the range.
+            }
+        }
+
+        throw new InvalidOperationException(
+            $"No free ports available in [{BasePort}, {EndPort}) for .NET 
{Environment.Version.Major}.x.");
     }
 
     private void ReleaseReservedPorts()
diff --git 
a/foreign/csharp/Iggy_SDK.Tests.Integration/Iggy_SDK.Tests.Integration.csproj 
b/foreign/csharp/Iggy_SDK.Tests.Integration/Iggy_SDK.Tests.Integration.csproj
index 9c8b3cf1e..1b229b82c 100644
--- 
a/foreign/csharp/Iggy_SDK.Tests.Integration/Iggy_SDK.Tests.Integration.csproj
+++ 
b/foreign/csharp/Iggy_SDK.Tests.Integration/Iggy_SDK.Tests.Integration.csproj
@@ -12,6 +12,7 @@
 
     <ItemGroup>
         <PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" 
/>
+        <PackageReference Include="Microsoft.Testing.Extensions.Retry" />
         <PackageReference Include="Microsoft.Testing.Extensions.TrxReport" />
         <PackageReference Include="Shouldly" />
         <PackageReference Include="Testcontainers" />

Reply via email to