Add TorMonitor class.
MODIFY
WalletWasabi.Gui/Config.cs
WalletWasabi.Gui/Config.cs
@@ -201,7 +201,7 @@ namespace WalletWasabi.Gui
public Uri GetCurrentBackendUri()
{
- if (TorProcessManager.RequestFallbackAddressUsage)
+ if (TorMonitor.RequestFallbackAddressUsage)
{
return GetFallbackBackendUri();
}

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -65,6 +65,7 @@ namespace WalletWasabi.Gui
public Node RegTestMempoolServingNode { get; private set; }
public EndPoint? TorSocks5EndPoint { get; private set; }
private TorProcessManager? TorManager { get; set; }
+ private TorMonitor? TorMonitor { get; set; }
public CoreNode BitcoinCoreNode { get; private set; }
public HostedServices HostedServices { get; }
@@ -183,7 +184,9 @@ namespace WalletWasabi.Gui
}
var fallbackRequestTestUri = new Uri(Config.GetFallbackBackendUri(), "/api/software/versions");
- TorManager.StartMonitor(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(7), fallbackRequestTestUri);
+
+ TorMonitor = new TorMonitor(TorManager);
+ TorMonitor.StartMonitor(fallbackRequestTestUri, Config.TorSocks5EndPoint);
}
else
{
@@ -748,6 +751,12 @@ namespace WalletWasabi.Gui
}
}
+ if (TorMonitor is { } torMonitor)
+ {
+ await torMonitor.StopAsync().ConfigureAwait(false);
+ Logger.LogInfo($"{nameof(TorMonitor)} is stopped.");
+ }
+
if (TorManager is { } torManager)
{
await torManager.StopAsync(Config.TerminateTorOnExit).ConfigureAwait(false);

ADD
WalletWasabi/Tor/TorMonitor.cs
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Logging;
+using WalletWasabi.Tor.Exceptions;
+using WalletWasabi.Tor.Http;
+using WalletWasabi.Tor.Socks5.Models.Fields.OctetFields;
+
+namespace WalletWasabi.Tor
+{
+ /// <summary>
+ /// Monitors Tor process.
+ /// </summary>
+ public class TorMonitor
+ {
+ public static readonly TimeSpan TorMisbehaviorCheckPeriod = TimeSpan.FromSeconds(3);
+ public static readonly TimeSpan CheckIfRunningAfterTorMisbehavedFor = TimeSpan.FromSeconds(7);
+
+ private const long StateNotStarted = 0;
+
+ private const long StateRunning = 1;
+
+ private const long StateStopping = 2;
+
+ private const long StateStopped = 3;
+
+ /// <summary>
+ /// Value can be any of: <see cref="StateNotStarted"/>, <see cref="StateRunning"/>, <see cref="StateStopping"/> and <see cref="StateStopped"/>.
+ /// </summary>
+ private long _monitorState;
+
+ /// <summary>
+ /// Creates a new instance of the object.
+ /// </summary>
+ public TorMonitor(TorProcessManager torProcessManager)
+ {
+ _monitorState = StateNotStarted;
+ MonitorCts = new CancellationTokenSource();
+ TorProcessManager = torProcessManager;
+ }
+
+ public static bool RequestFallbackAddressUsage { get; private set; } = false;
+
+ private CancellationTokenSource MonitorCts { get; set; }
+
+ private TorProcessManager TorProcessManager { get; }
+
+ public bool IsRunning => Interlocked.Read(ref _monitorState) == StateRunning;
+
+ public void StartMonitor(Uri fallBackTestRequestUri, EndPoint torSocks5EndPoint)
+ {
+ Logger.LogInfo("Starting Tor monitor...");
+ if (Interlocked.CompareExchange(ref _monitorState, StateRunning, StateNotStarted) != StateNotStarted)
+ {
+ return;
+ }
+
+ Task.Run(async () =>
+ {
+ try
+ {
+ while (IsRunning)
+ {
+ try
+ {
+ await Task.Delay(TorMisbehaviorCheckPeriod, MonitorCts.Token).ConfigureAwait(false);
+
+ if (TorHttpClient.TorDoesntWorkSince is { }) // If Tor misbehaves.
+ {
+ TimeSpan torMisbehavedFor = DateTimeOffset.UtcNow - TorHttpClient.TorDoesntWorkSince ?? TimeSpan.Zero;
+
+ if (torMisbehavedFor > CheckIfRunningAfterTorMisbehavedFor)
+ {
+ if (TorHttpClient.LatestTorException is TorSocks5FailureResponseException torEx)
+ {
+ if (torEx.RepField == RepField.HostUnreachable)
+ {
+ Uri baseUri = new Uri($"{fallBackTestRequestUri.Scheme}://{fallBackTestRequestUri.DnsSafeHost}");
+ using (var client = new TorHttpClient(baseUri, torSocks5EndPoint))
+ {
+ var message = new HttpRequestMessage(HttpMethod.Get, fallBackTestRequestUri);
+ await client.SendAsync(message, MonitorCts.Token).ConfigureAwait(false);
+ }
+
+ // Check if it changed in the meantime...
+ if (TorHttpClient.LatestTorException is TorSocks5FailureResponseException torEx2 && torEx2.RepField == RepField.HostUnreachable)
+ {
+ // Fallback here...
+ RequestFallbackAddressUsage = true;
+ }
+ }
+ }
+ else
+ {
+ Logger.LogInfo($"Tor did not work properly for {(int)torMisbehavedFor.TotalSeconds} seconds. Maybe it crashed. Attempting to start it...");
+ await TorProcessManager.StartAsync(ensureRunning: true).ConfigureAwait(false); // Try starting Tor, if it does not work it'll be another issue.
+ }
+ }
+ }
+ }
+ catch (Exception ex) when (ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException)
+ {
+ Logger.LogTrace(ex);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex);
+ }
+ }
+ }
+ finally
+ {
+ Interlocked.CompareExchange(ref _monitorState, StateStopped, StateStopping); // If IsStopping, make it stopped.
+ }
+ });
+ }
+
+ public async Task StopAsync()
+ {
+ Interlocked.CompareExchange(ref _monitorState, StateStopping, StateRunning); // If running, make it stopping.
+
+ MonitorCts?.Cancel();
+ while (Interlocked.CompareExchange(ref _monitorState, StateStopped, StateNotStarted) == StateStopping)
+ {
+ await Task.Delay(50).ConfigureAwait(false);
+ }
+ MonitorCts?.Dispose();
+ }
+ }
+}

