Start working on a task that monitors the Tor process
MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -135,6 +135,7 @@ namespace WalletWasabi.Gui
TorManager = new TorProcessManager(Config.GetTorSocks5EndPoint(), TorLogsFile);
TorManager.Start(false, DataDir);
+ TorManager.StartMonitor(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3), TimeSpan.FromMinutes(3), DataDir);
Logger.LogInfo<TorProcessManager>($"{nameof(TorProcessManager)} is initialized.");
@@ -329,6 +330,9 @@ namespace WalletWasabi.Gui
RegTestMemPoolServingNode.Disconnect();
Logger.LogInfo($"{nameof(RegTestMemPoolServingNode)} is disposed.", nameof(Global));
}
+
+ TorManager?.Dispose();
+ Logger.LogInfo($"{nameof(TorManager)} is stopped.", nameof(Global));
}
catch (Exception ex)
{

MODIFY
WalletWasabi/Http/Models/StatusLine.cs
WalletWasabi/Http/Models/StatusLine.cs
@@ -45,7 +45,7 @@ namespace WalletWasabi.Http.Models
}
catch (Exception ex)
{
- Logger.LogDebug<StatusLine>(ex);
+ Logger.LogTrace<StatusLine>(ex); // Often happens when internet connection is lost.
throw new NotSupportedException($"Invalid {nameof(StatusLine)}: {statusLineString}.", ex);
}
}

MODIFY
WalletWasabi/TorSocks5/TorHttpClient.cs
WalletWasabi/TorSocks5/TorHttpClient.cs
@@ -20,6 +20,8 @@ namespace WalletWasabi.TorSocks5
{
public class TorHttpClient : IDisposable
{
+ public static DateTimeOffset? TorDoesntWorkSince { get; private set; } = null; // This sets the first time the request failed with exception.
+
public Uri DestinationUri { get; }
public IPEndPoint TorSocks5EndPoint { get; }
@@ -64,7 +66,9 @@ namespace WalletWasabi.TorSocks5
{
try
{
- return await SendAsync(request);
+ HttpResponseMessage ret = await SendAsync(request);
+ TorDoesntWorkSince = null;
+ return ret;
}
catch (Exception ex)
{
@@ -76,7 +80,9 @@ namespace WalletWasabi.TorSocks5
cancel.ThrowIfCancellationRequested();
try
{
- return await SendAsync(request);
+ HttpResponseMessage ret2 = await SendAsync(request);
+ TorDoesntWorkSince = null;
+ return ret2;
}
// If we get ttlexpired then wait and retry again linux often do this.
catch (TorSocks5FailureResponseException ex2) when (ex2.RepField == RepField.TtlExpired)
@@ -97,14 +103,29 @@ namespace WalletWasabi.TorSocks5
}
cancel.ThrowIfCancellationRequested();
- return await SendAsync(request);
+
+ HttpResponseMessage ret3 = await SendAsync(request);
+ TorDoesntWorkSince = null;
+ return ret3;
}
}
}
catch (TaskCanceledException ex)
{
+ if (TorDoesntWorkSince == null)
+ {
+ TorDoesntWorkSince = DateTimeOffset.UtcNow;
+ }
throw new OperationCanceledException(ex.Message, ex, cancel);
}
+ catch
+ {
+ if (TorDoesntWorkSince == null)
+ {
+ TorDoesntWorkSince = DateTimeOffset.UtcNow;
+ }
+ throw;
+ }
}
/// <remarks>

