Increase SingleInstanceChecker timeouts for CI
MODIFY
WalletWasabi.Tests/UnitTests/Clients/SingleInstanceCheckerTests.cs
WalletWasabi.Tests/UnitTests/Clients/SingleInstanceCheckerTests.cs
@@ -13,16 +13,14 @@ namespace WalletWasabi.Tests.UnitTests.Clients;
[Collection("Serial unit tests collection")]
public class SingleInstanceCheckerTests
{
- private static Random Random { get; } = new();
+ /// <summary>Everything takes longer on CI. Timeouts sane on users' machines are too short for CI.</summary>
+ private const int TimeoutMultiplier = 3;
/// <summary>
/// Global port may collide when several PRs are being tested on CI at the same time,
/// so we need some sort of non-determinism here (i.e. random numbers).
/// </summary>
- private static int GenerateRandomPort()
- {
- return Random.Next(37128, 50000);
- }
+ private static int GenerateRandomPort() => Random.Shared.Next(37128, 50000);
[Fact]
public async Task SingleInstanceTestsAsync()
@@ -32,26 +30,26 @@ public class SingleInstanceCheckerTests
int regTestPort = testNetPort + 1;
// Disposal test.
- await using (SingleInstanceChecker sic = new(mainNetPort))
+ await using (SingleInstanceChecker sic = new(mainNetPort, TimeoutMultiplier))
{
await sic.EnsureSingleOrThrowAsync();
}
// Check different networks.
- await using SingleInstanceChecker sicMainNet = new(mainNetPort);
+ await using SingleInstanceChecker sicMainNet = new(mainNetPort, TimeoutMultiplier);
await sicMainNet.EnsureSingleOrThrowAsync();
- await using SingleInstanceChecker sicMainNet2 = new(mainNetPort);
- await Assert.ThrowsAsync<OperationCanceledException>(async () => await sicMainNet2.EnsureSingleOrThrowAsync());
+ await using SingleInstanceChecker sicMainNet2 = new(mainNetPort, TimeoutMultiplier);
+ await Assert.ThrowsAsync<OperationCanceledException>(sicMainNet2.EnsureSingleOrThrowAsync);
- await using SingleInstanceChecker sicTestNet = new(testNetPort);
+ await using SingleInstanceChecker sicTestNet = new(testNetPort, TimeoutMultiplier);
await sicTestNet.EnsureSingleOrThrowAsync();
- await using SingleInstanceChecker sicTestNet2 = new(testNetPort);
- await Assert.ThrowsAsync<OperationCanceledException>(async () => await sicTestNet2.EnsureSingleOrThrowAsync());
+ await using SingleInstanceChecker sicTestNet2 = new(testNetPort, TimeoutMultiplier);
+ await Assert.ThrowsAsync<OperationCanceledException>(sicTestNet2.EnsureSingleOrThrowAsync);
- await using SingleInstanceChecker sicRegTest = new(regTestPort);
+ await using SingleInstanceChecker sicRegTest = new(regTestPort, TimeoutMultiplier);
await sicRegTest.EnsureSingleOrThrowAsync();
- await using SingleInstanceChecker sicRegTest2 = new(regTestPort);
- await Assert.ThrowsAsync<OperationCanceledException>(async () => await sicRegTest2.EnsureSingleOrThrowAsync());
+ await using SingleInstanceChecker sicRegTest2 = new(regTestPort, TimeoutMultiplier);
+ await Assert.ThrowsAsync<OperationCanceledException>(sicRegTest2.EnsureSingleOrThrowAsync);
}
[Fact]
@@ -70,12 +68,12 @@ public class SingleInstanceCheckerTests
// I am the first instance this should be fine.
await firstInstance.EnsureSingleOrThrowAsync();
- await using SingleInstanceChecker secondInstance = new(mainNetPort);
+ await using SingleInstanceChecker secondInstance = new(mainNetPort, TimeoutMultiplier);
for (int i = 0; i < 2; i++)
{
// I am the second one.
- await Assert.ThrowsAsync<OperationCanceledException>(async () => await secondInstance.EnsureSingleOrThrowAsync());
+ await Assert.ThrowsAsync<OperationCanceledException>(secondInstance.EnsureSingleOrThrowAsync);
}
// Overall Timeout.
@@ -126,7 +124,7 @@ public class SingleInstanceCheckerTests
}
// One more to check if the first instance was able to recover from the port scan operation.
- await Assert.ThrowsAsync<OperationCanceledException>(async () => await secondInstance.EnsureSingleOrThrowAsync());
+ await Assert.ThrowsAsync<OperationCanceledException>(secondInstance.EnsureSingleOrThrowAsync);
while (Interlocked.Read(ref eventCalled) != 3)
{

MODIFY
WalletWasabi/Services/SingleInstanceChecker.cs
WalletWasabi/Services/SingleInstanceChecker.cs
@@ -15,6 +15,9 @@ public class SingleInstanceChecker : BackgroundService, IAsyncDisposable
private const string WasabiMagicString = "InBitcoinWeTrust";
public static readonly TimeSpan ClientTimeOut = TimeSpan.FromSeconds(2);
+ /// <summary>Multiplier to be applied to all timeouts in this class.</summary>
+ private readonly int _timeoutMultiplier;
+
/// <summary>
/// Creates an object to ensure mutual exclusion of Wasabi instances per Network <paramref name="network"/>.
/// The solution based on TCP socket.
@@ -27,9 +30,11 @@ public class SingleInstanceChecker : BackgroundService, IAsyncDisposable
/// <summary>
/// Use this constructor only for testing.
/// </summary>
- public SingleInstanceChecker(int port)
+ /// <param name="timeoutMultiplier">Used for multiplying all specified timeouts. For CI testing purposes only.</param>
+ public SingleInstanceChecker(int port, int timeoutMultiplier = 1)
{
Port = port;
+ _timeoutMultiplier = timeoutMultiplier;
}
private int Port { get; }
@@ -81,18 +86,18 @@ public class SingleInstanceChecker : BackgroundService, IAsyncDisposable
NoDelay = true
};
- using CancellationTokenSource timeoutCts = new(TimeSpan.FromSeconds(10));
+ using CancellationTokenSource timeoutCts = new(TimeSpan.FromSeconds(_timeoutMultiplier * 10));
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(DisposeCts.Token, timeoutCts.Token);
await client.ConnectAsync(IPAddress.Loopback, Port, cts.Token).ConfigureAwait(false);
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
await using NetworkStream networkStream = client.GetStream();
- networkStream.WriteTimeout = (int)ClientTimeOut.TotalMilliseconds * 2;
+ networkStream.WriteTimeout = _timeoutMultiplier * (int)ClientTimeOut.TotalMilliseconds * 2;
await using var writer = new StreamWriter(networkStream, Encoding.UTF8);
#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
- await writer.WriteAsync(new StringBuilder(WasabiMagicString), cts.Token).ConfigureAwait(false);
+ await writer.WriteAsync(WasabiMagicString.AsMemory(), cts.Token).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
await networkStream.FlushAsync(cts.Token).ConfigureAwait(false);
// I was able to signal to the other instance successfully so just continue.
@@ -147,10 +152,10 @@ public class SingleInstanceChecker : BackgroundService, IAsyncDisposable
await using NetworkStream networkStream = client.GetStream();
#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
- networkStream.ReadTimeout = (int)ClientTimeOut.TotalMilliseconds;
+ networkStream.ReadTimeout = _timeoutMultiplier * (int)ClientTimeOut.TotalMilliseconds;
using var reader = new StreamReader(networkStream, Encoding.UTF8);
// Make sure the client will be disconnected.
- using CancellationTokenSource timeOutCts = new(ClientTimeOut);
+ using CancellationTokenSource timeOutCts = new(_timeoutMultiplier * ClientTimeOut);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(timeOutCts.Token, stoppingToken);
// The read operation cancellation will happen on reader disposal.
@@ -195,7 +200,7 @@ public class SingleInstanceChecker : BackgroundService, IAsyncDisposable
DisposeCts.Cancel();
// Stopping the execution task and wait until it finishes.
- using CancellationTokenSource timeout = new(TimeSpan.FromSeconds(20));
+ using CancellationTokenSource timeout = new(TimeSpan.FromSeconds(_timeoutMultiplier * 20));
// This is added because Dispose is called from the Main and Main cannot be an async function.
while (!ExecuteTask.IsCompleted)

Increase SingleInstanceChecker timeouts for CI