Add TerminateService
MODIFY
WalletWasabi.Gui/CommandLine/Daemon.cs
WalletWasabi.Gui/CommandLine/Daemon.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Logging;
+using WalletWasabi.Services.Terminate;
using WalletWasabi.Userfacing;
using WalletWasabi.Wallets;
@@ -11,13 +12,14 @@ namespace WalletWasabi.Gui.CommandLine
{
public class Daemon
{
- public Daemon(Global global)
+ public Daemon(Global global, TerminateService terminateService)
{
Global = global;
+ TerminateService = terminateService;
}
private Global Global { get; }
-
+ public TerminateService TerminateService { get; }
private Wallet Wallet { get; set; }
internal async Task RunAsync(string walletName, string destinationWalletName, bool keepMixAlive)
@@ -68,7 +70,7 @@ namespace WalletWasabi.Gui.CommandLine
Logger.LogInfo("Correct password.");
- await Global.InitializeNoWalletAsync();
+ await Global.InitializeNoWalletAsync(TerminateService);
if (Global.KillRequested)
{
return;
@@ -133,7 +135,6 @@ namespace WalletWasabi.Gui.CommandLine
}
finally
{
- await Global.DisposeAsync().ConfigureAwait(false);
Logger.LogInfo($"{nameof(Daemon)} stopped.");
}
}

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -35,6 +35,7 @@ using WalletWasabi.Helpers;
using WalletWasabi.Legal;
using WalletWasabi.Logging;
using WalletWasabi.Services;
+using WalletWasabi.Services.Terminate;
using WalletWasabi.Stores;
using WalletWasabi.Tor;
using WalletWasabi.Userfacing;
@@ -69,7 +70,7 @@ namespace WalletWasabi.Gui
public HostedServices HostedServices { get; }
- public bool KillRequested => Interlocked.Read(ref _dispose) > 0;
+ public bool KillRequested => false;
public UiConfig UiConfig { get; }
@@ -128,7 +129,7 @@ namespace WalletWasabi.Gui
private SingleInstanceChecker SingleInstanceChecker { get; }
- public async Task InitializeNoWalletAsync()
+ public async Task InitializeNoWalletAsync(TerminateService terminateService)
{
InitializationStarted = true;
AddressManager = null;
@@ -156,18 +157,6 @@ namespace WalletWasabi.Gui
HostedServices.Register(new SystemAwakeChecker(WalletManager), "System Awake Checker");
- #region ProcessKillSubscription
-
- AppDomain.CurrentDomain.ProcessExit += async (s, e) => await DisposeAsync().ConfigureAwait(false);
- Console.CancelKeyPress += async (s, e) =>
- {
- e.Cancel = true;
- Logger.LogWarning("Process was signaled for killing.", nameof(Global));
- await DisposeAsync().ConfigureAwait(false);
- };
-
- #endregion ProcessKillSubscription
-
cancel.ThrowIfCancellationRequested();
#region TorProcessInitialization
@@ -342,7 +331,7 @@ namespace WalletWasabi.Gui
var jsonRpcServerConfig = new JsonRpcServerConfiguration(Config);
if (jsonRpcServerConfig.IsEnabled)
{
- RpcServer = new JsonRpcServer(this, jsonRpcServerConfig);
+ RpcServer = new JsonRpcServer(this, jsonRpcServerConfig, terminateService);
try
{
await RpcServer.StartAsync(cancel).ConfigureAwait(false);
@@ -613,28 +602,8 @@ namespace WalletWasabi.Gui
Logger.LogInfo($"Transaction Notification ({notificationType}): {title} - {message} - {e.Transaction.GetHash()}");
}
- /// <summary>
- /// 0: nobody called
- /// 1: somebody called
- /// 2: call finished
- /// </summary>
- private long _dispose = 0; // To detect redundant calls
-
public async Task DisposeAsync()
{
- var compareRes = Interlocked.CompareExchange(ref _dispose, 1, 0);
- if (compareRes == 1)
- {
- while (Interlocked.Read(ref _dispose) != 2)
- {
- await Task.Delay(50).ConfigureAwait(false);
- }
- return;
- }
- else if (compareRes == 2)
- {
- return;
- }
Logger.LogWarning("Process is exiting.", nameof(Global));
try
@@ -786,8 +755,6 @@ namespace WalletWasabi.Gui
finally
{
StoppingCts?.Dispose();
- Interlocked.Exchange(ref _dispose, 2);
- Logger.LogSoftwareStopped("Wasabi");
}
}
}

MODIFY
WalletWasabi.Gui/MainWindow.xaml.cs
WalletWasabi.Gui/MainWindow.xaml.cs
@@ -121,8 +121,6 @@ namespace WalletWasabi.Gui
wm.OnClose();
Logger.LogInfo($"{nameof(WalletManagerViewModel)} closed.");
}
-
- await Global.DisposeAsync();
}
catch (Exception ex)
{

MODIFY
WalletWasabi.Gui/Program.cs
WalletWasabi.Gui/Program.cs
@@ -17,6 +17,7 @@ using WalletWasabi.Gui.CrashReport;
using WalletWasabi.Gui.ViewModels;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
+using WalletWasabi.Services.Terminate;
using WalletWasabi.Wallets;
// This is temporary and to facilitate the migration to new UI.
@@ -32,6 +33,8 @@ namespace WalletWasabi.Gui
// This is only needed to pass CrashReporter to AppMainAsync otherwise it could be a local variable in Main().
private static CrashReporter CrashReporter;
+ private static TerminateService TerminateService;
+
/// Warning! In Avalonia applications Main must not be async. Otherwise application may not run on OSX.
/// see https://github.com/AvaloniaUI/Avalonia/wiki/Unresolved-platform-support-issues
private static void Main(string[] args)
@@ -41,6 +44,7 @@ namespace WalletWasabi.Gui
try
{
+ TerminateService = new TerminateService(TerminateApplication);
CrashReporter = new CrashReporter();
Global = CreateGlobal();
Locator.CurrentMutable.RegisterConstant(Global);
@@ -77,7 +81,7 @@ namespace WalletWasabi.Gui
}
}
- TerminateApplicationAsync(appException).GetAwaiter().GetResult();
+ TerminateService.Terminate(appException);
}
private static Global CreateGlobal()
@@ -98,7 +102,7 @@ namespace WalletWasabi.Gui
private static bool ProcessCliCommands(string[] args)
{
- var daemon = new Daemon(Global);
+ var daemon = new Daemon(Global, TerminateService);
var interpreter = new CommandInterpreter(Console.Out, Console.Error);
var executionTask = interpreter.ExecuteCommandsAsync(
args,
@@ -118,7 +122,7 @@ namespace WalletWasabi.Gui
var statusBarViewModel = new StatusBarViewModel(Global.DataDir, Global.Network, Global.Config, Global.HostedServices, Global.BitcoinStore.SmartHeaderChain, Global.Synchronizer, Global.LegalDocuments);
MainWindowViewModel.Instance = new MainWindowViewModel(Global.Network, Global.UiConfig, Global.WalletManager, statusBarViewModel, IoC.Get<IShell>());
- await Global.InitializeNoWalletAsync();
+ await Global.InitializeNoWalletAsync(TerminateService);
MainWindowViewModel.Instance.Initialize(Global.Nodes.ConnectedNodes);
@@ -129,11 +133,11 @@ namespace WalletWasabi.Gui
var criticalException = ex is OperationCanceledException ? null : ex;
// There is no other way to stop the creation of the WasabiWindow.
- await TerminateApplicationAsync(criticalException);
+ TerminateService.Terminate(criticalException);
}
}
- private static async Task TerminateApplicationAsync(Exception? criticalException = null)
+ private static void TerminateApplication(Exception? criticalException = null)
{
if (criticalException is { })
{
@@ -155,7 +159,7 @@ namespace WalletWasabi.Gui
if (Global is { } global)
{
- await global.DisposeAsync().ConfigureAwait(false);
+ global.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
}
AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
@@ -166,7 +170,9 @@ namespace WalletWasabi.Gui
Logger.LogSoftwareStopped("Wasabi GUI");
}
- Environment.Exit(criticalException is { } ? 1 : 0);
+ Logger.LogSoftwareStopped("Wasabi");
+
+ TerminateService?.Dispose();
}
private static void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)

