From e16f0fcd3df944f01f082da9a3fd905f8349d565 Mon Sep 17 00:00:00 2001 From: Zulux91 <28682730+Zulux91@users.noreply.github.com> Date: Mon, 10 Aug 2026 11:42:03 -0400 Subject: [PATCH] Sample thread CPU time by tid on Android get_cycles passes the pthread handle to pthread_getcpuclockid, which glibc answers with an error for a thread that has already exited -- the else branch below returns the last known value for exactly that case. bionic instead looks the handle up in its list of live threads and aborts the process when it is not there. m_thread is never cleared when a thread ends and the performance overlay samples every PPU, SPU and RSX thread on a timer, so one finished thread is enough to take the emulator down with it. Latent here rather than absent: it needs the overlay on and a thread to have gone. Record the kernel tid at initialize and build the per-thread clock id from it the way bionic does once its own lookup succeeds, so clock_gettime simply fails for a dead thread, which is what the surrounding code already expects. Cleared at finalize so a thread stops being sampled before it goes away. Other platforms keep the original path. Found and fixed by Zulux91 in PS3Native. --- Utilities/Thread.cpp | 19 +++++++++++++++++-- Utilities/Thread.h | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 86705ef7b..735d9d18a 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -2844,6 +2844,7 @@ void thread_base::initialize(void (*error_cb)()) [[maybe_unused]] u64 new_tid = 0; #elif defined(ANDROID) const u64 new_tid = pthread_self(); + m_native_tid = static_cast(gettid()); #else const u64 new_tid = reinterpret_cast(pthread_self()); #endif @@ -2998,6 +2999,10 @@ u64 thread_base::finalize(thread_state result_state) noexcept // Avoid race with the destructor const u64 _self = m_thread; +#ifdef ANDROID + m_native_tid = 0; +#endif + // Set result state (errored or finalized) m_sync.fetch_op([&](u32& v) { @@ -3373,11 +3378,21 @@ u64 thread_base::get_cycles() clockid_t _clock; struct timespec thread_time; #ifdef ANDROID - pthread_t thread_id = handle; + const u32 native_tid = m_native_tid; + + if (!handle || !native_tid) + { + return m_cycles; + } + + _clock = (~static_cast(native_tid) << 3) | 6; + + if (!clock_gettime(_clock, &thread_time)) #else pthread_t thread_id = reinterpret_cast(handle); -#endif + if (!pthread_getcpuclockid(thread_id, &_clock) && !clock_gettime(_clock, &thread_time)) +#endif { cycles = static_cast(thread_time.tv_sec) * 1'000'000'000 + thread_time.tv_nsec; #endif diff --git a/Utilities/Thread.h b/Utilities/Thread.h index 639d4f192..cca276077 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -139,6 +139,10 @@ private: // Thread handle (platform-specific) atomic_t m_thread{0}; +#ifdef ANDROID + atomic_t m_native_tid{0}; +#endif + // Thread cycles atomic_t m_cycles{0};