From 42b9c1e8fede88880454154f8c3683f1f8424ed9 Mon Sep 17 00:00:00 2001
From: jhorv <38920027+jhorv@users.noreply.github.com>
Date: Fri, 26 May 2023 17:57:43 -0400
Subject: [PATCH] Ryujinx.Ava: fixes for random hangs on exit (#4827)

* Attempt at fixing hang on exit by ending the WindowNotificationManager notification loop, so that the Thread running it can exit.

* explicitly apply the NotificationManager template to allow the notification loop to begin

* NotificationHelper - remove explicity call to ApplyTemplate(). Change to ManualResetEventSlim so we can cancel the Wait on it.

* add a timeout to AudioRenderSystem.Stop()'s waiting for the termination signal, log a warning if this timeout occurs, and continue execution

* NotifiationHelper - cancel first, the CompleteAdding()

* Remove AudioRenderSystem._terminationEvent, redundant

* NotificationHelper - use host.Closing event to trigger cancellation instead of _notifationManager.DetachedFromLogicalTree

* Change NotificationHelper to use an explicit Thread for background work.  Wait on the cancellationToken's WaitHandle so the Thread doesn't have to deal with async. Wrap foreach in try/catch (OperationCanceledException) to swallow the escaping exception from the GetConsumingEnumerable().

* adjust formatting of AsyncWorkQueue constructor to use object initializers consistently

* use AsyncWorkQueue to do everything I added in SetNotificationManager()

* Revert "use AsyncWorkQueue to do everything I added in SetNotificationManager()"

This reverts commit f0e78366b8776ec8e2fef8ab023c0db1833155d3.

* use AsyncWorkQueue to handle the Thread-related changes previously made to NotificationHelper.SetNotificationHelper(). Wrap it in Lazy<T> and force instantiation in the TemplateApplied event handler to accomodate for the fact that AsyncWorkQueue starts immediately, and the notification dispatch loop was being delayed by _templateAppliedEvent.

* impl changes suggested by AcK77

* impl changes suggested by AcK77 (more)
---
 .../Renderer/Server/AudioRenderSystem.cs      | 14 --------
 .../UI/Helpers/NotificationHelper.cs          | 35 +++++++++++--------
 src/Ryujinx.Common/AsyncWorkQueue.cs          |  8 +++--
 3 files changed, 25 insertions(+), 32 deletions(-)

diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 53570243d8..1ad7b8590c 100644
--- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -31,7 +31,6 @@ namespace Ryujinx.Audio.Renderer.Server
         private AudioRendererRenderingDevice _renderingDevice;
         private AudioRendererExecutionMode _executionMode;
         private IWritableEvent _systemEvent;
-        private ManualResetEvent _terminationEvent;
         private MemoryPoolState _dspMemoryPoolState;
         private VoiceContext _voiceContext;
         private MixContext _mixContext;
@@ -83,7 +82,6 @@ namespace Ryujinx.Audio.Renderer.Server
         public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
         {
             _manager = manager;
-            _terminationEvent = new ManualResetEvent(false);
             _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
             _voiceContext = new VoiceContext();
             _mixContext = new MixContext();
@@ -387,11 +385,6 @@ namespace Ryujinx.Audio.Renderer.Server
                 _isActive = false;
             }
 
-            if (_executionMode == AudioRendererExecutionMode.Auto)
-            {
-                _terminationEvent.WaitOne();
-            }
-
             Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
         }
 
@@ -668,8 +661,6 @@ namespace Ryujinx.Audio.Renderer.Server
             {
                 if (_isActive)
                 {
-                    _terminationEvent.Reset();
-
                     if (!_manager.Processor.HasRemainingCommands(_sessionId))
                     {
                         GenerateCommandList(out CommandList commands);
@@ -686,10 +677,6 @@ namespace Ryujinx.Audio.Renderer.Server
                         _isDspRunningBehind = true;
                     }
                 }
-                else
-                {
-                    _terminationEvent.Set();
-                }
             }
         }
 
@@ -857,7 +844,6 @@ namespace Ryujinx.Audio.Renderer.Server
                 }
 
                 _manager.Unregister(this);
-                _terminationEvent.Dispose();
                 _workBufferMemoryPin.Dispose();
 
                 if (MemoryManager is IRefCounted rc)
diff --git a/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs b/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
index 7e2afb8bd9..f207c5fb02 100644
--- a/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
@@ -3,21 +3,20 @@ using Avalonia.Controls;
 using Avalonia.Controls.Notifications;
 using Avalonia.Threading;
 using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Common;
 using System;
 using System.Collections.Concurrent;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace Ryujinx.Ava.UI.Helpers
 {
     public static class NotificationHelper
     {
-        private const int MaxNotifications      = 4; 
+        private const int MaxNotifications      = 4;
         private const int NotificationDelayInMs = 5000;
 
         private static WindowNotificationManager _notificationManager;
 
-        private static readonly ManualResetEvent                 _templateAppliedEvent = new(false);
         private static readonly BlockingCollection<Notification> _notifications        = new();
 
         public static void SetNotificationManager(Window host)
@@ -29,25 +28,31 @@ namespace Ryujinx.Ava.UI.Helpers
                 Margin   = new Thickness(0, 0, 15, 40)
             };
 
+            var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>(
+                () => new AsyncWorkQueue<Notification>(notification =>
+                    {
+                        Dispatcher.UIThread.Post(() =>
+                        {
+                            _notificationManager.Show(notification);
+                        });
+                    },
+                    "UI.NotificationThread",
+                    _notifications),
+                LazyThreadSafetyMode.ExecutionAndPublication);
+
             _notificationManager.TemplateApplied += (sender, args) =>
             {
-                _templateAppliedEvent.Set();
+                // NOTE: Force creation of the AsyncWorkQueue.
+                _ = maybeAsyncWorkQueue.Value;
             };
 
-            Task.Run(async () =>
+            host.Closing += (sender, args) =>
             {
-                _templateAppliedEvent.WaitOne();
-
-                foreach (var notification in _notifications.GetConsumingEnumerable())
+                if (maybeAsyncWorkQueue.IsValueCreated)
                 {
-                    Dispatcher.UIThread.Post(() =>
-                    {
-                        _notificationManager.Show(notification);
-                    });
-
-                    await Task.Delay(NotificationDelayInMs / MaxNotifications);
+                    maybeAsyncWorkQueue.Value.Dispose();
                 }
-            });
+            };
         }
 
         public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)
diff --git a/src/Ryujinx.Common/AsyncWorkQueue.cs b/src/Ryujinx.Common/AsyncWorkQueue.cs
index 80f8dcfe4c..746ef4cace 100644
--- a/src/Ryujinx.Common/AsyncWorkQueue.cs
+++ b/src/Ryujinx.Common/AsyncWorkQueue.cs
@@ -22,9 +22,11 @@ namespace Ryujinx.Common
             _cts = new CancellationTokenSource();
             _queue = collection;
             _workerAction = callback;
-            _workerThread = new Thread(DoWork) { Name = name };
-
-            _workerThread.IsBackground = true;
+            _workerThread = new Thread(DoWork)
+            {
+                Name = name,
+                IsBackground = true
+            };
             _workerThread.Start();
         }