MODIFY
WalletWasabi/TorSocks5/TorProcessManager.cs
WalletWasabi/TorSocks5/TorProcessManager.cs
@@ -11,17 +11,22 @@ using WalletWasabi.Logging;
namespace WalletWasabi.TorSocks5
{
- public class TorProcessManager
+ public class TorProcessManager : IDisposable
{
public IPEndPoint TorSocks5EndPoint { get; }
public string LogFile { get; }
+ public Process TorProcess { get; private set; }
+
/// <param name="torSocks5EndPoint">Opt out Tor with null.</param>
/// <param name="logFile">Opt out of logging with null.</param>
public TorProcessManager(IPEndPoint torSocks5EndPoint, string logFile)
{
TorSocks5EndPoint = torSocks5EndPoint ?? new IPEndPoint(IPAddress.Loopback, 9050);
LogFile = logFile;
+ _running = 0;
+ Stop = new CancellationTokenSource();
+ TorProcess = null;
}
public void Start(bool ensureRunning, string dataDir)
@@ -122,7 +127,7 @@ namespace WalletWasabi.TorSocks5
CreateNoWindow = true,
RedirectStandardOutput = true
};
- Process.Start(torProcessStartInfo);
+ TorProcess = Process.Start(torProcessStartInfo);
Logger.LogInfo<TorProcessManager>($"Starting Tor process with Process.Start.");
}
else // Linux and OSX
@@ -188,5 +193,140 @@ namespace WalletWasabi.TorSocks5
return true;
}
}
+
+ #region Monitor
+
+ /// <summary>
+ /// 0: Not started, 1: Running, 2: Stopping, 3: Stopped
+ /// </summary>
+ private long _running;
+
+ public bool IsRunning => Interlocked.Read(ref _running) == 1;
+ public bool IsStopping => Interlocked.Read(ref _running) == 2;
+
+ private CancellationTokenSource Stop { get; }
+
+ public void StartMonitor(TimeSpan torMisbehaviorCheckPeriod, TimeSpan checkIfRunningAfterTorMisbehavedFor, TimeSpan tryRestartAfterTorMisbehavedFor, string dataDirToStartWith)
+ {
+ Logger.LogInfo<TorProcessManager>("Starting Tor monitor...");
+ Interlocked.Exchange(ref _running, 1);
+
+ Task.Run(async () =>
+ {
+ try
+ {
+ while (IsRunning)
+ {
+ try
+ {
+ // If stop was requested return.
+ if (IsRunning == false) return;
+
+ await Task.Delay(torMisbehaviorCheckPeriod, Stop.Token).ConfigureAwait(false);
+
+ if (!(TorHttpClient.TorDoesntWorkSince is null)) // If Tor misbehaves.
+ {
+ TimeSpan torMisbehavedFor = (DateTimeOffset.UtcNow - TorHttpClient.TorDoesntWorkSince) ?? TimeSpan.Zero;
+
+ if (torMisbehavedFor > tryRestartAfterTorMisbehavedFor)
+ {
+ Logger.LogInfo<TorProcessManager>($"Tor didn't work properly for {(int)torMisbehavedFor.TotalSeconds} seconds. Attempting to restart...");
+ Logger.LogInfo<TorProcessManager>($"Attempting to kill Tor...");
+ // Try killing, then starting Tor.
+ if (TorProcess != null)
+ {
+ new Thread(delegate () // Don't ask. This is the only way it worked on Win10
+ {
+ TorProcess?.Kill();
+ }).Start();
+ }
+ else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ try
+ {
+ string runTorCmd = $"killall tor";
+ EnvironmentHelpers.ShellExec(runTorCmd, false); // Not always have permission and stuff.
+ }
+ catch (Exception ex)
+ {
+ Logger.LogInfo<TorProcessManager>($"Couldn't kill tor...");
+ Logger.LogDebug<TorProcessManager>(ex);
+ }
+ }
+ else
+ {
+ Logger.LogInfo<TorProcessManager>($"Couldn't kill Tor.");
+ }
+ Logger.LogInfo<TorProcessManager>($"Attempting to start Tor...");
+
+ // Give some time before trying to restart.
+ await Task.Delay(3000, Stop.Token).ConfigureAwait(false);
+ Start(true, dataDirToStartWith);
+ await Task.Delay(7000, Stop.Token).ConfigureAwait(false);
+ }
+ else if (torMisbehavedFor > checkIfRunningAfterTorMisbehavedFor)
+ {
+ Logger.LogInfo<TorProcessManager>($"Tor didn't work properly for {(int)torMisbehavedFor.TotalSeconds} seconds. Maybe it crashed. Attempting to start it...");
+ Start(true, dataDirToStartWith); // Try starting Tor, if doesn't work it'll be another issue.
+ await Task.Delay(7000, Stop.Token).ConfigureAwait(false);
+ }
+ }
+ }
+ catch (TaskCanceledException ex)
+ {
+ Logger.LogTrace<TorProcessManager>(ex);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug<TorProcessManager>(ex);
+ }
+ }
+ }
+ finally
+ {
+ if (IsStopping)
+ {
+ Interlocked.Exchange(ref _running, 3);
+ }
+ }
+ });
+ }
+
+ #region IDisposable Support
+
+ private volatile bool _disposedValue = false; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ if (IsRunning)
+ {
+ Interlocked.Exchange(ref _running, 2);
+ }
+ Stop?.Cancel();
+ while (IsStopping)
+ {
+ Task.Delay(50).GetAwaiter().GetResult(); // DO NOT MAKE IT ASYNC (.NET Core threading brainfart)
+ }
+ Stop?.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ }
+
+ #endregion IDisposable Support
+
+ #endregion Monitor
}
}

Start working on a task that monitors the Tor process