Multiwalleting dequeue logic
MODIFY
WalletWasabi.Gui/Dialogs/CannotCloseDialogViewModel.cs
WalletWasabi.Gui/Dialogs/CannotCloseDialogViewModel.cs
@@ -96,7 +96,7 @@ namespace WalletWasabi.Gui.Dialogs
CancelTokenSource = new CancellationTokenSource().DisposeWith(Disposables);
- Initialization = StartDequeueAsync(CancelTokenSource.Token);
+ Initialization = StartDequeueAsync();
base.OnOpen();
}
@@ -108,7 +108,7 @@ namespace WalletWasabi.Gui.Dialogs
base.OnClose();
}
- private async Task StartDequeueAsync(CancellationToken token)
+ private async Task StartDequeueAsync()
{
IsBusy = true;
try
@@ -125,62 +125,10 @@ namespace WalletWasabi.Gui.Dialogs
}
}
- start = DateTime.Now;
- bool last = false;
- while (!last)
- {
- last = DateTime.Now - start > TimeSpan.FromMinutes(2);
- if (CancelTokenSource.IsCancellationRequested)
- {
- break;
- }
+ using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMinutes(6));
+ using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, CancelTokenSource.Token);
+ await Global.WalletManager.DequeueAllCoinsGracefullyAsync(DequeueReason.ApplicationExit, linkedCts.Token);
- try
- {
- if (Global.WalletService is null || Global.WalletService.ChaumianClient is null)
- {
- return;
- }
-
- SmartCoin[] enqueuedCoins = Global.WalletService.Coins.CoinJoinInProcess().ToArray();
- Exception latestException = null;
- foreach (var coin in enqueuedCoins)
- {
- try
- {
- if (CancelTokenSource.IsCancellationRequested)
- {
- break;
- }
-
- await Global.WalletService.ChaumianClient.DequeueCoinsFromMixAsync(new SmartCoin[] { coin }, DequeueReason.ApplicationExit); // Dequeue coins one-by-one to check cancel flag more frequently.
- }
- catch (Exception ex)
- {
- latestException = ex;
-
- if (last) // if this is the last iteration and we are still failing then we throw the exception
- {
- throw ex;
- }
- }
- }
-
- if (latestException is null) // no exceptions were thrown during the for-each so we are done with dequeuing
- {
- last = true;
- }
- }
- catch (Exception ex)
- {
- if (last)
- {
- throw ex;
- }
-
- await Task.Delay(5000, token); // wait, maybe the situation will change
- }
- }
if (!CancelTokenSource.IsCancellationRequested)
{
CancelTokenSource.Cancel();
@@ -189,7 +137,7 @@ namespace WalletWasabi.Gui.Dialogs
}
catch (Exception ex)
{
- SetWarningMessage(ex.Message, token);
+ SetWarningMessage(ex.Message, CancelTokenSource.Token);
}
finally
{

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -70,7 +70,7 @@ namespace WalletWasabi.Gui
public HostedServices HostedServices { get; }
- public bool KillRequested { get; private set; } = false;
+ public bool KillRequested => Interlocked.Read(ref _dispose) > 0;
public UiConfig UiConfig { get; private set; }
@@ -119,49 +119,6 @@ namespace WalletWasabi.Gui
return false;
}
- private int _isDesperateDequeuing = 0;
-
- public async Task TryDesperateDequeueAllCoinsAsync()
- {
- // If already desperate dequeuing then return.
- // If not desperate dequeuing then make sure we're doing that.
- if (Interlocked.CompareExchange(ref _isDesperateDequeuing, 1, 0) == 1)
- {
- return;
- }
- try
- {
- await DesperateDequeueAllCoinsAsync();
- }
- catch (NotSupportedException ex)
- {
- Logger.LogWarning(ex.Message);
- }
- catch (Exception ex)
- {
- Logger.LogWarning(ex);
- }
- finally
- {
- Interlocked.Exchange(ref _isDesperateDequeuing, 0);
- }
- }
-
- public async Task DesperateDequeueAllCoinsAsync()
- {
- if (WalletService is null || WalletService.ChaumianClient is null)
- {
- return;
- }
-
- SmartCoin[] enqueuedCoins = WalletService.Coins.CoinJoinInProcess().ToArray();
- if (enqueuedCoins.Any())
- {
- Logger.LogWarning("Unregistering coins in CoinJoin process.");
- await WalletService.ChaumianClient.DequeueCoinsFromMixAsync(enqueuedCoins, DequeueReason.ApplicationExit);
- }
- }
-
private bool InitializationCompleted { get; set; } = false;
private bool InitializationStarted { get; set; } = false;
@@ -171,13 +128,12 @@ namespace WalletWasabi.Gui
public async Task InitializeNoWalletAsync()
{
InitializationStarted = true;
-
- try
- {
AddressManager = null;
TorManager = null;
var cancel = StoppingCts.Token;
+ try
+ {
#region ConfigInitialization
Config = new Config(Path.Combine(DataDir, "Config.json"));
@@ -211,11 +167,12 @@ namespace WalletWasabi.Gui
#region ProcessKillSubscription
- AppDomain.CurrentDomain.ProcessExit += async (s, e) => await TryDesperateDequeueAllCoinsAsync();
+ AppDomain.CurrentDomain.ProcessExit += async (s, e) => await DisposeAsync().ConfigureAwait(false);
Console.CancelKeyPress += async (s, e) =>
{
e.Cancel = true;
- await StopAndExitAsync();
+ Logger.LogWarning("Process was signaled for killing.", nameof(Global));
+ await DisposeAsync().ConfigureAwait(false);
};
#endregion ProcessKillSubscription
@@ -403,8 +360,12 @@ namespace WalletWasabi.Gui
WalletManager.Initialize(BitcoinStore, Synchronizer, Nodes, DataDir, Config.ServiceConfiguration, FeeProviders, BitcoinCoreNode);
}
catch (Exception ex)
+ {
+ if (!cancel.IsCancellationRequested)
{
Logger.LogCritical(ex);
+ }
+
InitializationCompleted = true;
await DisposeAsync().ConfigureAwait(false);
Environment.Exit(1);
@@ -415,22 +376,6 @@ namespace WalletWasabi.Gui
}
}
- internal async Task StopAndExitAsync()
- {
- Logger.LogWarning("Process was signaled for killing.", nameof(Global));
-
- KillRequested = true;
- await TryDesperateDequeueAllCoinsAsync();
- Dispatcher.UIThread.PostLogException(() =>
- {
- var window = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).MainWindow;
- window?.Close();
- });
- await DisposeAsync();
-
- Logger.LogSoftwareStopped("Wasabi");
- }
-
private async Task<AddressManagerBehavior> InitializeAddressManagerBehaviorAsync()
{
var needsToDiscoverPeers = true;
@@ -754,6 +699,7 @@ namespace WalletWasabi.Gui
{
return;
}
+ Logger.LogWarning("Process is exiting.", nameof(Global));
try
{
@@ -766,7 +712,14 @@ namespace WalletWasabi.Gui
await WaitForInitializationCompletedAsync().ConfigureAwait(false);
- await WalletManager.RemoveAndStopAllAsync().ConfigureAwait(false);
+ using var dequeueCts = new CancellationTokenSource(TimeSpan.FromMinutes(6));
+ await WalletManager.RemoveAndStopAllAsync(dequeueCts.Token).ConfigureAwait(false);
+
+ Dispatcher.UIThread.PostLogException(() =>
+ {
+ var window = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).MainWindow;
+ window?.Close();
+ });
WalletManager.OnDequeue -= WalletManager_OnDequeue;
WalletManager.WalletRelevantTransactionProcessed -= WalletManager_WalletRelevantTransactionProcessed;
@@ -879,6 +832,7 @@ namespace WalletWasabi.Gui
{
StoppingCts?.Dispose();
Interlocked.Exchange(ref _dispose, 2);
+ Logger.LogSoftwareStopped("Wasabi");
}
}

