Make HLE disposable safely (#850)

* Make HLE disposable safely

This fix the oldest issue with the HLE code: the kernel side
disposability.

Changelog:

- Implement KProcess::UnpauseAndTerminateAllThreadsExcept, KThread::Terminate, KThread::TerminateCurrentProcess, KThread::PrepareForTermiation and the svc post handler accurately.
- Implement svcTerminateProcess and svcExitProcess. (both untested)
- Fix KHandleTable::Destroy not decrementing refcount of all objects stored in the table.
- Spawn a custom KProcess with the maximum priority to terminate every guest KProcess. (terminating kernel emulation safely)
- General system stability improvements to enhance the user's experience.

* Fix a typo in a comment in KProcess.cs

* Address gdk's comments
This commit is contained in:
Thog 2019-12-26 02:50:17 +01:00 committed by Ac_K
parent 87bfe681ef
commit 55c956e2ec
7 changed files with 293 additions and 31 deletions

View File

@ -753,22 +753,43 @@ namespace Ryujinx.HLE.HOS
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{
KProcess terminationProcess = new KProcess(this);
KThread terminationThread = new KThread(this);
terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () =>
{ {
// Force all threads to exit. // Force all threads to exit.
lock (Processes) lock (Processes)
{ {
foreach (KProcess process in Processes.Values) foreach (KProcess process in Processes.Values)
{ {
process.StopAllThreads(); process.Terminate();
// Exit ourself now!
Scheduler.ExitThread(terminationThread);
Scheduler.GetCurrentThread().Exit();
Scheduler.RemoveThread(terminationThread);
} }
} }
});
terminationThread.Start();
// Signal the vsync event to avoid issues of KThread waiting on it.
if (Device.EnableDeviceVsync)
{
Device.VsyncEvent.Set();
}
// This is needed as the IPC Dummy KThread is also counted in the ThreadCounter.
ThreadCounter.Signal();
// It's only safe to release resources once all threads // It's only safe to release resources once all threads
// have exited. // have exited.
ThreadCounter.Signal(); ThreadCounter.Signal();
//ThreadCounter.Wait(); // FIXME: Uncomment this ThreadCounter.Wait();
// BODY: Right now, guest processes don't exit properly because the logic waits for them to exit.
// BODY: However, this doesn't happen when you close the main window so we need to find a way to make them exit gracefully
Scheduler.Dispose(); Scheduler.Dispose();

View File

@ -272,6 +272,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
disposableObj.Dispose(); disposableObj.Dispose();
} }
entry.Obj.DecrementReferenceCount();
entry.Obj = null; entry.Obj = null;
entry.Next = _nextFreeEntry; entry.Next = _nextFreeEntry;

View File

