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)