mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-03 03:05:18 +02:00
Reported hang: the CPU thread held g_frameMutex (NativeFrame) and blocked on g_shutdownLock inside a queued memory.read, while the GUI thread held g_shutdownLock (CtrlMemView::onPaint) and blocked on g_frameMutex. Textbook ABBA. The CPU thread's order is structural - NativeFrame wraps everything below it in g_frameMutex, and both Core_ProcessCPUQueue() and runImDebugger() -> DisassembleRange() lock memory from under there - so the GUI side is the one that has to match. Swaps the three handlers that had it backwards (CtrlMemView::onPaint, CtrlDisAsmView::onPaint, CtrlStackTraceView:: loadStackTrace) to take g_frameMutex first. They already took both locks, so this is ordering only, and g_shutdownLock is recursive so nesting is fine. Also drops the Memory::MemoryInitedLock from the WebSocket LockMemory(), which is what made the CPU thread want that lock in the first place. It was guarding against another thread tearing down the memory system, but that doesn't happen: Memory::Shutdown() is only reached via CPU_Shutdown() <- PSP_Shutdown(), whose callers all run on the CPU thread, and Memory::Reinit() runs from Memory::DoState() on savestate load, likewise. WebSocket.cpp additionally holds lifecycleLock across the whole handler and takes it on STOPPING. Note this second part isn't sufficient on its own - ImMemView's copy-disassembly path also locks memory from inside the frame span - which is why the ordering fix is the real one. Not removing Memory::Lock() from the Win32 paint handlers: teardown isn't fully inside the g_frameMutex span yet. EmuScreen::render()'s PSP_Shutdown() is, but the ones in EmuScreen::sendMessage() (game reset, loading a new game) run from g_screenManager->sendMessage(), above where NativeFrame takes the guard. Closing that is the prerequisite, and is left for later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9