Add TaskRemembler
MODIFY
WalletWasabi/Blockchain/Transactions/TransactionStore.cs
WalletWasabi/Blockchain/Transactions/TransactionStore.cs
@@ -10,6 +10,7 @@ using WalletWasabi.Blockchain.Transactions.Operations;
using WalletWasabi.Helpers;
using WalletWasabi.Io;
using WalletWasabi.Logging;
+using WalletWasabi.Nito.AsyncEx;
namespace WalletWasabi.Blockchain.Transactions
{
@@ -25,6 +26,8 @@ namespace WalletWasabi.Blockchain.Transactions
private List<ITxStoreOperation> Operations { get; } = new List<ITxStoreOperation>();
private object OperationsLock { get; } = new object();
+ private TaskRemembler TaskRemembler { get; } = new TaskRemembler();
+
public async Task InitializeAsync(string workFolderPath, Network network, string operationName)
{
using (BenchmarkLogger.Measure(operationName: operationName))
@@ -111,12 +114,12 @@ namespace WalletWasabi.Blockchain.Transactions
if (ret.isAdded)
{
- _ = TryAppendToFileAsync(tx);
+ TaskRemembler.AddAndClearCompleted(TryAppendToFileAsync(tx));
}
if (ret.isUpdated)
{
- _ = TryUpdateFileAsync(tx);
+ TaskRemembler.AddAndClearCompleted(TryUpdateFileAsync(tx));
}
return ret;
@@ -153,7 +156,7 @@ namespace WalletWasabi.Blockchain.Transactions
if (ret)
{
- _ = TryUpdateFileAsync(tx);
+ TaskRemembler.AddAndClearCompleted(TryUpdateFileAsync(tx));
}
return ret;
@@ -182,7 +185,7 @@ namespace WalletWasabi.Blockchain.Transactions
if (isRemoved)
{
- _ = TryRemoveFromFileAsync(hash);
+ TaskRemembler.AddAndClearCompleted(TryRemoveFromFileAsync(hash));
}
return isRemoved;
@@ -395,7 +398,7 @@ namespace WalletWasabi.Blockchain.Transactions
public async ValueTask DisposeAsync()
{
- using var _ = await TransactionsFileAsyncLock.LockAsync().ConfigureAwait(false);
+ await TaskRemembler.WhenAllAsync().ConfigureAwait(false);
}
}
}

MODIFY
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
@@ -22,6 +22,7 @@ using WalletWasabi.Crypto.Randomness;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Models;
+using WalletWasabi.Nito.AsyncEx;
using WalletWasabi.Services;
using WalletWasabi.WebClients.Wasabi;
using static WalletWasabi.Crypto.SchnorrBlinding;
@@ -47,6 +48,8 @@ namespace WalletWasabi.CoinJoin.Client.Clients
private long _statusProcessing;
+ private TaskRemembler TaskRemembler { get; } = new TaskRemembler();
+
public CoinJoinClient(
WasabiSynchronizer synchronizer,
Network network,
@@ -74,7 +77,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
var lastResponse = Synchronizer.LastResponse;
if (lastResponse is { })
{
- _ = TryProcessStatusAsync(Synchronizer.LastResponse.CcjRoundStates);
+ TaskRemembler.AddAndClearCompleted(TryProcessStatusAsync(Synchronizer.LastResponse.CcjRoundStates));
}
}
@@ -1050,6 +1053,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
}
}
}
+ await TaskRemembler.WhenAllAsync().ConfigureAwait(false);
}
public async Task DequeueAllCoinsFromMixGracefullyAsync(DequeueReason reason, CancellationToken cancel)

ADD
WalletWasabi/Nito/AsyncEx/TaskRemembler.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.Nito.AsyncEx
+{
+ /// <summary>
+ /// To remember tasks those were fired to forget and so wait for them during dispose.
+ /// </summary>
+ public class TaskRemembler
+ {
+ private HashSet<Task> Tasks { get; } = new HashSet<Task>();
+ private object Lock { get; } = new object();
+
+ /// <summary>
+ /// Adds tasks and clears completed ones atomically.
+ /// </summary>
+ public void AddAndClearCompleted(params Task[] tasks)
+ {
+ lock (Lock)
+ {
+ AddNoLock(tasks);
+
+ ClearCompletedNoLock();
+ }
+ }
+
+ /// <summary>
+ /// Wait for all tasks to complete.
+ /// </summary>
+ public async Task WhenAllAsync()
+ {
+ do
+ {
+ Task[] tasks;
+ lock (Lock)
+ {
+ // 1. Clear all the completed tasks.
+ ClearCompletedNoLock();
+ tasks = Tasks.ToArray();
+
+ // 2. If all tasks cleared, then break.
+ if (!tasks.Any())
+ {
+ break;
+ }
+ }
+
+ // 3. Wait for all tasks to complete.
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ } while (true);
+ }
+
+ private void AddNoLock(params Task[] tasks)
+ {
+ foreach (var t in tasks)
+ {
+ Tasks.Add(t);
+ }
+ }
+
+ private void ClearCompletedNoLock()
+ {
+ var toRemove = new List<Task>();
+
+ foreach (var t in Tasks)
+ {
+ if (t.IsCompleted)
+ {
+ toRemove.Add(t);
+ }
+ }
+
+ foreach (var t in toRemove)
+ {
+ Tasks.Remove(t);
+ }
+ }
+ }
+}

MODIFY
WalletWasabi/Stores/IndexStore.cs
WalletWasabi/Stores/IndexStore.cs
@@ -13,6 +13,7 @@ using WalletWasabi.Helpers;
using WalletWasabi.Io;
using WalletWasabi.Logging;
using WalletWasabi.Models;
+using WalletWasabi.Nito.AsyncEx;
namespace WalletWasabi.Stores
{
@@ -39,6 +40,8 @@ namespace WalletWasabi.Stores
SmartHeaderChain = Guard.NotNull(nameof(hashChain), hashChain);
}
+ private TaskRemembler TaskRemembler { get; } = new TaskRemembler();
+
public event EventHandler<FilterModel>? Reorged;
public event EventHandler<FilterModel>? NewFilter;
@@ -300,7 +303,7 @@ namespace WalletWasabi.Stores
if (successAny)
{
- _ = TryCommitToFileAsync(TimeSpan.FromSeconds(3), cancel);
+ TaskRemembler.AddAndClearCompleted(TryCommitToFileAsync(TimeSpan.FromSeconds(3), cancel));
}
}
@@ -321,7 +324,7 @@ namespace WalletWasabi.Stores
Reorged?.Invoke(this, filter);
- _ = TryCommitToFileAsync(TimeSpan.FromSeconds(3), cancel);
+ TaskRemembler.AddAndClearCompleted(TryCommitToFileAsync(TimeSpan.FromSeconds(3), cancel));
return filter;
}
@@ -500,10 +503,7 @@ namespace WalletWasabi.Stores
public async ValueTask DisposeAsync()
{
- // Wait for the ongoing operations related to locks.
- using var dispose1 = await IndexLock.LockAsync().ConfigureAwait(false);
- using var dispose2 = await MatureIndexAsyncLock.LockAsync().ConfigureAwait(false);
- using var dispose3 = await ImmatureIndexAsyncLock.LockAsync().ConfigureAwait(false);
+ await TaskRemembler.WhenAllAsync().ConfigureAwait(false);
}
}
}

Add TaskRemembler