From a27986c31167d8ce60efcee7e901da241f63ed08 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Wed, 4 Aug 2021 15:28:33 -0300
Subject: [PATCH] Make audio disposal thread safe on all 3 backends (#2527)

* Make audio disposal thread safe on all 3 backends

* Make OpenAL more consistent with the other backends

* Remove Window.Cursor = null, and change dummy TValue to byte
---
 .../OpenALHardwareDeviceDriver.cs             | 58 +++++--------------
 .../OpenALHardwareDeviceSession.cs            |  4 +-
 .../SDL2HardwareDeviceDriver.cs               | 33 ++++-------
 .../SDL2HardwareDeviceSession.cs              |  4 +-
 .../SoundIoHardwareDeviceDriver.cs            | 50 +++++-----------
 .../SoundIoHardwareDeviceSession.cs           |  4 +-
 Ryujinx/Ui/RendererWidgetBase.cs              |  7 +--
 7 files changed, 45 insertions(+), 115 deletions(-)

diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
index 721e96c6..60c364da 100644
--- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
@@ -3,7 +3,7 @@ using Ryujinx.Audio.Common;
 using Ryujinx.Audio.Integration;
 using Ryujinx.Memory;
 using System;
-using System.Collections.Generic;
+using System.Collections.Concurrent;
 using System.Linq;
 using System.Threading;
 using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
@@ -12,11 +12,10 @@ namespace Ryujinx.Audio.Backends.OpenAL
 {
     public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
     {
-        private object _lock = new object();
-        private ALDevice _device;
-        private ALContext _context;
-        private ManualResetEvent _updateRequiredEvent;
-        private List<OpenALHardwareDeviceSession> _sessions;
+        private readonly ALDevice _device;
+        private readonly ALContext _context;
+        private readonly ManualResetEvent _updateRequiredEvent;
+        private readonly ConcurrentDictionary<OpenALHardwareDeviceSession, byte> _sessions;
         private bool _stillRunning;
         private Thread _updaterThread;
 
@@ -25,7 +24,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
             _device = ALC.OpenDevice("");
             _context = ALC.CreateContext(_device, new ALContextAttributes());
             _updateRequiredEvent = new ManualResetEvent(false);
-            _sessions = new List<OpenALHardwareDeviceSession>();
+            _sessions = new ConcurrentDictionary<OpenALHardwareDeviceSession, byte>();
 
             _stillRunning = true;
             _updaterThread = new Thread(Update)
@@ -72,22 +71,16 @@ namespace Ryujinx.Audio.Backends.OpenAL
                 throw new ArgumentException($"{channelCount}");
             }
 
-            lock (_lock)
-            {
-                OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
+            OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
 
-                _sessions.Add(session);
+            _sessions.TryAdd(session, 0);
 
-                return session;
-            }
+            return session;
         }
 
-        internal void Unregister(OpenALHardwareDeviceSession session)
+        internal bool Unregister(OpenALHardwareDeviceSession session)
         {
-            lock (_lock)
-            {
-                _sessions.Remove(session);
-            }
+            return _sessions.TryRemove(session, out _);
         }
 
         public ManualResetEvent GetUpdateRequiredEvent()
@@ -103,14 +96,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
             {
                 bool updateRequired = false;
 
-                lock (_lock)
+                foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
                 {
-                    foreach (OpenALHardwareDeviceSession session in _sessions)
+                    if (session.Update())
                     {
-                        if (session.Update())
-                        {
-                            updateRequired = true;
-                        }
+                        updateRequired = true;
                     }
                 }
 
@@ -135,26 +125,10 @@ namespace Ryujinx.Audio.Backends.OpenAL
             {
                 _stillRunning = false;
 
-                int sessionCount = 0;
-
-                // NOTE: This is done in a way to avoid possible situations when the OpenALHardwareDeviceSession is already being dispose in another thread but doesn't hold the lock and tries to Unregister.
-                do
+                foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
                 {
-                    lock (_lock)
-                    {
-                        if (_sessions.Count == 0)
-                        {
-                            break;
-                        }
-
-                        OpenALHardwareDeviceSession session = _sessions[_sessions.Count - 1];
-
-                        session.Dispose();
-
-                        sessionCount = _sessions.Count;
-                    }
+                    session.Dispose();
                 }
-                while (sessionCount > 0);
 
                 ALC.DestroyContext(_context);
                 ALC.CloseDevice(_device);
diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
index f0227bf8..f0c0f498 100644
--- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
+++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
@@ -190,7 +190,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
 
         protected virtual void Dispose(bool disposing)
         {
-            if (disposing)
+            if (disposing && _driver.Unregister(this))
             {
                 lock (_lock)
                 {
@@ -198,8 +198,6 @@ namespace Ryujinx.Audio.Backends.OpenAL
                     Stop();
 
                     AL.DeleteSource(_sourceId);
-
-                    _driver.Unregister(this);
                 }
             }
         }
diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
index 07131d1d..2c1baa47 100644
--- a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
@@ -1,10 +1,9 @@
-using Ryujinx.Audio.Backends.Common;
-using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Common;
 using Ryujinx.Audio.Integration;
 using Ryujinx.Memory;
 using Ryujinx.SDL2.Common;
 using System;
-using System.Collections.Generic;
+using System.Collections.Concurrent;
 using System.Runtime.InteropServices;
 using System.Threading;
 
@@ -15,15 +14,13 @@ namespace Ryujinx.Audio.Backends.SDL2
 {
     public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
     {
-        private object _lock = new object();
-
-        private ManualResetEvent _updateRequiredEvent;
-        private List<SDL2HardwareDeviceSession> _sessions;
+        private readonly ManualResetEvent _updateRequiredEvent;
+        private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions;
 
         public SDL2HardwareDeviceDriver()
         {
             _updateRequiredEvent = new ManualResetEvent(false);
-            _sessions = new List<SDL2HardwareDeviceSession>();
+            _sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>();
 
             SDL2Driver.Instance.Initialize();
         }
@@ -64,22 +61,16 @@ namespace Ryujinx.Audio.Backends.SDL2
                 throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
             }
 
-            lock (_lock)
-            {
-                SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
+            SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
 
-                _sessions.Add(session);
+            _sessions.TryAdd(session, 0);
 
-                return session;
-            }
+            return session;
         }
 
-        internal void Unregister(SDL2HardwareDeviceSession session)
+        internal bool Unregister(SDL2HardwareDeviceSession session)
         {
-            lock (_lock)
-            {
-                _sessions.Remove(session);
-            }
+            return _sessions.TryRemove(session, out _);
         }
 
         private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount)
@@ -149,10 +140,8 @@ namespace Ryujinx.Audio.Backends.SDL2
         {
             if (disposing)
             {
-                while (_sessions.Count > 0)
+                foreach (SDL2HardwareDeviceSession session in _sessions.Keys)
                 {
-                    SDL2HardwareDeviceSession session = _sessions[_sessions.Count - 1];
-
                     session.Dispose();
                 }
 
diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
index 344dd9b6..ceb6e706 100644
--- a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
+++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
@@ -201,7 +201,7 @@ namespace Ryujinx.Audio.Backends.SDL2
 
         protected virtual void Dispose(bool disposing)
         {
-            if (disposing)
+            if (disposing && _driver.Unregister(this))
             {
                 PrepareToClose();
                 Stop();
@@ -210,8 +210,6 @@ namespace Ryujinx.Audio.Backends.SDL2
                 {
                     SDL_CloseAudioDevice(_outputStream);
                 }
-
-                _driver.Unregister(this);
             }
         }
 
diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
index b9b549e6..20aa4cbf 100644
--- a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
@@ -3,7 +3,7 @@ using Ryujinx.Audio.Integration;
 using Ryujinx.Memory;
 using SoundIOSharp;
 using System;
-using System.Collections.Generic;
+using System.Collections.Concurrent;
 using System.Threading;
 
 using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
@@ -12,19 +12,17 @@ namespace Ryujinx.Audio.Backends.SoundIo
 {
     public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
     {
-        private object _lock = new object();
-
-        private SoundIO _audioContext;
-        private SoundIODevice _audioDevice;
-        private ManualResetEvent _updateRequiredEvent;
-        private List<SoundIoHardwareDeviceSession> _sessions;
+        private readonly SoundIO _audioContext;
+        private readonly SoundIODevice _audioDevice;
+        private readonly ManualResetEvent _updateRequiredEvent;
+        private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
         private int _disposeState;
 
         public SoundIoHardwareDeviceDriver()
         {
             _audioContext = new SoundIO();
             _updateRequiredEvent = new ManualResetEvent(false);
-            _sessions = new List<SoundIoHardwareDeviceSession>();
+            _sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>();
 
             _audioContext.Connect();
             _audioContext.FlushEvents();
@@ -142,22 +140,16 @@ namespace Ryujinx.Audio.Backends.SoundIo
                 throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
             }
 
-            lock (_lock)
-            {
-                SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
+            SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount);
 
-                _sessions.Add(session);
+            _sessions.TryAdd(session, 0);
 
-                return session;
-            }
+            return session;
         }
 
-        internal void Unregister(SoundIoHardwareDeviceSession session)
+        internal bool Unregister(SoundIoHardwareDeviceSession session)
         {
-            lock (_lock)
-            {
-                _sessions.Remove(session);
-            }
+            return _sessions.TryRemove(session, out _);
         }
 
         public static SoundIOFormat GetSoundIoFormat(SampleFormat format)
@@ -219,26 +211,10 @@ namespace Ryujinx.Audio.Backends.SoundIo
         {
             if (disposing)
             {
-                int sessionCount = 0;
-
-                // NOTE: This is done in a way to avoid possible situations when the SoundIoHardwareDeviceSession is already being dispose in another thread but doesn't hold the lock and tries to Unregister.
-                do
+                foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
                 {
-                    lock (_lock)
-                    {
-                        if (_sessions.Count == 0)
-                        {
-                            break;
-                        }
-
-                        SoundIoHardwareDeviceSession session = _sessions[_sessions.Count - 1];
-
-                        session.Dispose();
-
-                        sessionCount = _sessions.Count;
-                    }
+                    session.Dispose();
                 }
-                while (sessionCount > 0);
 
                 _audioContext.Disconnect();
                 _audioContext.Dispose();
diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
index 884e75ed..ee2eeb77 100644
--- a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
+++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
@@ -423,14 +423,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
 
         protected virtual void Dispose(bool disposing)
         {
-            if (disposing)
+            if (disposing && _driver.Unregister(this))
             {
                 PrepareToClose();
                 Stop();
 
                 _outputStream.Dispose();
-
-                _driver.Unregister(this);
             }
         }
 
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index d74cfec1..e1278b39 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -7,7 +7,6 @@ using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Configuration;
 using Ryujinx.Graphics.GAL;
-using Ryujinx.HLE.HOS.Services.Hid;
 using Ryujinx.Input;
 using Ryujinx.Input.GTK3;
 using Ryujinx.Input.HLE;
@@ -138,8 +137,6 @@ namespace Ryujinx.Ui
         {
             ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged;
 
-            Window.Cursor = null;
-
             NpadManager.Dispose();
             Dispose();
         }
@@ -151,7 +148,7 @@ namespace Ryujinx.Ui
                 _lastCursorMoveTime = Stopwatch.GetTimestamp();
             }
 
-            if(ConfigurationState.Instance.Hid.EnableMouse)
+            if (ConfigurationState.Instance.Hid.EnableMouse)
             {
                 Window.Cursor = _invisibleCursor;
             }
@@ -609,7 +606,7 @@ namespace Ryujinx.Ui
             {
                 state |= KeyboardHotkeyState.ToggleVSync;
             }
-            
+
             if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot))
             {
                 state |= KeyboardHotkeyState.Screenshot;