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 7a22ed5b8 fix(csharp): use OS ephemeral ports in IggyClusterFixture to 
fix flaky CI (#3155)
7a22ed5b8 is described below

commit 7a22ed5b8680a5c872f23731340417e7ee45b812
Author: Krishna Vishal <[email protected]>
AuthorDate: Thu Apr 23 13:02:56 2026 +0530

    fix(csharp): use OS ephemeral ports in IggyClusterFixture to fix flaky CI 
(#3155)
---
 .../Fixtures/IggyClusterFixture.cs                 | 52 ++++++++++++++--------
 1 file changed, 33 insertions(+), 19 deletions(-)

diff --git 
a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs 
b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs
index 32b026a85..0717e6269 100644
--- a/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.Integration/Fixtures/IggyClusterFixture.cs
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+using System.Net;
+using System.Net.Sockets;
 using DotNet.Testcontainers.Builders;
 using DotNet.Testcontainers.Containers;
 using DotNet.Testcontainers.Networks;
@@ -27,8 +29,11 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
     private const string LeaderAlias = "iggy-leader";
     private const string FollowerAlias = "iggy-follower";
 
-    private static readonly Random Random = new();
-    private static readonly HashSet<ushort> UsedPorts = [];
+    // 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.
+    private readonly List<TcpListener> _portReservations = [];
     private readonly IContainer _followerContainer;
     private readonly ushort _followerHttpPort;
     private readonly ushort _followerQuicPort;
@@ -52,14 +57,14 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
 
     public IggyClusterFixture()
     {
-        _leaderTcpPort = GetRandomPort();
-        _leaderHttpPort = GetRandomPort();
-        _leaderQuicPort = GetRandomPort();
-        _leaderWsPort = GetRandomPort();
-        _followerTcpPort = GetRandomPort();
-        _followerHttpPort = GetRandomPort();
-        _followerQuicPort = GetRandomPort();
-        _followerWsPort = GetRandomPort();
+        _leaderTcpPort = ReservePort();
+        _leaderHttpPort = ReservePort();
+        _leaderQuicPort = ReservePort();
+        _leaderWsPort = ReservePort();
+        _followerTcpPort = ReservePort();
+        _followerHttpPort = ReservePort();
+        _followerQuicPort = ReservePort();
+        _followerWsPort = ReservePort();
 
         _network = new NetworkBuilder()
             .WithName($"iggy-cluster-{Guid.NewGuid():N}")
@@ -129,6 +134,7 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
 
     public async ValueTask DisposeAsync()
     {
+        ReleaseReservedPorts();
         await SaveContainerLogsAsync(_leaderContainer, "leader");
         await SaveContainerLogsAsync(_followerContainer, "follower");
         await _followerContainer.StopAsync();
@@ -139,6 +145,10 @@ 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());
     }
 
@@ -152,18 +162,22 @@ public class IggyClusterFixture : IAsyncInitializer, 
IAsyncDisposable
         return $"127.0.0.1:{_followerTcpPort}";
     }
 
-    private static ushort GetRandomPort()
+    private ushort ReservePort()
     {
-        lock (UsedPorts)
-        {
-            ushort port;
-            do
-            {
-                port = (ushort)Random.Next(30000, 40000);
-            } while (!UsedPorts.Add(port));
+        var listener = new TcpListener(IPAddress.Loopback, 0);
+        listener.Start();
+        _portReservations.Add(listener);
+        return (ushort)((IPEndPoint)listener.LocalEndpoint).Port;
+    }
 
-            return port;
+    private void ReleaseReservedPorts()
+    {
+        foreach (var listener in _portReservations)
+        {
+            listener.Stop();
         }
+
+        _portReservations.Clear();
     }
 
     private static async Task SaveContainerLogsAsync(IContainer container, 
string role)

Reply via email to