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

@ -38,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public ulong PersonalMmHeapPagesCount { get; private set; }
private ProcessState _state;
public ProcessState State { get; private set; }
private object _processLock;
private object _threadingLock;
@ -383,7 +383,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Name = creationInfo.Name;
_state = ProcessState.Created;
State = ProcessState.Created;
_creationTimestamp = PerformanceCounter.ElapsedMilliseconds;
@ -579,7 +579,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
lock (_processLock)
{
if (_state > ProcessState.CreatedAttached)
if (State > ProcessState.CreatedAttached)
{
return KernelResult.InvalidState;
}
@ -733,8 +733,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
mainThread.SetEntryArguments(0, mainThreadHandle);
ProcessState oldState = _state;
ProcessState newState = _state != ProcessState.Created
ProcessState oldState = State;
ProcessState newState = State != ProcessState.Created
? ProcessState.Attached
: ProcessState.Started;
@ -768,9 +768,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private void SetState(ProcessState newState)
{
if (_state != newState)
if (State != newState)
{
_state = newState;
State = newState;
_signaled = true;
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()
{
ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory);
@ -909,12 +923,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
lock (_processLock)
{
if (_state >= ProcessState.Started)
if (State >= ProcessState.Started)
{
if (_state == ProcessState.Started ||
_state == ProcessState.Crashed ||
_state == ProcessState.Attached ||
_state == ProcessState.DebugSuspended)
if (State == ProcessState.Started ||
State == ProcessState.Crashed ||
State == ProcessState.Attached ||
State == ProcessState.DebugSuspended)
{
SetState(ProcessState.Exiting);
@ -933,23 +947,98 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
if (shallTerminate)
{
// UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread());
HandleTable.Destroy();
SignalExitForDebugEvent();
SignalExitToDebugTerminated();
SignalExit();
}
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;
}
}
}
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 SignalExitForDebugEvent()
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.
}
@ -976,7 +1065,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
lock (_processLock)
{
if (_state != ProcessState.Exited && _signaled)
if (State != ProcessState.Exited && _signaled)
{
_signaled = false;
@ -999,7 +1088,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
foreach (KThread thread in _threads)
{
thread.Context.Running = false;
System.Scheduler.ExitThread(thread);
System.Scheduler.CoreManager.Set(thread.HostThread);
}