From e756ad4556773ddf5b1d955e8372f0c736ab8337 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 16 May 2026 01:11:06 +0000 Subject: [PATCH] Fix ProcessLoader stale PID validation against kernel process table (#102) This PR adresses the following issue : `ProcessLoader.ActiveApplication` could return invalid results when `_latestPid` pointed to a process that no longer existed in the kernel's process table. The original exception path was commented out and bypassed (by sh0inx?) with `GetValueOrDefault` to prevent UI lockups, but this only resolved the symptoms without fixing the root cause. This was due to sevral factors : - `_latestPid` was never reset or validated against the actual process state - ProcessLoader maintained its own `_processesByPid` dictionary separate from the kernel's `KernelContext.Processes` - No cleanups happened when processes exited or were terminated - ProcessLoader state could drift out of sync with the kernel process table **Solution/Fixes** - Validate` _latestPid` against the kernel process table before returning `ActiveApplication` - Check process state (Exited/Exiting) and automatically clear stale references - Add thread-safe cleanup methods (`ClearProcess`, `ClearAllProcesses`) - Integrate `ClearAllProcesses` into Switch.Dispose for proper shutdown cleanup - Add warning logs when stale PID is detected and cleared for debugging **Code Changes**: - `ProcessLoader.cs`: Add `_pidLock`, update `ActiveApplication` with validation, add cleanup methods - `Switch.cs`: Call `Processes.ClearAllProcesses()` in Dispose() Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/102 --- .../Loaders/Processes/ProcessLoader.cs | 92 +++++++++++++++++-- src/Ryujinx.HLE/Switch.cs | 1 + 2 files changed, 84 insertions(+), 9 deletions(-) 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();