Make sure Stopping IndexDownloader is near instant.
MODIFY
WalletWasabi/Extensions/TorHttpClientExtensions.cs
WalletWasabi/Extensions/TorHttpClientExtensions.cs
+using System;
using System.Net;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.TorSocks5;
public static class TorHttpClientExtensions
{
- public static async Task<HttpResponseMessage> SendAndRetryAsync(this TorHttpClient client, HttpMethod method, HttpStatusCode expectedCode, string relativeUri, int retry = 2, HttpContent content = null)
+ /// <remarks>
+ /// Throws OperationCancelledException if <paramref name="cancel"/> is set.
+ /// </remarks>
+ public static async Task<HttpResponseMessage> SendAndRetryAsync(this TorHttpClient client, HttpMethod method, HttpStatusCode expectedCode, string relativeUri, int retry = 2, HttpContent content = null, CancellationToken cancel = default)
{
HttpResponseMessage response = null;
while (retry-- > 0)
{
response?.Dispose();
- response = await client.SendAsync(method, relativeUri, content);
+ cancel.ThrowIfCancellationRequested();
+ response = await client.SendAsync(method, relativeUri, content, cancel: cancel);
if (response.StatusCode == expectedCode)
{
break;
}
- await Task.Delay(1000);
+ try
+ {
+ await Task.Delay(1000, cancel);
+ }
+ catch (TaskCanceledException ex)
+ {
+ throw new OperationCanceledException(ex.Message, ex, cancel);
+ }
}
return response;
}

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -47,7 +47,7 @@ namespace WalletWasabi.Services
public bool IsRunning => Interlocked.Read(ref _running) == 1;
public bool IsStopping => Interlocked.Read(ref _running) == 2;
- private CancellationTokenSource Stop { get; }
+ private CancellationTokenSource Cancel { get; }
public CcjClient(Network network, BlindingRsaPubKey coordinatorPubKey, KeyManager keyManager, Uri ccjHostUri, IPEndPoint torSocks5EndPoint = null)
{
@@ -59,7 +59,7 @@ namespace WalletWasabi.Services
SatoshiClient = new SatoshiClient(ccjHostUri, torSocks5EndPoint);
_running = 0;
- Stop = new CancellationTokenSource();
+ Cancel = new CancellationTokenSource();
_frequentStatusProcessingIfNotMixing = 0;
State = new CcjClientState();
MixLock = new AsyncLock();
@@ -122,7 +122,7 @@ namespace WalletWasabi.Services
}
}
- await Task.Delay(TimeSpan.FromSeconds(delay), Stop.Token);
+ await Task.Delay(TimeSpan.FromSeconds(delay), Cancel.Token);
await ProcessStatusAsync();
}
catch (TaskCanceledException ex)
@@ -160,7 +160,7 @@ namespace WalletWasabi.Services
delay = new Random().Next(0, 7); // delay the response to defend timing attack privacy
}
- await Task.Delay(TimeSpan.FromSeconds(delay), Stop.Token);
+ await Task.Delay(TimeSpan.FromSeconds(delay), Cancel.Token);
using (await MixLock.LockAsync())
{
@@ -648,13 +648,13 @@ namespace WalletWasabi.Services
{
Interlocked.Exchange(ref _running, 2);
}
- Stop?.Cancel();
+ Cancel?.Cancel();
while (IsStopping)
{
await Task.Delay(50);
}
- Stop?.Dispose();
+ Cancel?.Dispose();
using (await MixLock.LockAsync())
{

MODIFY
WalletWasabi/Services/IndexDownloader.cs
WalletWasabi/Services/IndexDownloader.cs
@@ -64,6 +64,8 @@ namespace WalletWasabi.Services
public bool IsRunning => Interlocked.Read(ref _running) == 1;
public bool IsStopping => Interlocked.Read(ref _running) == 2;
+ private CancellationTokenSource Cancel { get; }
+
public IndexDownloader(Network network, string indexFilePath, Uri indexHostUri, IPEndPoint torSocks5EndPoint = null)
{
Network = Guard.NotNull(nameof(network), network);
@@ -74,6 +76,7 @@ namespace WalletWasabi.Services
IndexLock = new AsyncLock();
_running = 0;
+ Cancel = new CancellationTokenSource();
var indexDir = Path.GetDirectoryName(IndexFilePath);
if (!string.IsNullOrEmpty(indexDir))
@@ -128,13 +131,13 @@ namespace WalletWasabi.Services
bestKnownFilter = BestFilter;
- var filters = await WasabiClient.GetFiltersAsync(bestKnownFilter.BlockHash, 1000);
+ var filters = await WasabiClient.GetFiltersAsync(bestKnownFilter.BlockHash, 1000, Cancel.Token);
if (!filters.Any())
{
continue;
}
- using (await IndexLock.LockAsync())
+ using (await IndexLock.LockAsync(Cancel.Token))
{
var filtersList = filters.ToList(); // performance
for (int i = 0; i < filtersList.Count; i++)
@@ -160,13 +163,21 @@ namespace WalletWasabi.Services
continue;
}
+ catch (TaskCanceledException ex)
+ {
+ Logger.LogTrace<CcjClient>(ex);
+ }
+ catch (OperationCanceledException ex)
+ {
+ Logger.LogTrace<CcjClient>(ex);
+ }
catch (HttpRequestException ex) when (ex.Message.StartsWith(HttpStatusCode.NotFound.ToReasonString()))
{
// Reorg happened
var reorgedHash = bestKnownFilter.BlockHash;
Logger.LogInfo<IndexDownloader>($"REORG Invalid Block: {reorgedHash}");
// 1. Rollback index
- using (await IndexLock.LockAsync())
+ using (await IndexLock.LockAsync(Cancel.Token))
{
Index.RemoveAt(Index.Count - 1);
BestFilter = Index.Last();
@@ -189,7 +200,14 @@ namespace WalletWasabi.Services
{
if (IsRunning)
{
- await Task.Delay(requestInterval); // Ask for new index in every requestInterval.
+ try
+ {
+ await Task.Delay(requestInterval, Cancel.Token); // Ask for new index in every requestInterval.
+ }
+ catch (TaskCanceledException ex)
+ {
+ Logger.LogTrace<CcjClient>(ex);
+ }
}
}
}
@@ -219,11 +237,13 @@ namespace WalletWasabi.Services
{
Interlocked.Exchange(ref _running, 2);
}
+ Cancel?.Cancel();
while (IsStopping)
{
Task.Delay(50).GetAwaiter().GetResult(); // DO NOT MAKE IT ASYNC (.NET Core threading brainfart)
}
+ Cancel?.Dispose();
WasabiClient?.Dispose();
}

MODIFY
WalletWasabi/TorSocks5/TorHttpClient.cs
WalletWasabi/TorSocks5/TorHttpClient.cs
@@ -14,6 +14,7 @@ using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
+using System.Threading;
namespace WalletWasabi.TorSocks5
{
@@ -61,7 +62,10 @@ namespace WalletWasabi.TorSocks5
}
}
- public async Task<HttpResponseMessage> SendAsync(HttpMethod method, string relativeUri, HttpContent content = null)
+ /// <remarks>
+ /// Throws OperationCancelledException if <paramref name="cancel"/> is set.
+ /// </remarks>
+ public async Task<HttpResponseMessage> SendAsync(HttpMethod method, string relativeUri, HttpContent content = null, CancellationToken cancel = default)
{
Guard.NotNull(nameof(method), method);
relativeUri = Guard.NotNull(nameof(relativeUri), relativeUri);
@@ -72,7 +76,9 @@ namespace WalletWasabi.TorSocks5
request.Content = content;
}
- using (await AsyncLock.LockAsync())
+ try
+ {
+ using (await AsyncLock.LockAsync(cancel))
{
try
{
@@ -85,6 +91,7 @@ namespace WalletWasabi.TorSocks5
TorSocks5Client?.Dispose(); // rebuild the connection and retry
TorSocks5Client = null;
+ cancel.ThrowIfCancellationRequested();
try
{
return await SendAsync(request);
@@ -97,14 +104,31 @@ namespace WalletWasabi.TorSocks5
TorSocks5Client?.Dispose(); // rebuild the connection and retry
TorSocks5Client = null;
- await Task.Delay(1000);
+ try
+ {
+ await Task.Delay(1000, cancel);
+ }
+ catch (TaskCanceledException tce)
+ {
+ throw new OperationCanceledException(tce.Message, tce, cancel);
+ }
+ }
+
+ cancel.ThrowIfCancellationRequested();
return await SendAsync(request);
}
}
}
+ catch (TaskCanceledException ex)
+ {
+ throw new OperationCanceledException(ex.Message, ex, cancel);
+ }
}
- private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
+ /// <remarks>
+ /// Throws OperationCancelledException if <paramref name="cancel"/> is set.
+ /// </remarks>
+ private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancel = default)
{
Guard.NotNull(nameof(request), request);
@@ -165,6 +189,7 @@ namespace WalletWasabi.TorSocks5
TorSocks5Client.Stream = stream;
}
+ cancel.ThrowIfCancellationRequested();
// https://tools.ietf.org/html/rfc7230#section-3.3.2
// A user agent SHOULD send a Content - Length in a request message when

