diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index a86f9f246f..ae7d804ed1 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -177,6 +177,8 @@ void PathBrowser::HandlePath() { ready_ = true; } } + + DetachThreadFromJNI(); }); } diff --git a/Common/Thread/ThreadManager.cpp b/Common/Thread/ThreadManager.cpp index 0414ee5e79..921831fa40 100644 --- a/Common/Thread/ThreadManager.cpp +++ b/Common/Thread/ThreadManager.cpp @@ -44,6 +44,7 @@ struct ThreadContext { std::atomic cancelled; std::atomic private_single; std::deque private_queue; + char name[16]; }; ThreadManager::ThreadManager() : global_(new GlobalThreadContext()) { @@ -124,14 +125,13 @@ bool ThreadManager::TeardownTask(Task *task, bool enqueue) { } static void WorkerThreadFunc(GlobalThreadContext *global, ThreadContext *thread) { - char threadName[16]; if (thread->type == TaskType::CPU_COMPUTE) { - snprintf(threadName, sizeof(threadName), "PoolWorker %d", thread->index); + snprintf(thread->name, sizeof(thread->name), "PoolWorker %d", thread->index); } else { _assert_(thread->type == TaskType::IO_BLOCKING); - snprintf(threadName, sizeof(threadName), "PoolWorkerIO %d", thread->index); + snprintf(thread->name, sizeof(thread->name), "PoolWorkerIO %d", thread->index); } - SetCurrentThreadName(threadName); + SetCurrentThreadName(thread->name); const bool isCompute = thread->type == TaskType::CPU_COMPUTE; const auto global_queue_size = [isCompute, &global]() -> int { @@ -185,6 +185,9 @@ static void WorkerThreadFunc(GlobalThreadContext *global, ThreadContext *thread) thread->queue_size--; } } + + // In case it got attached to JNI, detach it. Don't think this has any side effects if called redundantly. + DetachThreadFromJNI(); } void ThreadManager::Init(int numRealCores, int numLogicalCoresPerCpu) { diff --git a/Common/Thread/ThreadUtil.cpp b/Common/Thread/ThreadUtil.cpp index 73eab75e5c..43a44e82df 100644 --- a/Common/Thread/ThreadUtil.cpp +++ b/Common/Thread/ThreadUtil.cpp @@ -12,10 +12,15 @@ #elif defined(__ANDROID__) +#include "android/jni/app-android.h" + #define TLS_SUPPORTED #endif +// TODO: Many other platforms also support TLS, in fact probably nearly all that we support +// these days. + #include #include @@ -62,6 +67,13 @@ static EXCEPTION_DISPOSITION NTAPI ignore_handler(EXCEPTION_RECORD *rec, } #endif +void DetachThreadFromJNI() { +#if PPSSPP_PLATFORM(ANDROID) + Android_DetachThreadFromJNI(); +#else + // Do nothing +#endif +} #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) typedef HRESULT (WINAPI *TSetThreadDescription)(HANDLE, PCWSTR); @@ -90,7 +102,15 @@ static void InitializeSetThreadDescription() { void SetCurrentThreadNameThroughException(const char *threadName); #endif -void SetCurrentThreadName(const char* threadName) { +const char *GetCurrentThreadName() { +#ifdef TLS_SUPPORTED + return curThreadName; +#else + return ""; +#endif +} + +void SetCurrentThreadName(const char *threadName) { #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) InitializeSetThreadDescription(); if (g_pSetThreadDescription) { diff --git a/Common/Thread/ThreadUtil.h b/Common/Thread/ThreadUtil.h index 982de2a41c..a14c112f60 100644 --- a/Common/Thread/ThreadUtil.h +++ b/Common/Thread/ThreadUtil.h @@ -2,11 +2,26 @@ #include -// Note that name must be a global string that lives until the end of the process, +// Note that the string pointed to must be have a lifetime until the end of the thread, // for AssertCurrentThreadName to work. void SetCurrentThreadName(const char *threadName); void AssertCurrentThreadName(const char *threadName); +// If TLS is not supported, this will return an empty string. +const char *GetCurrentThreadName(); + // Just gets a cheap thread identifier so that you can see different threads in debug output, // exactly what it is is badly specified and not useful for anything. int GetCurrentThreadIdForDebug(); + +// Call when leaving threads. On Android, calls DetachCurrentThread. +// Threads that use scoped storage I/O end up attached as JNI threads, and will thus +// need this in order to follow the rules correctly. Some devices seem to enforce this. +void DetachThreadFromJNI(); + +class AndroidJNIThreadContext { +public: + ~AndroidJNIThreadContext() { + DetachThreadFromJNI(); + } +}; diff --git a/Core/Config.cpp b/Core/Config.cpp index 4971f9a8d6..11ffc2fcd7 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -32,6 +32,7 @@ #include "Common/Log.h" #include "Common/TimeUtil.h" +#include "Common/Thread/ThreadUtil.h" #include "Common/Data/Format/IniFile.h" #include "Common/Data/Format/JSONReader.h" #include "Common/Data/Text/I18n.h" @@ -43,6 +44,7 @@ #include "Common/System/Display.h" #include "Common/System/System.h" #include "Common/StringUtils.h" +#include "Common/Thread/ThreadUtil.h" #include "Common/GPU/Vulkan/VulkanLoader.h" #include "Common/VR/PPSSPPVR.h" #include "Core/Config.h" @@ -1733,6 +1735,7 @@ void Config::RemoveRecent(const std::string &file) { void Config::CleanRecent() { private_->SetRecentIsosThread([this] { + SetCurrentThreadName("RecentISOs"); double startTime = time_now_d(); std::lock_guard guard(private_->recentIsosLock); @@ -1763,6 +1766,8 @@ void Config::CleanRecent() { INFO_LOG(SYSTEM, "CleanRecent took %0.2f", time_now_d() - startTime); recentIsos = cleanedRecent; + + DetachThreadFromJNI(); }); } diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index a4d192d9ed..4c6ae7558d 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -38,6 +38,7 @@ #include "Common/Log.h" #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" +#include "Common/Thread/ThreadUtil.h" #include "Core/Config.h" #include "Core/Loaders.h" #include "Core/ELF/ParamSFO.h" @@ -278,11 +279,15 @@ ZipFileContents DetectZipFileContents(struct zip *z, ZipFileInfo *info) { // Parameters need to be by value, since this is a thread func. bool GameManager::InstallGame(Path url, Path fileName, bool deleteAfter) { + SetCurrentThreadName("InstallGame"); + if (installInProgress_ || installDonePending_) { ERROR_LOG(HLE, "Cannot have two installs in progress at the same time"); return false; } + AndroidJNIThreadContext context; // Destructor detaches. + if (!File::Exists(fileName)) { ERROR_LOG(HLE, "Game file '%s' doesn't exist", fileName.c_str()); return false; @@ -445,6 +450,7 @@ std::string GameManager::GetISOGameID(FileLoader *loader) const { if (!bd) { return ""; } + ISOFileSystem umd(&handles, bd); PSPFileInfo info = umd.GetFileInfo("/PSP_GAME/PARAM.SFO"); diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 18ef723d44..9164cdac6a 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -237,7 +237,12 @@ JNIEnv* getEnv() { JNIEnv *env; int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6); if (status < 0) { - status = gJvm->AttachCurrentThread(&env, NULL); + // TODO: We should have a version of getEnv that doesn't allow auto-attach. + INFO_LOG(SYSTEM, "Thread '%s' not attached to JVM, attaching.", GetCurrentThreadName()); + JavaVMAttachArgs args{}; + args.version = JNI_VERSION_1_6; + args.name = GetCurrentThreadName(); + status = gJvm->AttachCurrentThread(&env, &args); if (status < 0) { return nullptr; } @@ -265,6 +270,11 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) { return JNI_VERSION_1_6; } +void Android_DetachThreadFromJNI() { + INFO_LOG(SYSTEM, "Detaching thread from JNI: '%s'", GetCurrentThreadName()); + gJvm->DetachCurrentThread(); +} + // Only used in OpenGL mode. static void EmuThreadFunc() { JNIEnv *env; diff --git a/android/jni/app-android.h b/android/jni/app-android.h index c3d7e6b9a6..8c0deff287 100644 --- a/android/jni/app-android.h +++ b/android/jni/app-android.h @@ -27,4 +27,7 @@ public: }; #endif +void Android_DetachThreadFromJNI(); + #endif +