MODIFY
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
using System;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using NBitcoin;
using WalletWasabi.Blockchain.Analysis.Clustering;
@@ -213,7 +214,7 @@ namespace WalletWasabi.Gui.Rpc
[JsonRpcMethod("stop")]
public async Task StopAsync()
{
- await Global.StopAndExitAsync().ConfigureAwait(false);
+ await Global.DisposeAsync().ConfigureAwait(false);
}
private void AssertWalletIsLoaded()

MODIFY
WalletWasabi.Tests/RegressionTests/BuildTests.cs
WalletWasabi.Tests/RegressionTests/BuildTests.cs
@@ -395,7 +395,7 @@ namespace WalletWasabi.Tests.RegressionTests
finally
{
bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
- await walletManager.RemoveAndStopAllAsync();
+ await walletManager.RemoveAndStopAllAsync(CancellationToken.None);
// Dispose wasabi synchronizer service.
if (synchronizer is { })
{

MODIFY
WalletWasabi.Tests/RegressionTests/SendTests.cs
WalletWasabi.Tests/RegressionTests/SendTests.cs
@@ -495,7 +495,7 @@ namespace WalletWasabi.Tests.RegressionTests
finally
{
bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
- await walletManager.RemoveAndStopAllAsync();
+ await walletManager.RemoveAndStopAllAsync(CancellationToken.None);
// Dispose wasabi synchronizer service.
if (synchronizer is { })
{
@@ -663,7 +663,7 @@ namespace WalletWasabi.Tests.RegressionTests
finally
{
bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
- await walletManager.RemoveAndStopAllAsync();
+ await walletManager.RemoveAndStopAllAsync(CancellationToken.None);
// Dispose wasabi synchronizer service.
if (synchronizer is { })
{

MODIFY
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
@@ -832,11 +832,6 @@ namespace WalletWasabi.CoinJoin.Client.Clients
}
}
- public async Task DequeueCoinsFromMixAsync(TxoRef coin, DequeueReason reason)
- {
- await DequeueCoinsFromMixAsync(new[] { coin }, reason).ConfigureAwait(false);
- }
-
public async Task DequeueCoinsFromMixAsync(TxoRef[] coins, DequeueReason reason)
{
if (coins is null || !coins.Any())
@@ -1028,7 +1023,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
using (await MixLock.LockAsync(cancel).ConfigureAwait(false))
{
- await DequeueSpentCoinsFromMixNoLockAsync().ConfigureAwait(false);
+ await DequeueAllCoinsFromMixGracefullyAsync(DequeueReason.ApplicationExit, cancel).ConfigureAwait(false);
State.DisposeAllAliceClients();
@@ -1062,5 +1057,23 @@ namespace WalletWasabi.CoinJoin.Client.Clients
}
}
}
+
+ public async Task DequeueAllCoinsFromMixGracefullyAsync(DequeueReason reason, CancellationToken cancel)
+ {
+ while (true)
+ {
+ cancel.ThrowIfCancellationRequested();
+
+ try
+ {
+ await DequeueAllCoinsFromMixAsync(reason).ConfigureAwait(false);
+ break;
+ }
+ catch
+ {
+ await Task.Delay(1000, cancel).ConfigureAwait(false); // wait, maybe the situation will change
+ }
+ }
+ }
}
}

MODIFY
WalletWasabi/Wallets/WalletManager.cs
WalletWasabi/Wallets/WalletManager.cs
@@ -114,6 +114,16 @@ namespace WalletWasabi.Wallets
}
}
+ public async Task DequeueAllCoinsGracefullyAsync(DequeueReason reason, CancellationToken token)
+ {
+ IEnumerable<Task> tasks = null;
+ lock (Lock)
+ {
+ tasks = Wallets.Keys.Select(x => x.ChaumianClient.DequeueAllCoinsFromMixGracefullyAsync(reason, token)).ToArray();
+ }
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ }
+
private void ChaumianClient_OnDequeue(object sender, DequeueResult e)
{
var handler = OnDequeue;
@@ -126,7 +136,7 @@ namespace WalletWasabi.Wallets
handler?.Invoke(sender, e);
}
- public async Task RemoveAndStopAllAsync()
+ public async Task RemoveAndStopAllAsync(CancellationToken cancel)
{
try
{
@@ -138,7 +148,7 @@ namespace WalletWasabi.Wallets
Logger.LogWarning($"{nameof(CancelAllInitialization)} is disposed. This can occur due to an error while processing the wallet.");
}
- using (await AddRemoveLock.LockAsync().ConfigureAwait(false))
+ using (await AddRemoveLock.LockAsync(cancel).ConfigureAwait(false))
{
List<WalletService> walletsListClone;
lock (Lock)
@@ -147,6 +157,11 @@ namespace WalletWasabi.Wallets
}
foreach (var walletService in walletsListClone)
{
+ if (cancel.IsCancellationRequested)
+ {
+ return;
+ }
+
walletService.TransactionProcessor.WalletRelevantTransactionProcessed -= TransactionProcessor_WalletRelevantTransactionProcessed;
walletService.ChaumianClient.OnDequeue -= ChaumianClient_OnDequeue;
@@ -158,6 +173,8 @@ namespace WalletWasabi.Wallets
}
}
+ try
+ {
var keyManager = walletService.KeyManager;
if (keyManager is { } && !string.IsNullOrWhiteSpace(WalletBackupsDir))
{
@@ -165,9 +182,14 @@ namespace WalletWasabi.Wallets
keyManager.ToFile(backupWalletFilePath);
Logger.LogInfo($"{nameof(walletService.KeyManager)} backup saved to `{backupWalletFilePath}`.");
}
- await walletService.StopAsync(CancellationToken.None).ConfigureAwait(false);
+ await walletService.StopAsync(cancel).ConfigureAwait(false);
Logger.LogInfo($"{nameof(WalletService)} is stopped.");
}
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ }
+ }
}
}

Multiwalleting dequeue logic