MODIFY
WalletWasabi/Tor/TorProcessManager.cs
WalletWasabi/Tor/TorProcessManager.cs
@@ -2,38 +2,21 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Net;
-using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Logging;
using WalletWasabi.Microservices;
-using WalletWasabi.Tor.Exceptions;
-using WalletWasabi.Tor.Http;
using WalletWasabi.Tor.Socks5;
-using WalletWasabi.Tor.Socks5.Models.Fields.OctetFields;
namespace WalletWasabi.Tor
{
/// <summary>
- /// Installs, starts and monitors Tor program.
+ /// Manages lifetime of Tor process.
/// </summary>
/// <seealso href="https://2019.www.torproject.org/docs/tor-manual.html.en"/>
public class TorProcessManager
{
- private const long StateNotStarted = 0;
-
- private const long StateRunning = 1;
-
- private const long StateStopping = 2;
-
- private const long StateStopped = 3;
-
- /// <summary>
- /// Value can be any of: <see cref="StateNotStarted"/>, <see cref="StateRunning"/>, <see cref="StateStopping"/> and <see cref="StateStopped"/>.
- /// </summary>
- private long _monitorState;
-
/// <summary>
/// Creates a new instance of the object.
/// </summary>
@@ -42,8 +25,6 @@ namespace WalletWasabi.Tor
public TorProcessManager(TorSettings settings, EndPoint torSocks5EndPoint)
{
TorSocks5EndPoint = torSocks5EndPoint;
- _monitorState = StateNotStarted;
- Stop = new CancellationTokenSource();
TorProcess = null;
Settings = settings;
TorSocks5Client = new TorSocks5Client(torSocks5EndPoint);
@@ -53,18 +34,12 @@ namespace WalletWasabi.Tor
private EndPoint TorSocks5EndPoint { get; }
- public static bool RequestFallbackAddressUsage { get; private set; } = false;
-
private ProcessAsync? TorProcess { get; set; }
private TorSettings Settings { get; }
private TorSocks5Client TorSocks5Client { get; }
- public bool IsRunning => Interlocked.Read(ref _monitorState) == StateRunning;
-
- private CancellationTokenSource Stop { get; set; }
-
/// <summary>
/// Installs Tor if it is not installed, then it starts Tor.
/// </summary>
@@ -154,93 +129,8 @@ namespace WalletWasabi.Tor
return false;
}
- #region Monitor
-
- public void StartMonitor(TimeSpan torMisbehaviorCheckPeriod, TimeSpan checkIfRunningAfterTorMisbehavedFor, Uri fallBackTestRequestUri)
- {
- Logger.LogInfo("Starting Tor monitor...");
- if (Interlocked.CompareExchange(ref _monitorState, StateRunning, StateNotStarted) != StateNotStarted)
- {
- return;
- }
-
- Task.Run(async () =>
- {
- try
- {
- while (IsRunning)
- {
- try
- {
- await Task.Delay(torMisbehaviorCheckPeriod, Stop.Token).ConfigureAwait(false);
-
- if (TorHttpClient.TorDoesntWorkSince is { }) // If Tor misbehaves.
- {
- TimeSpan torMisbehavedFor = DateTimeOffset.UtcNow - TorHttpClient.TorDoesntWorkSince ?? TimeSpan.Zero;
-
- if (torMisbehavedFor > checkIfRunningAfterTorMisbehavedFor)
- {
- if (TorHttpClient.LatestTorException is TorSocks5FailureResponseException torEx)
- {
- if (torEx.RepField == RepField.HostUnreachable)
- {
- Uri baseUri = new Uri($"{fallBackTestRequestUri.Scheme}://{fallBackTestRequestUri.DnsSafeHost}");
- using (var client = new TorHttpClient(baseUri, TorSocks5EndPoint))
- {
- var message = new HttpRequestMessage(HttpMethod.Get, fallBackTestRequestUri);
- await client.SendAsync(message, Stop.Token).ConfigureAwait(false);
- }
-
- // Check if it changed in the meantime...
- if (TorHttpClient.LatestTorException is TorSocks5FailureResponseException torEx2 && torEx2.RepField == RepField.HostUnreachable)
- {
- // Fallback here...
- RequestFallbackAddressUsage = true;
- }
- }
- }
- else
- {
- Logger.LogInfo($"Tor did not work properly for {(int)torMisbehavedFor.TotalSeconds} seconds. Maybe it crashed. Attempting to start it...");
- await StartAsync(ensureRunning: true).ConfigureAwait(false); // Try starting Tor, if it does not work it'll be another issue.
- }
- }
- }
- }
- catch (Exception ex) when (ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException)
- {
- Logger.LogTrace(ex);
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex);
- }
- }
- }
- finally
- {
- Interlocked.CompareExchange(ref _monitorState, StateStopped, StateStopping); // If IsStopping, make it stopped.
- }
- });
- }
-
public async Task StopAsync(bool killTor = false)
{
- Interlocked.CompareExchange(ref _monitorState, StateStopping, StateRunning); // If running, make it stopping.
-
- if (TorSocks5EndPoint is null)
- {
- Interlocked.Exchange(ref _monitorState, StateStopped);
- }
-
- Stop?.Cancel();
- while (Interlocked.CompareExchange(ref _monitorState, StateStopped, StateNotStarted) == StateStopping)
- {
- await Task.Delay(50).ConfigureAwait(false);
- }
- Stop?.Dispose();
- Stop = null;
-
if (TorProcess is { } && killTor)
{
Logger.LogInfo($"Killing Tor process.");
@@ -260,9 +150,6 @@ namespace WalletWasabi.Tor
}
TorProcess?.Dispose();
- TorProcess = null;
}
-
- #endregion Monitor
}
}

Add TorMonitor class.