diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index ce2133fdc..403a1a684 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -7,6 +7,7 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Processes.Extensions; using System.Collections.Concurrent; @@ -23,16 +24,62 @@ namespace Ryujinx.HLE.Loaders.Processes private ulong _latestPid; - public ProcessResult ActiveApplication + private readonly object _pidLock = new(); + +#nullable enable + public ProcessResult? ActiveApplication { get { - if (_latestPid != 0 && _processesByPid.TryGetValue(_latestPid, out ProcessResult process)) - return process; + lock (_pidLock) + { + // Check if _latestPid is still valid + if (_latestPid == 0) + { + return null; + } - return null; + // 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; + + 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; + + 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 public ProcessLoader(Switch device) { @@ -235,7 +282,7 @@ namespace Ryujinx.HLE.Loaders.Processes } // Explicitly null TitleId to disable the shader cache. - Graphics.Gpu.GraphicsConfig.TitleId = null; + GraphicsConfig.TitleId = null; _device.Gpu.HostInitalized.Set(); ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device, @@ -274,5 +321,37 @@ 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; + } + } + } + } + + /// + /// Clears all processes from the ProcessLoader's tracking. + /// This should be called during system shutdown. + /// + public void ClearAllProcesses() + { + lock (_pidLock) + { + _processesByPid.Clear(); + _latestPid = 0; + } + } } } diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index c7c01eb8c..96b0c0a48 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -208,6 +208,7 @@ namespace Ryujinx.HLE { if (disposing) { + Processes.ClearAllProcesses(); System.Dispose(); AudioDeviceDriver.Dispose(); FileSystem.Dispose();