From 00ce9eea620652b97b4d3e8cd9218c6fccff8b1c Mon Sep 17 00:00:00 2001
From: Mary <me@thog.eu>
Date: Tue, 29 Jun 2021 19:37:13 +0200
Subject: Fix disposing of IPC sessions server at emulation stop (#2334)

---
 .../OpenALHardwareDeviceDriver.cs                  | 23 ++++++++----
 .../SoundIoHardwareDeviceDriver.cs                 | 26 +++++++++++---
 .../SoundIoHardwareDeviceSession.cs                |  6 +++-
 Ryujinx.Audio/Input/AudioInputManager.cs           | 25 +++++++++++--
 Ryujinx.Audio/Input/AudioInputSystem.cs            | 13 +++++--
 Ryujinx.Audio/Output/AudioOutputManager.cs         | 25 +++++++++++--
 Ryujinx.Audio/Output/AudioOutputSystem.cs          | 11 +++++-
 Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs |  7 +++-
 .../Renderer/Server/AudioRendererManager.cs        | 10 +++++-
 Ryujinx.HLE/HOS/Horizon.cs                         |  9 +++--
 .../LibraryAppletCreator/ILibraryAppletAccessor.cs | 27 +++++++-------
 .../HOS/Services/Audio/AudioIn/AudioInServer.cs    | 11 ++----
 .../HOS/Services/Audio/AudioOut/AudioOutServer.cs  | 11 ++----
 .../Audio/AudioRenderer/AudioRendererServer.cs     | 11 ++----
 .../IDeliveryCacheDirectoryService.cs              | 10 +++---
 .../ServiceCreator/IDeliveryCacheFileService.cs    | 10 +++---
 .../ServiceCreator/IDeliveryCacheStorageService.cs | 10 +++---
 Ryujinx.HLE/HOS/Services/DisposableIpcService.cs   | 20 +++++++++++
 .../Friend/ServiceCreator/INotificationService.cs  |  9 +++--
 .../HOS/Services/Fs/FileSystemProxy/IFile.cs       | 12 ++-----
 .../HOS/Services/Fs/FileSystemProxy/IStorage.cs    | 12 ++-----
 Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs |  9 +++--
 Ryujinx.HLE/HOS/Services/IpcService.cs             | 13 +++++++
 .../Services/Nifm/StaticService/IGeneralService.cs |  9 +++--
 Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs        | 15 ++++----
 Ryujinx.HLE/HOS/Services/ServerBase.cs             | 41 ++++++++++++++++++----
 Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs      |  7 ++++
 Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs   | 13 +++----
 28 files changed, 283 insertions(+), 122 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/DisposableIpcService.cs

diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
index 387ae772..721e96c6 100644
--- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
@@ -133,19 +133,28 @@ namespace Ryujinx.Audio.Backends.OpenAL
         {
             if (disposing)
             {
-                lock (_lock)
-                {
-                    _stillRunning = false;
-                    _updaterThread.Join();
+                _stillRunning = false;
 
-                    // Loop against all sessions to dispose them (they will unregister themself)
-                    while (_sessions.Count > 0)
+                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
+                {
+                    lock (_lock)
                     {
-                        OpenALHardwareDeviceSession session = _sessions[0];
+                        if (_sessions.Count == 0)
+                        {
+                            break;
+                        }
+
+                        OpenALHardwareDeviceSession session = _sessions[_sessions.Count - 1];
 
                         session.Dispose();
+
+                        sessionCount = _sessions.Count;
                     }
                 }
+                while (sessionCount > 0);
 
                 ALC.DestroyContext(_context);
                 ALC.CloseDevice(_device);
diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
index 00977fcb..b9b549e6 100644
--- a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
@@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
         private SoundIODevice _audioDevice;
         private ManualResetEvent _updateRequiredEvent;
         private List<SoundIoHardwareDeviceSession> _sessions;
+        private int _disposeState;
 
         public SoundIoHardwareDeviceDriver()
         {
@@ -208,19 +209,36 @@ namespace Ryujinx.Audio.Backends.SoundIo
 
         public void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
 
         protected virtual void Dispose(bool disposing)
         {
             if (disposing)
             {
-                while (_sessions.Count > 0)
+                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
                 {
-                    SoundIoHardwareDeviceSession session = _sessions[_sessions.Count - 1];
+                    lock (_lock)
+                    {
+                        if (_sessions.Count == 0)
+                        {
+                            break;
+                        }
+
+                        SoundIoHardwareDeviceSession session = _sessions[_sessions.Count - 1];
+
+                        session.Dispose();
 
-                    session.Dispose();
+                        sessionCount = _sessions.Count;
+                    }
                 }
+                while (sessionCount > 0);
 
                 _audioContext.Disconnect();
                 _audioContext.Dispose();
diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
index 925a1cb4..884e75ed 100644
--- a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
+++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
@@ -17,6 +17,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
         private DynamicRingBuffer _ringBuffer;
         private ulong _playedSampleCount;
         private ManualResetEvent _updateRequiredEvent;
+        private int _disposeState;
 
         public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
         {
@@ -435,7 +436,10 @@ namespace Ryujinx.Audio.Backends.SoundIo
 
         public override void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
     }
 }
diff --git a/Ryujinx.Audio/Input/AudioInputManager.cs b/Ryujinx.Audio/Input/AudioInputManager.cs
index e098ae9e..5c1f01db 100644
--- a/Ryujinx.Audio/Input/AudioInputManager.cs
+++ b/Ryujinx.Audio/Input/AudioInputManager.cs
@@ -21,6 +21,8 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Memory;
 using System;
 using System.Diagnostics;
+using System.Linq;
+using System.Threading;
 
 namespace Ryujinx.Audio.Input
 {
@@ -61,6 +63,11 @@ namespace Ryujinx.Audio.Input
         /// </summary>
         private int _activeSessionCount;
 
+        /// <summary>
+        /// The dispose state.
+        /// </summary>
+        private int _disposeState;
+
         /// <summary>
         /// Create a new <see cref="AudioInputManager"/>.
         /// </summary>
@@ -248,14 +255,28 @@ namespace Ryujinx.Audio.Input
 
         public void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
 
         protected virtual void Dispose(bool disposing)
         {
             if (disposing)
             {
-                // Nothing to do here.
+                // Clone the sessions array to dispose them outside the lock.
+                AudioInputSystem[] sessions;
+
+                lock (_sessionLock)
+                {
+                    sessions = _sessions.ToArray();
+                }
+
+                foreach (AudioInputSystem input in sessions)
+                {
+                    input?.Dispose();
+                }
             }
         }
     }
diff --git a/Ryujinx.Audio/Input/AudioInputSystem.cs b/Ryujinx.Audio/Input/AudioInputSystem.cs
index 8064a947..b3fd91e7 100644
--- a/Ryujinx.Audio/Input/AudioInputSystem.cs
+++ b/Ryujinx.Audio/Input/AudioInputSystem.cs
@@ -18,6 +18,7 @@
 using Ryujinx.Audio.Common;
 using Ryujinx.Audio.Integration;
 using System;
+using System.Threading;
 
 namespace Ryujinx.Audio.Input
 {
@@ -62,10 +63,15 @@ namespace Ryujinx.Audio.Input
         private AudioInputManager _manager;
 
         /// <summary>
-        /// THe lock of the parent.
+        /// The lock of the parent.
         /// </summary>
         private object _parentLock;
 
+        /// <summary>
+        /// The dispose state.
+        /// </summary>
+        private int _disposeState;
+
         /// <summary>
         /// Create a new <see cref="AudioInputSystem"/>.
         /// </summary>
@@ -384,7 +390,10 @@ namespace Ryujinx.Audio.Input
 
         public void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
 
         protected virtual void Dispose(bool disposing)
diff --git a/Ryujinx.Audio/Output/AudioOutputManager.cs b/Ryujinx.Audio/Output/AudioOutputManager.cs
index baa84997..852632fa 100644
--- a/Ryujinx.Audio/Output/AudioOutputManager.cs
+++ b/Ryujinx.Audio/Output/AudioOutputManager.cs
@@ -21,6 +21,8 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Memory;
 using System;
 using System.Diagnostics;
+using System.Linq;
+using System.Threading;
 
 namespace Ryujinx.Audio.Output
 {
@@ -61,6 +63,11 @@ namespace Ryujinx.Audio.Output
         /// </summary>
         private int _activeSessionCount;
 
+        /// <summary>
+        /// The dispose state.
+        /// </summary>
+        private int _disposeState;
+
         /// <summary>
         /// Create a new <see cref="AudioOutputManager"/>.
         /// </summary>
@@ -242,14 +249,28 @@ namespace Ryujinx.Audio.Output
 
         public void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
 
         protected virtual void Dispose(bool disposing)
         {
             if (disposing)
             {
-                // Nothing to do here.
+                // Clone the sessions array to dispose them outside the lock.
+                AudioOutputSystem[] sessions;
+
+                lock (_sessionLock)
+                {
+                    sessions = _sessions.ToArray();
+                }
+
+                foreach (AudioOutputSystem output in sessions)
+                {
+                    output?.Dispose();
+                }
             }
         }
     }
diff --git a/Ryujinx.Audio/Output/AudioOutputSystem.cs b/Ryujinx.Audio/Output/AudioOutputSystem.cs
index f5db9d7a..d32d417a 100644
--- a/Ryujinx.Audio/Output/AudioOutputSystem.cs
+++ b/Ryujinx.Audio/Output/AudioOutputSystem.cs
@@ -18,6 +18,7 @@
 using Ryujinx.Audio.Common;
 using Ryujinx.Audio.Integration;
 using System;
+using System.Threading;
 
 namespace Ryujinx.Audio.Output
 {
@@ -66,6 +67,11 @@ namespace Ryujinx.Audio.Output
         /// </summary>
         private object _parentLock;
 
+        /// <summary>
+        /// The dispose state.
+        /// </summary>
+        private int _disposeState;
+
         /// <summary>
         /// Create a new <see cref="AudioOutputSystem"/>.
         /// </summary>
@@ -357,7 +363,10 @@ namespace Ryujinx.Audio.Output
 
         public void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
 
         protected virtual void Dispose(bool disposing)
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 943a2d78..6aed3c5d 100644
--- a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -94,6 +94,8 @@ namespace Ryujinx.Audio.Renderer.Server
 
         private AudioRendererManager _manager;
 
+        private int _disposeState;
+
         public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
         {
             _manager            = manager;
@@ -811,7 +813,10 @@ namespace Ryujinx.Audio.Renderer.Server
 
         public void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
 
         protected virtual void Dispose(bool disposing)
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
index 004ac656..71d0f318 100644
--- a/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
+++ b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
@@ -82,6 +82,11 @@ namespace Ryujinx.Audio.Renderer.Server
         /// </summary>
         public AudioProcessor Processor { get; }
 
+        /// <summary>
+        /// The dispose state.
+        /// </summary>
+        private int _disposeState;
+
         /// <summary>
         /// Create a new <see cref="AudioRendererManager"/>.
         /// </summary>
@@ -313,7 +318,10 @@ namespace Ryujinx.Audio.Renderer.Server
 
         public void Dispose()
         {
-            Dispose(true);
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
         }
 
         protected virtual void Dispose(bool disposing)
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index a4ed7c37..916ed797 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -70,6 +70,7 @@ namespace Ryujinx.HLE.HOS
 
         internal List<NfpDevice> NfpDevices { get; private set; }
 
+        internal ServerBase SmServer { get; private set; }
         internal ServerBase BsdServer { get; private set; }
         internal ServerBase AudRenServer { get; private set; }
         internal ServerBase AudOutServer { get; private set; }
@@ -284,13 +285,11 @@ namespace Ryujinx.HLE.HOS
 
         public void InitializeServices()
         {
-            IUserInterface sm = new IUserInterface(KernelContext);
-            sm.TrySetServer(new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext)));
+            SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext));
 
             // Wait until SM server thread is done with initialization,
             // only then doing connections to SM is safe.
-            sm.Server.InitDone.WaitOne();
-            sm.Server.InitDone.Dispose();
+            SmServer.InitDone.WaitOne();
 
             BsdServer = new ServerBase(KernelContext, "BsdServer");
             AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
@@ -419,7 +418,7 @@ namespace Ryujinx.HLE.HOS
                         SurfaceFlinger.Dispose();
 
                         // Terminate HLE services (must be done after the application is already terminated,
-                        // otherwise the application will receive errors due to service termination.
+                        // otherwise the application will receive errors due to service termination).
                         foreach (KProcess process in KernelContext.Processes.Values.Where(x => !x.Flags.HasFlag(ProcessCreationFlags.IsApplication)))
                         {
                             process.Terminate();
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
index 1377eac0..2deb830e 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
@@ -8,7 +8,7 @@ using System;
 
 namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator
 {
-    class ILibraryAppletAccessor : IpcService, IDisposable
+    class ILibraryAppletAccessor : DisposableIpcService
     {
         private KernelContext _kernelContext;
 
@@ -241,21 +241,24 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
             return ResultCode.Success;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            if (_stateChangedEventHandle != 0)
+            if (isDisposing)
             {
-                _kernelContext.Syscall.CloseHandle(_stateChangedEventHandle);
-            }
+                if (_stateChangedEventHandle != 0)
+                {
+                    _kernelContext.Syscall.CloseHandle(_stateChangedEventHandle);
+                }
 
-            if (_normalOutDataEventHandle != 0)
-            {
-                _kernelContext.Syscall.CloseHandle(_normalOutDataEventHandle);
-            }
+                if (_normalOutDataEventHandle != 0)
+                {
+                    _kernelContext.Syscall.CloseHandle(_normalOutDataEventHandle);
+                }
 
-            if (_interactiveOutDataEventHandle != 0)
-            {
-                _kernelContext.Syscall.CloseHandle(_interactiveOutDataEventHandle);
+                if (_interactiveOutDataEventHandle != 0)
+                {
+                    _kernelContext.Syscall.CloseHandle(_interactiveOutDataEventHandle);
+                }
             }
         }
     }
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
index b45a4d2c..f9a9447f 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
 {
-    class AudioInServer : IpcService, IDisposable
+    class AudioInServer : DisposableIpcService
     {
         private IAudioIn _impl;
 
@@ -193,14 +193,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
             return ResultCode.Success;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
+            if (isDisposing)
             {
                 _impl.Dispose();
             }
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
index b7515e0f..aff08811 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
 {
-    class AudioOutServer : IpcService, IDisposable
+    class AudioOutServer : DisposableIpcService
     {
         private IAudioOut _impl;
 
@@ -174,14 +174,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
             return ResultCode.Success;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
+            if (isDisposing)
             {
                 _impl.Dispose();
             }
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
index bb51b506..bd5030f3 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
@@ -7,7 +7,7 @@ using System.Buffers;
 
 namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
 {
-    class AudioRendererServer : IpcService, IDisposable
+    class AudioRendererServer : DisposableIpcService
     {
         private IAudioRenderer _impl;
 
@@ -172,14 +172,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
             return result;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
+            if (isDisposing)
             {
                 _impl.Dispose();
             }
diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs
index 51d8f66c..46c2c09c 100644
--- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs
+++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs
@@ -1,12 +1,11 @@
 using LibHac;
 using LibHac.Bcat;
 using Ryujinx.Common;
-using System;
 using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
 {
-    class IDeliveryCacheDirectoryService : IpcService, IDisposable
+    class IDeliveryCacheDirectoryService : DisposableIpcService
     {
         private LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService _base;
 
@@ -55,9 +54,12 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
             return (ResultCode)result.Value;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            _base?.Dispose();
+            if (isDisposing)
+            {
+                _base?.Dispose();
+            }
         }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs
index 9354b60e..55c89a3e 100644
--- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs
+++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs
@@ -1,11 +1,10 @@
 using LibHac;
 using LibHac.Bcat;
 using Ryujinx.Common;
-using System;
 
 namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
 {
-    class IDeliveryCacheFileService : IpcService, IDisposable
+    class IDeliveryCacheFileService : DisposableIpcService
     {
         private LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService _base;
 
@@ -68,9 +67,12 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
             return (ResultCode)result.Value;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            _base?.Dispose();
+            if (isDisposing)
+            {
+                _base?.Dispose();
+            }
         }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs
index cac5f170..0d2f2521 100644
--- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs
+++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs
@@ -1,11 +1,10 @@
 using LibHac;
 using LibHac.Bcat;
-using System;
 using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
 {
-    class IDeliveryCacheStorageService : IpcService, IDisposable
+    class IDeliveryCacheStorageService : DisposableIpcService
     {
         private LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService _base;
 
@@ -60,9 +59,12 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
             return (ResultCode)result.Value;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            _base?.Dispose();
+            if (isDisposing)
+            {
+                _base?.Dispose();
+            }
         }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs b/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs
new file mode 100644
index 00000000..7aecdfd1
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services
+{
+    abstract class DisposableIpcService : IpcService, IDisposable
+    {
+        private int _disposeState;
+
+        protected abstract void Dispose(bool isDisposing);
+
+        public void Dispose()
+        {
+            if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+            {
+                Dispose(true);
+            }
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs
index 700d4ab4..f5614ddd 100644
--- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs
+++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs
@@ -9,7 +9,7 @@ using System.Collections.Generic;
 
 namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
 {
-    class INotificationService : IpcService, IDisposable
+    class INotificationService : DisposableIpcService
     {
         private readonly UserId                       _userId;
         private readonly FriendServicePermissionLevel _permissionLevel;
@@ -167,9 +167,12 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
             }
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            NotificationEventHandler.Instance.UnregisterNotificationService(this);
+            if (isDisposing)
+            {
+                NotificationEventHandler.Instance.UnregisterNotificationService(this);
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs
index 681b6c17..cf1611e7 100644
--- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs
+++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs
@@ -1,10 +1,9 @@
 using LibHac;
 using LibHac.Fs;
-using System;
 
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 {
-    class IFile : IpcService, IDisposable
+    class IFile : DisposableIpcService
     {
         private LibHac.Fs.Fsa.IFile _baseFile;
 
@@ -82,14 +81,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
             return (ResultCode)result.Value;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
+            if (isDisposing)
             {
                 _baseFile?.Dispose();
             }
diff --git a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
index 89955634..62a3c62a 100644
--- a/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
+++ b/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs
@@ -1,10 +1,9 @@
 using LibHac;
 using Ryujinx.HLE.HOS.Ipc;
-using System;
 
 namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
 {
-    class IStorage : IpcService, IDisposable
+    class IStorage : DisposableIpcService
     {
         private LibHac.Fs.IStorage _baseStorage;
 
@@ -53,14 +52,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
             return (ResultCode)result.Value;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (disposing)
+            if (isDisposing)
             {
                 _baseStorage?.Dispose();
             }
diff --git a/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs b/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs
index d6449a2d..bc4a2eb9 100644
--- a/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs
+++ b/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs
@@ -3,7 +3,7 @@ using LibHac;
 
 namespace Ryujinx.HLE.HOS.Services.Fs
 {
-    class ISaveDataInfoReader : IpcService, IDisposable
+    class ISaveDataInfoReader : DisposableIpcService
     {
         private ReferenceCountedDisposable<LibHac.FsSrv.ISaveDataInfoReader> _baseReader;
 
@@ -29,9 +29,12 @@ namespace Ryujinx.HLE.HOS.Services.Fs
             return (ResultCode)result.Value;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            _baseReader.Dispose();
+            if (isDisposing)
+            {
+                _baseReader?.Dispose();
+            }
         }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs
index e9582c26..e3306071 100644
--- a/Ryujinx.HLE/HOS/Services/IpcService.cs
+++ b/Ryujinx.HLE/HOS/Services/IpcService.cs
@@ -265,5 +265,18 @@ namespace Ryujinx.HLE.HOS.Services
         {
             _parent = parent._parent;
         }
+
+        public virtual void DestroyAtExit()
+        {
+            foreach (object domainObject in _domainObjects.Values)
+            {
+                if (domainObject != this && domainObject is IDisposable disposableObj)
+                {
+                    disposableObj.Dispose();
+                }
+            }
+
+            _domainObjects.Clear();
+        }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs
index e650879b..2296838e 100644
--- a/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs
+++ b/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs
@@ -10,7 +10,7 @@ using System.Text;
 
 namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
 {
-    class IGeneralService : IpcService, IDisposable
+    class IGeneralService : DisposableIpcService
     {
         private GeneralServiceDetail _generalServiceDetail;
 
@@ -197,9 +197,12 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
             return (targetProperties, targetAddressInfo);
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            GeneralServiceManager.Remove(_generalServiceDetail.ClientId);
+            if (isDisposing)
+            {
+                GeneralServiceManager.Remove(_generalServiceDetail.ClientId);
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs
index ff5db94f..aa17f6ac 100644
--- a/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs
@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Ro
 {
     [Service("ldr:ro")]
     [Service("ro:1")] // 7.0.0+
-    class IRoInterface : IpcService, IDisposable
+    class IRoInterface : DisposableIpcService
     {
         private const int MaxNrr         = 0x40;
         private const int MaxNro         = 0x40;
@@ -571,14 +571,17 @@ namespace Ryujinx.HLE.HOS.Services.Ro
             return ResultCode.Success;
         }
 
-        public void Dispose()
+        protected override void Dispose(bool isDisposing)
         {
-            foreach (NroInfo info in _nroInfos)
+            if (isDisposing)
             {
-                UnmapNroFromInfo(info);
-            }
+                foreach (NroInfo info in _nroInfos)
+                {
+                    UnmapNroFromInfo(info);
+                }
 
-            _nroInfos.Clear();
+                _nroInfos.Clear();
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/ServerBase.cs b/Ryujinx.HLE/HOS/Services/ServerBase.cs
index c9d009a9..695394a5 100644
--- a/Ryujinx.HLE/HOS/Services/ServerBase.cs
+++ b/Ryujinx.HLE/HOS/Services/ServerBase.cs
@@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Sm;
 using System;
 using System.Buffers.Binary;
 using System.Collections.Generic;
@@ -12,7 +13,7 @@ using System.Threading;
 
 namespace Ryujinx.HLE.HOS.Services
 {
-    class ServerBase
+    class ServerBase : IDisposable
     {
         // Must be the maximum value used by services (highest one know is the one used by nvservices = 0x8000).
         // Having a size that is too low will cause failures as data copy will fail if the receiving buffer is
@@ -67,6 +68,9 @@ namespace Ryujinx.HLE.HOS.Services
 
         public void AddSessionObj(KServerSession serverSession, IpcService obj)
         {
+            // Ensure that the sever loop is running.
+            InitDone.WaitOne();
+
             _selfProcess.HandleTable.GenerateHandle(serverSession, out int serverSessionHandle);
             AddSessionObj(serverSessionHandle, obj);
         }
@@ -86,14 +90,10 @@ namespace Ryujinx.HLE.HOS.Services
                 _context.Syscall.ManageNamedPort("sm:", 50, out int serverPortHandle);
 
                 AddPort(serverPortHandle, SmObjectFactory);
-
-                InitDone.Set();
-            }
-            else
-            {
-                InitDone.Dispose();
             }
 
+            InitDone.Set();
+
             KThread thread = KernelStatic.GetCurrentThread();
             ulong messagePtr = thread.TlsAddress;
             _context.Syscall.SetHeapSize(0x200000, out ulong heapAddr);
@@ -153,6 +153,8 @@ namespace Ryujinx.HLE.HOS.Services
                     _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
                 }
             }
+
+            Dispose();
         }
 
         private bool Process(int serverSessionHandle, ulong recvListAddr)
@@ -349,5 +351,30 @@ namespace Ryujinx.HLE.HOS.Services
 
             return response;
         }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                foreach (IpcService service in _sessions.Values)
+                {
+                    if (service is IDisposable disposableObj)
+                    {
+                        disposableObj.Dispose();
+                    }
+
+                    service.DestroyAtExit();
+                }
+
+                _sessions.Clear();
+
+                InitDone.Dispose();
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
index 8b1ec5b8..a5595e31 100644
--- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
@@ -245,5 +245,12 @@ namespace Ryujinx.HLE.HOS.Services.Sm
 
             return name;
         }
+
+        public override void DestroyAtExit()
+        {
+            _commonServer.Dispose();
+
+            base.DestroyAtExit();
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs b/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs
index 62e118d9..0f38e685 100644
--- a/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs
@@ -4,10 +4,12 @@ using System.Security.Cryptography;
 namespace Ryujinx.HLE.HOS.Services.Spl
 {
     [Service("csrng")]
-    class IRandomInterface : IpcService, IDisposable
+    class IRandomInterface : DisposableIpcService
     {
         private RNGCryptoServiceProvider _rng;
 
+        private object _lock = new object();
+
         public IRandomInterface(ServiceCtx context)
         {
             _rng = new RNGCryptoServiceProvider();
@@ -26,14 +28,9 @@ namespace Ryujinx.HLE.HOS.Services.Spl
             return ResultCode.Success;
         }
 
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        protected virtual void Dispose(bool disposing)
+        protected override void Dispose(bool isDisposing)
         {
-            if (disposing)
+            if (isDisposing)
             {
                 _rng.Dispose();
             }
-- 
cgit v1.2.3-70-g09d2