diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 0c83cda56e..32d1cddcbf 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -601,7 +601,7 @@ GdbServer::HandledPacketType GdbServer::ThreadAction(char action, uint32_t tid) switch (action) { case 'c': { { - std::lock_guard lk(*SyscallHandler->TM.GetThreadsCreationMutex()); + std::lock_guard lk(SyscallHandler->TM.GetThreadsCreationMutex()); auto Threads = SyscallHandler->TM.GetThreads(); for (auto& Thread : *Threads) { Thread->ThreadSleeping.NotifyOne(); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h index d18d26118e..466706d040 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h @@ -23,18 +23,18 @@ class SyscallHandler; class SignalDelegator; // A latch that can be inspected to see if there is a waiter currently active. -// This allows us to remove the race condition between a thread trying to go asleep and something else telling it to go to sleep or wake up. -// -// Only one thread can ever wait on a latch, while another thread signals it. +// In contrast to std::condition_variable, an InspectableLatch may be signaled *before* being waited on without change of effect. class InspectableLatch final { public: bool Wait(struct timespec* Timeout = nullptr) { + LOGMAN_THROW_A_FMT(Mutex.load() != HAS_SIGNALED, "Stale signal mutex!"); + LOGMAN_THROW_A_FMT(Mutex.load() != HAS_WAITER, "Can't have multiple waiters on a single InspectableLatch!"); while (true) { uint32_t Expected = HAS_NO_WAITER; const uint32_t Desired = HAS_WAITER; if (Mutex.compare_exchange_strong(Expected, Desired)) { - // We have latched, now futex. + // We have latched, sleep using a futex syscall until signaled. constexpr int Op = FUTEX_WAIT | FUTEX_PRIVATE_FLAG; // WAIT will keep sleeping on the futex word while it is `val` int Result = ::syscall(SYS_futex, &Mutex, Op, @@ -64,6 +64,9 @@ class InspectableLatch final { } void NotifyOne() { +#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED + LOGMAN_THROW_A_FMT(Mutex.load() != HAS_SIGNALED, "Trying to double signal!"); +#endif DoNotify(1); } @@ -81,8 +84,8 @@ class InspectableLatch final { uint32_t Expected = HAS_WAITER; const uint32_t Desired = HAS_SIGNALED; - // If the mutex is in a waiting state and we have CAS exchanged it to HAS_SIGNALED, then execute a futex syscall. - // otherwise just leave since nothing was waiting. + // If the mutex is in a waiting state and we have CAS exchanged it to HAS_SIGNALED, then signal the thread to wake up with a futex + // syscall. otherwise just leave since nothing was waiting. if (Mutex.compare_exchange_strong(Expected, Desired)) { constexpr int Op = FUTEX_WAKE | FUTEX_PRIVATE_FLAG; @@ -243,8 +246,8 @@ class ThreadManager final { } } - FEXCore::ForkableUniqueMutex* GetThreadsCreationMutex() { - return &ThreadCreationMutex; + FEXCore::ForkableUniqueMutex& GetThreadsCreationMutex() { + return ThreadCreationMutex; } const fextl::vector* GetThreads() const {