Propagate cancellation token correctly and prefer OperationCanceledException over end-of-stream.
MODIFY
WalletWasabi/Extensions/StreamExtensions.cs
WalletWasabi/Extensions/StreamExtensions.cs
@@ -42,6 +42,7 @@ namespace System.IO
/// <param name="count">Number of bytes to read.</param>
/// <param name="cancellationToken">Cancellation token to cancel the asynchronous operation.</param>
/// <returns>Number of read bytes. At most <paramref name="count"/>.</returns>
+ /// <exception cref="OperationCanceledException">When operation is canceled.</exception>
public static async Task<int> ReadBlockAsync(this Stream stream, byte[] buffer, int count, CancellationToken cancellationToken = default)
{
int remaining = count;
@@ -49,6 +50,8 @@ namespace System.IO
{
int read = await stream.ReadAsync(buffer.AsMemory(count - remaining, remaining), cancellationToken).ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+
// End of stream.
if (read == 0)
{

MODIFY
WalletWasabi/Tor/Http/Extensions/HttpResponseMessageExtensions.cs
WalletWasabi/Tor/Http/Extensions/HttpResponseMessageExtensions.cs
@@ -15,7 +15,7 @@ namespace WalletWasabi.Tor.Http.Extensions
{
public static class HttpResponseMessageExtensions
{
- public static async Task<HttpResponseMessage> CreateNewAsync(Stream responseStream, HttpMethod requestMethod)
+ public static async Task<HttpResponseMessage> CreateNewAsync(Stream responseStream, HttpMethod requestMethod, CancellationToken cancellationToken = default)
{
// https://tools.ietf.org/html/rfc7230#section-3
// The normal procedure for parsing an HTTP message is to read the
@@ -36,18 +36,18 @@ namespace WalletWasabi.Tor.Http.Extensions
// CRLF
// [message - body]
- string startLine = await HttpMessageHelper.ReadStartLineAsync(responseStream).ConfigureAwait(false);
+ string startLine = await HttpMessageHelper.ReadStartLineAsync(responseStream, cancellationToken).ConfigureAwait(false);
- var statusLine = StatusLine.Parse(startLine);
- var response = new HttpResponseMessage(statusLine.StatusCode);
+ StatusLine statusLine = StatusLine.Parse(startLine);
+ HttpResponseMessage response = new(statusLine.StatusCode);
- string headers = await HttpMessageHelper.ReadHeadersAsync(responseStream).ConfigureAwait(false);
+ string headers = await HttpMessageHelper.ReadHeadersAsync(responseStream, cancellationToken).ConfigureAwait(false);
- var headerSection = await HeaderSection.CreateNewAsync(headers).ConfigureAwait(false);
- var headerStruct = headerSection.ToHttpResponseHeaders();
+ HeaderSection headerSection = await HeaderSection.CreateNewAsync(headers).ConfigureAwait(false);
+ HttpResponseContentHeaders headerStruct = headerSection.ToHttpResponseHeaders();
HttpMessageHelper.AssertValidHeaders(headerStruct.ResponseHeaders, headerStruct.ContentHeaders);
- byte[]? contentBytes = await HttpMessageHelper.GetContentBytesAsync(responseStream, headerStruct, requestMethod, statusLine).ConfigureAwait(false);
+ byte[]? contentBytes = await HttpMessageHelper.GetContentBytesAsync(responseStream, headerStruct, requestMethod, statusLine, cancellationToken).ConfigureAwait(false);
contentBytes = HttpMessageHelper.HandleGzipCompression(headerStruct.ContentHeaders, contentBytes);
response.Content = contentBytes is null ? null : new ByteArrayContent(contentBytes);

MODIFY
WalletWasabi/Tor/Http/Helpers/HttpMessageHelper.cs
WalletWasabi/Tor/Http/Helpers/HttpMessageHelper.cs
@@ -432,7 +432,7 @@ namespace WalletWasabi.Tor.Http.Helpers
}
var allData = new byte[(int)length];
- var num = await stream.ReadBlockAsync(allData, (int)length, ctsToken).ConfigureAwait(false);
+ int num = await stream.ReadBlockAsync(allData, (int)length, ctsToken).ConfigureAwait(false);
if (num < (int)length)
{
// https://tools.ietf.org/html/rfc7230#section-3.3.3

MODIFY
WalletWasabi/Tor/Socks5/Pool/TorHttpPool.cs
WalletWasabi/Tor/Socks5/Pool/TorHttpPool.cs
@@ -273,7 +273,7 @@ namespace WalletWasabi.Tor.Socks5.Pool
throw new TorConnectionWriteException("Could not use transport stream to write data.", e);
}
- return await HttpResponseMessageExtensions.CreateNewAsync(transportStream, request.Method).ConfigureAwait(false);
+ return await HttpResponseMessageExtensions.CreateNewAsync(transportStream, request.Method, token).ConfigureAwait(false);
}
private static string GetRequestHost(HttpRequestMessage request)
@@ -319,6 +319,7 @@ namespace WalletWasabi.Tor.Socks5.Pool
{
foreach (TorTcpConnection connection in list)
{
+ Logger.LogTrace($"Dispose connection: '{connection}'");
connection.Dispose();
}
}

Propagate cancellation token correctly and prefer OperationCanceledException over end-of-stream.