@ -38,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public ulong PersonalMmHeapPagesCount { get; private set; } public ulong PersonalMmHeapPagesCount { get; private set; }
private ProcessState _state; public ProcessState State { get; private set; }
private object _processLock; private object _processLock;
private object _threadingLock; private object _threadingLock;
@ -383,7 +383,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Name = creationInfo.Name; Name = creationInfo.Name;
_state = ProcessState.Created; State = ProcessState.Created;
_creationTimestamp = PerformanceCounter.ElapsedMilliseconds; _creationTimestamp = PerformanceCounter.ElapsedMilliseconds;
@ -579,7 +579,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
lock (_processLock) lock (_processLock)
{ {
if (_state > ProcessState.CreatedAttached) if (State > ProcessState.CreatedAttached)
{ {
return KernelResult.InvalidState; return KernelResult.InvalidState;
} }
@ -733,8 +733,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
mainThread.SetEntryArguments(0, mainThreadHandle); mainThread.SetEntryArguments(0, mainThreadHandle);
ProcessState oldState = _state; ProcessState oldState = State;
ProcessState newState = _state != ProcessState.Created ProcessState newState = State != ProcessState.Created
? ProcessState.Attached ? ProcessState.Attached
: ProcessState.Started; : ProcessState.Started;
@ -768,9 +768,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private void SetState(ProcessState newState) private void SetState(ProcessState newState)
{ {
if (_state != newState) if (State != newState)
{ {
_state = newState; State = newState;
_signaled = true; _signaled = true;
Signal(); Signal();
@ -820,6 +820,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
} }
} }
public void DecrementToZeroWhileTerminatingCurrent()
{
System.ThreadCounter.Signal();
while (Interlocked.Decrement(ref _threadCount) != 0)
{
Destroy();
TerminateCurrentProcess();
}
// Nintendo panic here because if it reaches this point, the current thread should be already dead.
// As we handle the death of the thread in the post SVC handler and inside the CPU emulator, we don't panic here.
}
public ulong GetMemoryCapacity() public ulong GetMemoryCapacity()
{ {
ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory); ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory);
@ -909,12 +923,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
lock (_processLock) lock (_processLock)
{ {
if (_state >= ProcessState.Started) if (State >= ProcessState.Started)
{ {
if (_state == ProcessState.Started || if (State == ProcessState.Started ||
_state == ProcessState.Crashed || State == ProcessState.Crashed ||
_state == ProcessState.Attached || State == ProcessState.Attached ||
_state == ProcessState.DebugSuspended) State == ProcessState.DebugSuspended)
{ {
SetState(ProcessState.Exiting); SetState(ProcessState.Exiting);
@ -933,23 +947,98 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
if (shallTerminate) if (shallTerminate)
{ {
// UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
HandleTable.Destroy(); HandleTable.Destroy();
SignalExitForDebugEvent(); SignalExitToDebugTerminated();
SignalExit(); SignalExit();
} }
return result; return result;
} }
private void UnpauseAndTerminateAllThreadsExcept(KThread thread) public void TerminateCurrentProcess()
{ {
// TODO. bool shallTerminate = false;
System.CriticalSection.Enter();
lock (_processLock)
{
if (State >= ProcessState.Started)
{
if (State == ProcessState.Started ||
State == ProcessState.Attached ||
State == ProcessState.DebugSuspended)
{
SetState(ProcessState.Exiting);
shallTerminate = true;
}
}
} }
private void SignalExitForDebugEvent() System.CriticalSection.Leave();
if (shallTerminate)
{
UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
HandleTable.Destroy();
// NOTE: this is supposed to be called in receiving of the mailbox.
SignalExitToDebugExited();
SignalExit();
}
}
private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
{
lock (_threadingLock)
{
System.CriticalSection.Enter();
foreach (KThread thread in _threads)
{
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
{
thread.PrepareForTermination();
}
}
System.CriticalSection.Leave();
}
KThread blockedThread = null;
lock (_threadingLock)
{
foreach (KThread thread in _threads)
{
if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
{
thread.IncrementReferenceCount();
blockedThread = thread;
break;
}
}
}
if (blockedThread != null)
{
blockedThread.Terminate();
blockedThread.DecrementReferenceCount();
}
}
private void SignalExitToDebugTerminated()
{
// TODO: Debug events.
}
private void SignalExitToDebugExited()
{ {
// TODO: Debug events. // TODO: Debug events.
} }
@ -976,7 +1065,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
lock (_processLock) lock (_processLock)
{ {
if (_state != ProcessState.Exited && _signaled) if (State != ProcessState.Exited && _signaled)
{ {
_signaled = false; _signaled = false;
@ -999,7 +1088,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
foreach (KThread thread in _threads) foreach (KThread thread in _threads)
{ {
thread.Context.Running = false; System.Scheduler.ExitThread(thread);
System.Scheduler.CoreManager.Set(thread.HostThread); System.Scheduler.CoreManager.Set(thread.HostThread);
} }

View File

@ -1,5 +1,6 @@
using ARMeilleure.State; using ARMeilleure.State;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System; using System;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
@ -29,6 +30,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
ExecutionContext context = (ExecutionContext)sender; ExecutionContext context = (ExecutionContext)sender;
svcFunc(this, context); svcFunc(this, context);
PostSvcHandler();
}
private void PostSvcHandler()
{
KThread currentThread = _system.Scheduler.GetCurrentThread();
currentThread.HandlePostSyscall();
} }
} }
} }

View File

@ -17,9 +17,41 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
ExitProcess(); ExitProcess();
} }
public KernelResult TerminateProcess64(int handle)
{
return TerminateProcess(handle);
}
private KernelResult TerminateProcess(int handle)
{
KProcess process = _process.HandleTable.GetObject<KProcess>(handle);
KernelResult result;
if (process != null)
{
if (process == _system.Scheduler.GetCurrentProcess())
{
result = KernelResult.Success;
process.DecrementToZeroWhileTerminatingCurrent();
}
else
{
result = process.Terminate();
process.DecrementReferenceCount();
}
}
else
{
result = KernelResult.InvalidHandle;
}
return result;
}
private void ExitProcess() private void ExitProcess()
{ {
_system.Scheduler.GetCurrentProcess().Terminate(); _system.Scheduler.GetCurrentProcess().TerminateCurrentProcess();
} }
public KernelResult SignalEvent64(int handle) public KernelResult SignalEvent64(int handle)
@ -184,6 +216,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ {
currentThread.PrintGuestStackTrace(); currentThread.PrintGuestStackTrace();
// As the process is exiting, this is probably caused by emulation termination.
if (currentThread.Owner.State == ProcessState.Exiting)
{
return;
}
// TODO: Debug events.
currentThread.Owner.TerminateCurrentProcess();
throw new GuestBrokeExecutionException(); throw new GuestBrokeExecutionException();
} }
else else

View File

@ -74,7 +74,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{ 0x72, nameof(SvcHandler.ConnectToPort64) }, { 0x72, nameof(SvcHandler.ConnectToPort64) },
{ 0x73, nameof(SvcHandler.SetProcessMemoryPermission64) }, { 0x73, nameof(SvcHandler.SetProcessMemoryPermission64) },
{ 0x77, nameof(SvcHandler.MapProcessCodeMemory64) }, { 0x77, nameof(SvcHandler.MapProcessCodeMemory64) },
{ 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) } { 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64) },
{ 0x7B, nameof(SvcHandler.TerminateProcess64) }
}; };
_svcTable64 = new Action<SvcHandler, ExecutionContext>[0x80]; _svcTable64 = new Action<SvcHandler, ExecutionContext>[0x80];

