From 5224cc49c4daa8d20999bad15a9b16d8d16b9d10 Mon Sep 17 00:00:00 2001
From: Subv <subv2112@gmail.com>
Date: Sun, 12 Aug 2018 16:35:27 -0500
Subject: [PATCH] Kernel/Mutex: Don't duplicate threads in the mutex waiter
 list.

Exit from AddMutexWaiter early if the thread is already waiting for a mutex owned by the owner thread.

This accounts for the possibility of a thread that is waiting on a condition variable being awakened twice in a row.

Also added more validation asserts.

This should fix one of the random crashes in Breath Of The Wild.
---
 src/core/hle/kernel/svc.cpp    |  3 +--
 src/core/hle/kernel/thread.cpp | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 5db2db687..5818cc06d 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -706,8 +706,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
             Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
             auto owner = g_handle_table.Get<Thread>(owner_handle);
             ASSERT(owner);
-            ASSERT(thread->status != ThreadStatus::Running);
-            thread->status = ThreadStatus::WaitMutex;
+            ASSERT(thread->status == ThreadStatus::WaitMutex);
             thread->wakeup_callback = nullptr;
 
             owner->AddMutexWaiter(thread);
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index b9022feae..40918ca81 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -419,12 +419,33 @@ VAddr Thread::GetCommandBufferAddress() const {
 }
 
 void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
+    if (thread->lock_owner == this) {
+        // If the thread is already waiting for this thread to release the mutex, ensure that the
+        // waiters list is consistent and return without doing anything.
+        auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
+        ASSERT(itr != wait_mutex_threads.end());
+        return;
+    }
+
+    // A thread can't wait on two different mutexes at the same time.
+    ASSERT(thread->lock_owner == nullptr);
+
+    // Ensure that the thread is not already in the list of mutex waiters
+    auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
+    ASSERT(itr == wait_mutex_threads.end());
+
     thread->lock_owner = this;
     wait_mutex_threads.emplace_back(std::move(thread));
     UpdatePriority();
 }
 
 void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
+    ASSERT(thread->lock_owner == this);
+
+    // Ensure that the thread is in the list of mutex waiters
+    auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
+    ASSERT(itr != wait_mutex_threads.end());
+
     boost::remove_erase(wait_mutex_threads, thread);
     thread->lock_owner = nullptr;
     UpdatePriority();