diff options
author | gdkchan <gab.dark.100@gmail.com> | 2018-12-18 03:33:36 -0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-18 03:33:36 -0200 |
commit | 0039bb639493b2d1e2764cae380311ba8e87704b (patch) | |
tree | 63a912a95c8261775c2acb8a5b9ca0f10ad4ae33 /Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | |
parent | 2534a7f10c627810e6e0272b4cc9758e90f733c1 (diff) |
Refactor SVC handler (#540)
* Refactor SVC handler
* Get rid of KernelErr
* Split kernel code files into multiple folders
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs')
-rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs new file mode 100644 index 00000000..3ad64024 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -0,0 +1,1030 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KThread : KSynchronizationObject, IKFutureSchedulerObject + { + public CpuThread Context { get; private set; } + + public long AffinityMask { get; set; } + + public long ThreadUid { get; private set; } + + public long TotalTimeRunning { get; set; } + + public KSynchronizationObject SignaledObj { get; set; } + + public ulong CondVarAddress { get; set; } + + private ulong _entrypoint; + + public ulong MutexAddress { get; set; } + + public KProcess Owner { get; private set; } + + private ulong _tlsAddress; + + public long LastScheduledTime { get; set; } + + public LinkedListNode<KThread>[] SiblingsPerCore { get; private set; } + + public LinkedList<KThread> Withholder { get; set; } + public LinkedListNode<KThread> WithholderNode { get; set; } + + public LinkedListNode<KThread> ProcessListNode { get; set; } + + private LinkedList<KThread> _mutexWaiters; + private LinkedListNode<KThread> _mutexWaiterNode; + + public KThread MutexOwner { get; private set; } + + public int ThreadHandleForUserMutex { get; set; } + + private ThreadSchedState _forcePauseFlags; + + public KernelResult ObjSyncResult { get; set; } + + public int DynamicPriority { get; set; } + public int CurrentCore { get; set; } + public int BasePriority { get; set; } + public int PreferredCore { get; set; } + + private long _affinityMaskOverride; + private int _preferredCoreOverride; + private int _affinityOverrideCount; + + public ThreadSchedState SchedFlags { get; private set; } + + public bool ShallBeTerminated { get; private set; } + + public bool SyncCancelled { get; set; } + public bool WaitingSync { get; set; } + + private bool _hasExited; + + public bool WaitingInArbitration { get; set; } + + private KScheduler _scheduler; + + private KSchedulingData _schedulingData; + + public long LastPc { get; set; } + + public KThread(Horizon system) : base(system) + { + _scheduler = system.Scheduler; + _schedulingData = system.Scheduler.SchedulingData; + + SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount]; + + _mutexWaiters = new LinkedList<KThread>(); + } + + public KernelResult Initialize( + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int defaultCpuCore, + KProcess owner, + ThreadType type = ThreadType.User) + { + if ((uint)type > 3) + { + throw new ArgumentException($"Invalid thread type \"{type}\"."); + } + + PreferredCore = defaultCpuCore; + + AffinityMask |= 1L << defaultCpuCore; + + SchedFlags = type == ThreadType.Dummy + ? ThreadSchedState.Running + : ThreadSchedState.None; + + CurrentCore = PreferredCore; + + DynamicPriority = priority; + BasePriority = priority; + + ObjSyncResult = KernelResult.ThreadNotStarted; + + _entrypoint = entrypoint; + + if (type == ThreadType.User) + { + if (owner.AllocateThreadLocalStorage(out _tlsAddress) != KernelResult.Success) + { + return KernelResult.OutOfMemory; + } + + MemoryHelper.FillWithZeros(owner.CpuMemory, (long)_tlsAddress, KTlsPageInfo.TlsEntrySize); + } + + bool is64Bits; + + if (owner != null) + { + Owner = owner; + + owner.IncrementThreadCount(); + + is64Bits = (owner.MmuFlags & 1) != 0; + } + else + { + is64Bits = true; + } + + Context = new CpuThread(owner.Translator, owner.CpuMemory, (long)entrypoint); + + Context.ThreadState.X0 = argsPtr; + Context.ThreadState.X31 = stackTop; + + Context.ThreadState.CntfrqEl0 = 19200000; + Context.ThreadState.Tpidr = (long)_tlsAddress; + + owner.SubscribeThreadEventHandlers(Context); + + Context.WorkFinished += ThreadFinishedHandler; + + ThreadUid = System.GetThreadUid(); + + if (owner != null) + { + owner.AddThread(this); + + if (owner.IsPaused) + { + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSection.Leave(); + + return KernelResult.Success; + } + + _forcePauseFlags |= ThreadSchedState.ProcessPauseFlag; + + CombineForcePauseFlags(); + + System.CriticalSection.Leave(); + } + } + + return KernelResult.Success; + } + + public KernelResult Start() + { + if (!System.KernelInitialized) + { + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; + + CombineForcePauseFlags(); + } + + System.CriticalSection.Leave(); + } + + KernelResult result = KernelResult.ThreadTerminating; + + System.CriticalSection.Enter(); + + if (!ShallBeTerminated) + { + KThread currentThread = System.Scheduler.GetCurrentThread(); + + while (SchedFlags != ThreadSchedState.TerminationPending && + currentThread.SchedFlags != ThreadSchedState.TerminationPending && + !currentThread.ShallBeTerminated) + { + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None) + { + result = KernelResult.InvalidState; + + break; + } + + if (currentThread._forcePauseFlags == ThreadSchedState.None) + { + if (Owner != null && _forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + SetNewSchedFlags(ThreadSchedState.Running); + + result = KernelResult.Success; + + break; + } + else + { + currentThread.CombineForcePauseFlags(); + + System.CriticalSection.Leave(); + System.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated) + { + break; + } + } + } + } + + System.CriticalSection.Leave(); + + return result; + } + + public void Exit() + { + System.CriticalSection.Enter(); + + _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; + + ExitImpl(); + + System.CriticalSection.Leave(); + } + + private void ExitImpl() + { + System.CriticalSection.Enter(); + + SetNewSchedFlags(ThreadSchedState.TerminationPending); + + _hasExited = true; + + Signal(); + + System.CriticalSection.Leave(); + } + + public KernelResult Sleep(long timeout) + { + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + SetNewSchedFlags(ThreadSchedState.Paused); + + if (timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(this, timeout); + } + + System.CriticalSection.Leave(); + + if (timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(this); + } + + return 0; + } + + public void Yield() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + //Move current thread to the end of the queue. + _schedulingData.Reschedule(DynamicPriority, CurrentCore, this); + } + + _scheduler.ThreadReselectionRequested = true; + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldWithLoadBalancing() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + int prio = DynamicPriority; + int core = CurrentCore; + + KThread nextThreadOnCurrentQueue = null; + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + //Move current thread to the end of the queue. + _schedulingData.Reschedule(prio, core, this); + + Func<KThread, bool> predicate = x => x.DynamicPriority == prio; + + nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate); + } + + IEnumerable<KThread> SuitableCandidates() + { + foreach (KThread thread in _schedulingData.SuggestedThreads(core)) + { + int srcCore = thread.CurrentCore; + + if (srcCore >= 0) + { + KThread selectedSrcCore = _scheduler.CoreContexts[srcCore].SelectedThread; + + if (selectedSrcCore == thread || ((selectedSrcCore?.DynamicPriority ?? 2) < 2)) + { + continue; + } + } + + //If the candidate was scheduled after the current thread, then it's not worth it, + //unless the priority is higher than the current one. + if (nextThreadOnCurrentQueue.LastScheduledTime >= thread.LastScheduledTime || + nextThreadOnCurrentQueue.DynamicPriority < thread.DynamicPriority) + { + yield return thread; + } + } + } + + KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); + + if (dst != null) + { + _schedulingData.TransferToCore(dst.DynamicPriority, core, dst); + + _scheduler.ThreadReselectionRequested = true; + } + + if (this != nextThreadOnCurrentQueue) + { + _scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldAndWaitForLoadBalancing() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + int core = CurrentCore; + + _schedulingData.TransferToCore(DynamicPriority, -1, this); + + KThread selectedThread = null; + + if (!_schedulingData.ScheduledThreads(core).Any()) + { + foreach (KThread thread in _schedulingData.SuggestedThreads(core)) + { + if (thread.CurrentCore < 0) + { + continue; + } + + KThread firstCandidate = _schedulingData.ScheduledThreads(thread.CurrentCore).FirstOrDefault(); + + if (firstCandidate == thread) + { + continue; + } + + if (firstCandidate == null || firstCandidate.DynamicPriority >= 2) + { + _schedulingData.TransferToCore(thread.DynamicPriority, core, thread); + + selectedThread = thread; + } + + break; + } + } + + if (selectedThread != this) + { + _scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void SetPriority(int priority) + { + System.CriticalSection.Enter(); + + BasePriority = priority; + + UpdatePriorityInheritance(); + + System.CriticalSection.Leave(); + } + + public KernelResult SetActivity(bool pause) + { + KernelResult result = KernelResult.Success; + + System.CriticalSection.Enter(); + + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + if (pause) + { + //Pause, the force pause flag should be clear (thread is NOT paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + { + _forcePauseFlags |= ThreadSchedState.ThreadPauseFlag; + + CombineForcePauseFlags(); + } + else + { + result = KernelResult.InvalidState; + } + } + else + { + //Unpause, the force pause flag should be set (thread is paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) + { + ThreadSchedState oldForcePauseFlags = _forcePauseFlags; + + _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; + + if ((oldForcePauseFlags & ~ThreadSchedState.ThreadPauseFlag) == ThreadSchedState.None) + { + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + } + else + { + result = KernelResult.InvalidState; + } + } + } + + System.CriticalSection.Leave(); + System.CriticalSection.Leave(); + + return result; + } + + public void CancelSynchronization() + { + System.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync) + { + SyncCancelled = true; + } + else if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + + SyncCancelled = true; + } + else + { + SignaledObj = null; + ObjSyncResult = KernelResult.Cancelled; + + SetNewSchedFlags(ThreadSchedState.Running); + + SyncCancelled = false; + } + + System.CriticalSection.Leave(); + } + + public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask) + { + System.CriticalSection.Enter(); + + bool useOverride = _affinityOverrideCount != 0; + + //The value -3 is "do not change the preferred core". + if (newCore == -3) + { + newCore = useOverride ? _preferredCoreOverride : PreferredCore; + + if ((newAffinityMask & (1 << newCore)) == 0) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidCombination; + } + } + + if (useOverride) + { + _preferredCoreOverride = newCore; + _affinityMaskOverride = newAffinityMask; + } + else + { + long oldAffinityMask = AffinityMask; + + PreferredCore = newCore; + AffinityMask = newAffinityMask; + + if (oldAffinityMask != newAffinityMask) + { + int oldCore = CurrentCore; + + if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0) + { + if (PreferredCore < 0) + { + CurrentCore = HighestSetCore(AffinityMask); + } + else + { + CurrentCore = PreferredCore; + } + } + + AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); + } + } + + System.CriticalSection.Leave(); + + return KernelResult.Success; + } + + private static int HighestSetCore(long mask) + { + for (int core = KScheduler.CpuCoresCount - 1; core >= 0; core--) + { + if (((mask >> core) & 1) != 0) + { + return core; + } + } + + return -1; + } + + private void CombineForcePauseFlags() + { + ThreadSchedState oldFlags = SchedFlags; + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + SchedFlags = lowNibble | _forcePauseFlags; + + AdjustScheduling(oldFlags); + } + + private void SetNewSchedFlags(ThreadSchedState newFlags) + { + System.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | newFlags; + + if ((oldFlags & ThreadSchedState.LowMask) != newFlags) + { + AdjustScheduling(oldFlags); + } + + System.CriticalSection.Leave(); + } + + public void ReleaseAndResume() + { + System.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + } + else + { + SetNewSchedFlags(ThreadSchedState.Running); + } + } + + System.CriticalSection.Leave(); + } + + public void Reschedule(ThreadSchedState newFlags) + { + System.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | + (newFlags & ThreadSchedState.LowMask); + + AdjustScheduling(oldFlags); + + System.CriticalSection.Leave(); + } + + public void AddMutexWaiter(KThread requester) + { + AddToMutexWaitersList(requester); + + requester.MutexOwner = this; + + UpdatePriorityInheritance(); + } + + public void RemoveMutexWaiter(KThread thread) + { + if (thread._mutexWaiterNode?.List != null) + { + _mutexWaiters.Remove(thread._mutexWaiterNode); + } + + thread.MutexOwner = null; + + UpdatePriorityInheritance(); + } + + public KThread RelinquishMutex(ulong mutexAddress, out int count) + { + count = 0; + + if (_mutexWaiters.First == null) + { + return null; + } + + KThread newMutexOwner = null; + + LinkedListNode<KThread> currentNode = _mutexWaiters.First; + + do + { + //Skip all threads that are not waiting for this mutex. + while (currentNode != null && currentNode.Value.MutexAddress != mutexAddress) + { + currentNode = currentNode.Next; + } + + if (currentNode == null) + { + break; + } + + LinkedListNode<KThread> nextNode = currentNode.Next; + + _mutexWaiters.Remove(currentNode); + + currentNode.Value.MutexOwner = newMutexOwner; + + if (newMutexOwner != null) + { + //New owner was already selected, re-insert on new owner list. + newMutexOwner.AddToMutexWaitersList(currentNode.Value); + } + else + { + //New owner not selected yet, use current thread. + newMutexOwner = currentNode.Value; + } + + count++; + + currentNode = nextNode; + } + while (currentNode != null); + + if (newMutexOwner != null) + { + UpdatePriorityInheritance(); + + newMutexOwner.UpdatePriorityInheritance(); + } + + return newMutexOwner; + } + + private void UpdatePriorityInheritance() + { + //If any of the threads waiting for the mutex has + //higher priority than the current thread, then + //the current thread inherits that priority. + int highestPriority = BasePriority; + + if (_mutexWaiters.First != null) + { + int waitingDynamicPriority = _mutexWaiters.First.Value.DynamicPriority; + + if (waitingDynamicPriority < highestPriority) + { + highestPriority = waitingDynamicPriority; + } + } + + if (highestPriority != DynamicPriority) + { + int oldPriority = DynamicPriority; + + DynamicPriority = highestPriority; + + AdjustSchedulingForNewPriority(oldPriority); + + if (MutexOwner != null) + { + //Remove and re-insert to ensure proper sorting based on new priority. + MutexOwner._mutexWaiters.Remove(_mutexWaiterNode); + + MutexOwner.AddToMutexWaitersList(this); + + MutexOwner.UpdatePriorityInheritance(); + } + } + } + + private void AddToMutexWaitersList(KThread thread) + { + LinkedListNode<KThread> nextPrio = _mutexWaiters.First; + + int currentPriority = thread.DynamicPriority; + + while (nextPrio != null && nextPrio.Value.DynamicPriority <= currentPriority) + { + nextPrio = nextPrio.Next; + } + + if (nextPrio != null) + { + thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread); + } + else + { + thread._mutexWaiterNode = _mutexWaiters.AddLast(thread); + } + } + + private void AdjustScheduling(ThreadSchedState oldFlags) + { + if (oldFlags == SchedFlags) + { + return; + } + + if (oldFlags == ThreadSchedState.Running) + { + //Was running, now it's stopped. + if (CurrentCore >= 0) + { + _schedulingData.Unschedule(DynamicPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Unsuggest(DynamicPriority, core, this); + } + } + } + else if (SchedFlags == ThreadSchedState.Running) + { + //Was stopped, now it's running. + if (CurrentCore >= 0) + { + _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewPriority(int oldPriority) + { + if (SchedFlags != ThreadSchedState.Running) + { + return; + } + + //Remove thread from the old priority queues. + if (CurrentCore >= 0) + { + _schedulingData.Unschedule(oldPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Unsuggest(oldPriority, core, this); + } + } + + //Add thread to the new priority queues. + KThread currentThread = _scheduler.GetCurrentThread(); + + if (CurrentCore >= 0) + { + if (currentThread == this) + { + _schedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this); + } + else + { + _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewAffinity(long oldAffinityMask, int oldCore) + { + if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount) + { + return; + } + + //Remove from old queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((oldAffinityMask >> core) & 1) != 0) + { + if (core == oldCore) + { + _schedulingData.Unschedule(DynamicPriority, core, this); + } + else + { + _schedulingData.Unsuggest(DynamicPriority, core, this); + } + } + } + + //Insert on new queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((AffinityMask >> core) & 1) != 0) + { + if (core == CurrentCore) + { + _schedulingData.Schedule(DynamicPriority, core, this); + } + else + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + public override bool IsSignaled() + { + return _hasExited; + } + + public void SetEntryArguments(long argsPtr, int threadHandle) + { + Context.ThreadState.X0 = (ulong)argsPtr; + Context.ThreadState.X1 = (ulong)threadHandle; + } + + public void ClearExclusive() + { + Owner.CpuMemory.ClearExclusive(CurrentCore); + } + + public void TimeUp() + { + ReleaseAndResume(); + } + + public void PrintGuestStackTrace() + { + Owner.Debugger.PrintGuestStackTrace(Context.ThreadState); + } + + private void ThreadFinishedHandler(object sender, EventArgs e) + { + System.Scheduler.ExitThread(this); + + Terminate(); + + System.Scheduler.RemoveThread(this); + } + + public void Terminate() + { + Owner?.RemoveThread(this); + + if (_tlsAddress != 0 && Owner.FreeThreadLocalStorage(_tlsAddress) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure freeing thread local storage."); + } + + System.CriticalSection.Enter(); + + //Wake up all threads that may be waiting for a mutex being held + //by this thread. + foreach (KThread thread in _mutexWaiters) + { + thread.MutexOwner = null; + thread._preferredCoreOverride = 0; + thread.ObjSyncResult = KernelResult.InvalidState; + + thread.ReleaseAndResume(); + } + + System.CriticalSection.Leave(); + + Owner?.DecrementThreadCountAndTerminateIfZero(); + } + } +}
\ No newline at end of file |