View File

@ -70,7 +70,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public ThreadSchedState SchedFlags { get; private set; } public ThreadSchedState SchedFlags { get; private set; }
public bool ShallBeTerminated { get; private set; } private int _shallBeTerminated;
public bool ShallBeTerminated { get => _shallBeTerminated != 0; set => _shallBeTerminated = value ? 1 : 0; }
public bool SyncCancelled { get; set; } public bool SyncCancelled { get; set; }
public bool WaitingSync { get; set; } public bool WaitingSync { get; set; }
@ -104,7 +106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
int priority, int priority,
int defaultCpuCore, int defaultCpuCore,
KProcess owner, KProcess owner,
ThreadType type = ThreadType.User) ThreadType type = ThreadType.User,
ThreadStart customHostThreadStart = null)
{ {
if ((uint)type > 3) if ((uint)type > 3)
{ {
@ -156,7 +159,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
is64Bits = true; is64Bits = true;
} }
HostThread = new Thread(() => ThreadStart(entrypoint)); HostThread = new Thread(customHostThreadStart == null ? () => ThreadStart(entrypoint) : customHostThreadStart);
Context = new ARMeilleure.State.ExecutionContext(); Context = new ARMeilleure.State.ExecutionContext();
@ -182,6 +185,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
ThreadUid = System.GetThreadUid(); ThreadUid = System.GetThreadUid();
HostThread.Name = $"Host Thread (thread id {ThreadUid})";
_hasBeenInitialized = true; _hasBeenInitialized = true;
if (owner != null) if (owner != null)
@ -300,6 +305,100 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
DecrementReferenceCount(); DecrementReferenceCount();
} }
public ThreadSchedState PrepareForTermination()
{
System.CriticalSection.Enter();
ThreadSchedState result;
if (Interlocked.CompareExchange(ref _shallBeTerminated, 1, 0) == 0)
{
if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.None)
{
SchedFlags = ThreadSchedState.TerminationPending;
}
else
{
if (_forcePauseFlags != ThreadSchedState.None)
{
_forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag;
ThreadSchedState oldSchedFlags = SchedFlags;
SchedFlags &= ThreadSchedState.LowMask;
AdjustScheduling(oldSchedFlags);
}
if (BasePriority >= 0x10)
{
SetPriority(0xF);
}
if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Running)
{
// TODO: GIC distributor stuffs (sgir changes ect)
}
SignaledObj = null;
ObjSyncResult = KernelResult.ThreadTerminating;
ReleaseAndResume();
}
}
result = SchedFlags;
System.CriticalSection.Leave();
return result & ThreadSchedState.LowMask;
}
public void Terminate()
{
ThreadSchedState state = PrepareForTermination();
if (state != ThreadSchedState.TerminationPending)
{
System.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
}
}
public void HandlePostSyscall()
{
ThreadSchedState state;
do
{
if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
{
System.Scheduler.ExitThread(this);
Exit();
// As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here.
break;
}
System.CriticalSection.Enter();
if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
{
state = ThreadSchedState.TerminationPending;
}
else
{
if (_forcePauseFlags != ThreadSchedState.None)
{
CombineForcePauseFlags();
}
state = ThreadSchedState.Running;
}
System.CriticalSection.Leave();
} while (state == ThreadSchedState.TerminationPending);
}
private void ExitImpl() private void ExitImpl()
{ {
System.CriticalSection.Enter(); System.CriticalSection.Enter();