diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index 900703f6e..f217ecd0b 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -28,21 +28,61 @@ namespace Ryujinx.HLE.Loaders.Processes private ulong _latestPid; + private readonly object _pidLock = new(); + #nullable enable public ProcessResult? ActiveApplication { get { - return _processesByPid.GetValueOrDefault(_latestPid); - - // Using this if statement locks up the UI and prevents a new game from loading. - // Haven't quite deduced why yet. - - if (!_processesByPid.TryGetValue(_latestPid, out ProcessResult value)) - throw new RyujinxException( - $"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?"); + lock (_pidLock) + { + // Check if _latestPid is still valid + if (_latestPid == 0) + { + return null; + } - return value; + // Verify process still exists in kernel (authoritative source) + if (!_device.System.KernelContext.Processes.TryGetValue(_latestPid, out HOS.Kernel.Process.KProcess? kernelProcess)) + { + // Process no longer exists in kernel, clear stale state + Logger.Warning?.Print(LogClass.Loader, + $"ActiveApplication PID {_latestPid} no longer exists in kernel, clearing stale state"); + + _processesByPid.TryRemove(_latestPid, out _); + _latestPid = 0; + TitleIDs.CurrentApplication.Value = null; + + return null; + } + + // Verify process still exists in ProcessLoader's dictionary + if (_processesByPid.TryGetValue(_latestPid, out ProcessResult? processResult)) + { + // Additional check: verify process state + if (kernelProcess.State == HOS.Kernel.Process.ProcessState.Exited || + kernelProcess.State == HOS.Kernel.Process.ProcessState.Exiting) + { + Logger.Warning?.Print(LogClass.Loader, + $"ActiveApplication PID {_latestPid} is in state {kernelProcess.State}, clearing"); + + _processesByPid.TryRemove(_latestPid, out _); + _latestPid = 0; + TitleIDs.CurrentApplication.Value = null; + + return null; + } + + return processResult; + } + + // Fallback: clear stale PID if not in our dictionary + Logger.Warning?.Print(LogClass.Loader, + $"ActiveApplication PID {_latestPid} not in ProcessLoader dictionary, clearing"); + _latestPid = 0; + return null; + } } } #nullable disable @@ -285,5 +325,39 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } + + /// + /// Clears a specific process from the ProcessLoader's tracking. + /// This should be called when a process exits or is terminated. + /// + /// The process ID to clear + public void ClearProcess(ulong pid) + { + lock (_pidLock) + { + if (_processesByPid.TryRemove(pid, out _)) + { + if (_latestPid == pid) + { + _latestPid = 0; + TitleIDs.CurrentApplication.Value = null; + } + } + } + } + + /// + /// Clears all processes from the ProcessLoader's tracking. + /// This should be called during system shutdown. + /// + public void ClearAllProcesses() + { + lock (_pidLock) + { + _processesByPid.Clear(); + _latestPid = 0; + TitleIDs.CurrentApplication.Value = null; + } + } } } diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 850c8b5fa..90af47988 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -183,6 +183,7 @@ namespace Ryujinx.HLE { if (disposing) { + Processes.ClearAllProcesses(); System.Dispose(); AudioDeviceDriver.Dispose(); FileSystem.Dispose();