MODIFY
WalletWasabi.Gui/Rpc/JsonRpcServer.cs
WalletWasabi.Gui/Rpc/JsonRpcServer.cs
@@ -7,12 +7,13 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using WalletWasabi.Logging;
using WalletWasabi.Gui.Models;
+using WalletWasabi.Services.Terminate;
namespace WalletWasabi.Gui.Rpc
{
public class JsonRpcServer : BackgroundService
{
- public JsonRpcServer(Global global, JsonRpcServerConfiguration config)
+ public JsonRpcServer(Global global, JsonRpcServerConfiguration config, TerminateService terminateService)
{
Config = config;
Listener = new HttpListener();
@@ -21,7 +22,7 @@ namespace WalletWasabi.Gui.Rpc
{
Listener.Prefixes.Add(prefix);
}
- Service = new WasabiJsonRpcService(global);
+ Service = new WasabiJsonRpcService(global, terminateService);
}
private HttpListener Listener { get; }

MODIFY
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
@@ -9,18 +9,21 @@ using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.CoinJoin.Client.Clients.Queuing;
using WalletWasabi.Helpers;
using WalletWasabi.Models;
+using WalletWasabi.Services.Terminate;
using WalletWasabi.Wallets;
namespace WalletWasabi.Gui.Rpc
{
public partial class WasabiJsonRpcService
{
- public WasabiJsonRpcService(Global global)
+ public WasabiJsonRpcService(Global global, TerminateService terminateService)
{
Global = global;
+ TerminateService = terminateService;
}
private Global Global { get; }
+ public TerminateService TerminateService { get; }
private Wallet ActiveWallet { get; set; }
[JsonRpcMethod("listunspentcoins")]
@@ -243,9 +246,9 @@ namespace WalletWasabi.Gui.Rpc
}
[JsonRpcMethod("stop")]
- public async Task StopAsync()
+ public void Stop()
{
- await Global.DisposeAsync().ConfigureAwait(false);
+ TerminateService.Terminate();
}
private void AssertWalletIsLoaded()

ADD
WalletWasabi/Services/Terminate/TerminateService.cs
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Logging;
+
+namespace WalletWasabi.Services.Terminate
+{
+ public class TerminateService : IDisposable
+ {
+ private bool _disposedValue;
+ private Action<Exception?> _terminateApplication;
+
+ public TerminateService(Action<Exception?> terminateApplication)
+ {
+ _terminateApplication = terminateApplication;
+ AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
+ Console.CancelKeyPress += Console_CancelKeyPress;
+ }
+
+ private void CurrentDomain_ProcessExit(object? sender, EventArgs e)
+ {
+ Logger.LogDebug("ProcessExit was called.");
+
+ // This must be a blocking call because after this the OS will terminate Wasabi process if exists.
+ Terminate();
+ }
+
+ private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
+ {
+ Logger.LogWarning("Process was signaled for termination.");
+ // This must be a blocking call because after this the OS will terminate Wasabi process if exists.
+ // In some cases CurrentDomain_ProcessExit is called after this by the OS.
+ Terminate();
+ }
+
+ private const long TerminateStatusIdle = 0;
+ private const long TerminateStatusInProgress = 1;
+ private const long TerminateFinished = 2;
+
+ private long _terminateStatus;
+
+ /// <summary>
+ /// This will terminate the application.
+ /// </summary>
+ /// <param name="criticalException"></param>
+ public void Terminate(Exception? criticalException = null)
+ {
+ var prevValue = Interlocked.CompareExchange(ref _terminateStatus, TerminateStatusInProgress, TerminateStatusIdle);
+ if (prevValue != TerminateStatusIdle)
+ {
+ while (_terminateStatus != TerminateFinished)
+ {
+ }
+ return;
+ }
+ Logger.LogDebug("Terminate application was started.");
+ _terminateApplication.Invoke(criticalException);
+
+ Interlocked.Exchange(ref _terminateStatus, TerminateFinished);
+
+ Environment.Exit(criticalException is { } ? 1 : 0);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_ProcessExit;
+ Console.CancelKeyPress -= Console_CancelKeyPress;
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}

Add TerminateService