Randomize lock names in SingleInstanceChecker test.
MODIFY
WalletWasabi.Tests/UnitTests/Clients/SingleInstanceCheckerTests.cs
WalletWasabi.Tests/UnitTests/Clients/SingleInstanceCheckerTests.cs
@@ -13,29 +13,42 @@ namespace WalletWasabi.Tests.UnitTests.Clients
[Fact]
public async Task SingleInstanceTestsAsync()
{
+ string mainLockName = GenerateLockName(Network.Main);
+
// Disposal test.
- using (SingleInstanceChecker sic = new SingleInstanceChecker(Network.Main))
+ using (SingleInstanceChecker sic = new SingleInstanceChecker(Network.Main, mainLockName))
{
await sic.CheckAsync();
}
// Check different networks.
- using (SingleInstanceChecker sic = new SingleInstanceChecker(Network.Main))
+ using (SingleInstanceChecker sic = new SingleInstanceChecker(Network.Main, mainLockName))
{
await sic.CheckAsync();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sic.CheckAsync());
- using SingleInstanceChecker sicMainNet2 = new SingleInstanceChecker(Network.Main);
+ using SingleInstanceChecker sicMainNet2 = new SingleInstanceChecker(Network.Main, mainLockName);
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sicMainNet2.CheckAsync());
- using SingleInstanceChecker sicTestNet = new SingleInstanceChecker(Network.TestNet);
+ string testnetLockName = GenerateLockName(Network.TestNet);
+ using SingleInstanceChecker sicTestNet = new SingleInstanceChecker(Network.TestNet, testnetLockName);
await sicTestNet.CheckAsync();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sicTestNet.CheckAsync());
- using SingleInstanceChecker sicRegTest = new SingleInstanceChecker(Network.RegTest);
+ string regtestLockName = GenerateLockName(Network.RegTest);
+ using SingleInstanceChecker sicRegTest = new SingleInstanceChecker(Network.RegTest, regtestLockName);
await sicRegTest.CheckAsync();
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sicRegTest.CheckAsync());
}
}
+
+ /// <summary>
+ /// Global lock names may collide when several PRs are being tested on CI at the same time,
+ /// so we need some sort of non-determinism here (e.g. random numbers).
+ /// </summary>
+ private static string GenerateLockName(Network network)
+ {
+ return $"{network}-{new Random().Next(1_000_000)}";
+ }
}
}

MODIFY
WalletWasabi/Services/SingleInstanceChecker.cs
WalletWasabi/Services/SingleInstanceChecker.cs
@@ -12,11 +12,28 @@ namespace WalletWasabi.Services
/// <summary>Unique prefix for global mutex name.</summary>
private const string MutexString = "WalletWasabiSingleInstance";
+ /// <summary>Name of system-wide mutex.</summary>
+ private readonly string LockName;
+
private bool _disposedValue;
- public SingleInstanceChecker(Network network)
+ /// <summary>
+ /// Creates a new instance of the object where lock name is based on <paramref name="network"/> name.
+ /// </summary>
+ /// <param name="network">Bitcoin network selected when Wasabi Wallet was started.</param>
+ public SingleInstanceChecker(Network network): this(network, network.ToString())
+ {
+ }
+
+ /// <summary>
+ /// Use this constructor only for testing.
+ /// </summary>
+ /// <param name="network">Bitcoin network selected when Wasabi Wallet was started.</param>
+ /// <param name="lockName">Name of system-wide mutex.</param>
+ public SingleInstanceChecker(Network network, string lockName)
{
Network = network;
+ LockName = $"{MutexString}-{lockName}";
}
private IDisposable? SingleApplicationLockHolder { get; set; }
@@ -30,7 +47,7 @@ namespace WalletWasabi.Services
}
// The disposal of this mutex handled by AsyncMutex.WaitForAllMutexToCloseAsync().
- var mutex = new AsyncMutex($"{MutexString}-{Network}");
+ var mutex = new AsyncMutex(LockName);
try
{
using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200));

Randomize lock names in SingleInstanceChecker test.