[Kernel] Fix NORMAL mutex self-relock inconsistency between fast and slow lock paths - #884
Open
theantipopau wants to merge 1 commit into
Open
theantipopau wants to merge 1 commit into
theantipopau wants to merge 1 commit into
Conversation
…slow lock paths sharpemu#748 deliberately reverted NORMAL pthread mutex self-relock from silent compatibility recursion back to a real EDEADLK, because the permissive behavior was starving other threads on some titles. That revert only updated the SyncRoot-protected check in PthreadMutexLockCore; an earlier uncontended-fast-path copy of the same self-owned/MutexTypeNormal case (added by sharpemu#451, before sharpemu#748 existed) still returns OK and increments the recursion count. Since sharpemu#748, whether a self-relock on a NORMAL mutex returns OK or EDEADLK depended entirely on which of the two duplicate checks handled the call, rather than on guest behavior. Aligns the fast path with the SyncRoot-protected one so the outcome is consistent regardless of which check is reached. Tested against real dumps (Stray PPSA02101, Paw Patrol: Grand Prix PPSA05521) that hit this exact self-relock case in a scePthreadMutexLock retry loop. This fix does not change their observed behavior — both were already reaching the SyncRoot-protected check, which already returned EDEADLK before this change — confirming the inconsistency was real but narrower than initially suspected. Filing as a standalone correctness fix; the actual cause of those two titles' hangs is still open (see sharpemu#519, sharpemu#875). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This was referenced Sep 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Fixes a narrow but real inconsistency in
PthreadMutexLockCore(KernelPthreadCompatExports.cs). There are two places that handle the same case — a thread callingscePthreadMutexLockon aMutexTypeNormalmutex it already owns — and they disagreed.Background
#451added an uncontended fast-path check (before acquiringstate.SyncRoot) that treats self-relock on a NORMAL mutex as compatibility recursion: increment the recursion count, returnOK.#748later found that behavior was starving other threads on some title, and deliberately reverted the other copy of this same check — the one inside theSyncRoot-protected slow path — back to a realEDEADLK. Good, evidence-based fix, explained clearly in that PR's commit message.#748's revert didn't touch the fast-path copy from#451, since it's a separateifblock earlier in the same function. So since#748, the actual outcome of a NORMAL self-relock has depended on which of the two checks a given call happens to reach —OKvia the fast path,EDEADLOCKvia the slow path — rather than on anything about guest behavior.This PR makes the fast path match the slow path's current (intentional, evidence-based) behavior: real
EDEADLK, no silent recursion.Testing
dotnet test --no-restore— 874 passed, 0 failed (873 existing + 1 new).NormalMutex_SelfRelockIsADeadlockNotSilentRecursiontoPthreadMutexSemanticsTests.cs, pinning the now-consistent behavior via the fast (uncontended, single-thread) path specifically — this test would have failed before this change.scePthreadMutexLockretry loop returningORBIS_GEN2_ERROR_DEADLOCKrepeatedly on the same guest mutex. I want to be upfront that this fix does not change either title's observed behavior — both were already reaching theSyncRoot-protected slow path, which already returnedEDEADLKbefore this change (confirmed by re-running both against this branch: identical result). So this is a real, narrower inconsistency than I initially suspected while investigating those two titles; it's worth fixing on its own merits (any title that happens to take the fast path deserves the same, intentional Fix Windows fixed-memory allocation and mutex handling issues #748 behavior as one that takes the slow path), but it is not a fix for Stray [PPSA02100] #519 or Paw Patrol: Grand Prix (PPSA05521) — startup spins in a codepoint-enumeration loop whose bound is a live-read shared counter #875 — those remain open with the actual cause of the self-relock still unclear.AI assistance disclosure
I used Claude Code (Anthropic) to help investigate
KernelPthreadCompatExports.cs, trace the git history of both self-relock checks (viagit log -p -L) to understand why they diverged, write the fix and the new test, and draft this PR description. I reviewed the code change, the git history it's based on, and the test/real-game verification myself and can explain and defend it in review.Checklist
CONTRIBUTING.md.N/A.N/A).🤖 Generated with Claude Code