MODIFY
WalletWasabi/WebClients/ChaumianCoinJoin/WasabiClient.cs
WalletWasabi/WebClients/ChaumianCoinJoin/WasabiClient.cs
@@ -12,6 +12,7 @@ using WalletWasabi.Bases;
using WalletWasabi.Models;
using System.Text;
using NBitcoin.RPC;
+using System.Threading;
namespace WalletWasabi.WebClients.ChaumianCoinJoin
{
@@ -24,11 +25,15 @@ namespace WalletWasabi.WebClients.ChaumianCoinJoin
#region blockchain
- public async Task<IEnumerable<string>> GetFiltersAsync(uint256 bestKnownBlockHash, int count)
+ /// <remarks>
+ /// Throws OperationCancelledException if <paramref name="cancel"/> is set.
+ /// </remarks>
+ public async Task<IEnumerable<string>> GetFiltersAsync(uint256 bestKnownBlockHash, int count, CancellationToken cancel = default)
{
using (var response = await TorClient.SendAndRetryAsync(HttpMethod.Get,
HttpStatusCode.OK,
- $"/api/v1/btc/blockchain/filters?bestKnownBlockHash={bestKnownBlockHash}&count={count}"))
+ $"/api/v1/btc/blockchain/filters?bestKnownBlockHash={bestKnownBlockHash}&count={count}",
+ cancel: cancel))
{
if (response.StatusCode == HttpStatusCode.NoContent)
{
@@ -94,7 +99,7 @@ namespace WalletWasabi.WebClients.ChaumianCoinJoin
await BroadcastAsync(transaction.Transaction);
}
- #endregion
+ #endregion blockchain
#region offchain
@@ -117,6 +122,6 @@ namespace WalletWasabi.WebClients.ChaumianCoinJoin
}
}
- #endregion
+ #endregion offchain
}
}

Make sure Stopping IndexDownloader is near instant.