diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index b45e72ee0..e74605b28 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -14,6 +14,7 @@ #include "xenia/base/platform.h" #include "xenia/base/threading_timer_queue.h" +#include #include #include #include @@ -981,11 +982,22 @@ class PosixCondition final : public PosixConditionBase { static void* ThreadStartRoutine(void* parameter); bool signaled() const override { return signaled_; } void post_execution() override { + // Reap exactly once. post_execution() runs on EVERY successful Wait(), and + // a thread object stays signaled forever once it has exited -- so every + // later wait on the same handle would pthread_join() a pthread_t that the + // first join already reaped and freed. On glibc that faults or, if the tid + // was recycled, blocks forever inside pthread_join(). The guest does + // exactly this at mission teardown (several waiters on one thread handle), + // which hung the waiting guest thread: the game froze to a black screen. + if (reaped_.exchange(true)) { + return; + } if (thread_) { pthread_join(thread_, nullptr); } sem_destroy(&suspend_sem_); } + std::atomic reaped_{false}; pthread_t thread_; pid_t tid_ = 0; // Kernel TID for setpriority() fallback mutable bool fifo_failed_ = false; // True after SCHED_FIFO was rejected