Skip to content

fix(libkernel): refuse to detach or rejoin a thread that is being joined - #6487

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
klosixgaming-arch:fix/pthread-detach-joiner
Oct 19, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
klosixgaming-arch:fix/pthread-detach-joiner

Conversation

@klosixgaming-arch

@klosixgaming-arch klosixgaming-arch commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

EA SPORTS FC 27 (PPSA34015) stopped once on Windows with:

terminate called after throwing an instance of 'std::system_error'
  what():  Joining guest thread: Access is denied
FATAL: abort() (SIGABRT)

The join comes from the title's thread wrapper (eboot 0x1d4b365, scePthreadJoin(handle, NULL) after its status poll). scePthreadJoin waits for _finished and then for the native handle. If another call releases the thread in the meantime, the join waits on a closed handle in freed memory. That other call is a scePthreadDetach, or a second scePthreadJoin whose ReleaseThread frees the thread. WaitForSingleObject then fails with ERROR_ACCESS_DENIED.

FreeBSD 11.4 libthr keeps a joiner field for this case (thr_join.c, thr_detach.c). This PR follows it, under _join_mtx:

  • scePthreadDetach returns EINVAL while a thread waits to join the target.
  • A second concurrent scePthreadJoin returns EOPNOTSUPP (ENOTSUP, 45), as join_common does.
  • A join cancelled while it waits clears the joiner before it exits (backout_join).
  • A join that completes sets _joined under the same lock, so a detach racing with its end returns EINVAL instead of releasing the thread a second time.

The libkernel debt entry for scePthreadJoin records this. main now has the _joined flag (second join after a completed one → ESRCH); this PR keeps it and adds the lock, so a detach cannot close the handle while a joiner waits.

Tested

  • New guest_pthread_join_detach:
    • A detach while a joiner waits returns EINVAL.
    • A second joiner gets EOPNOTSUPP.
    • The first join completes with the return value.
    • Detach then join returns EINVAL.
  • It passes on Linux (GCC 13.3, 20 consecutive runs) and on Windows 11 (WinLibs GCC 15.2.0). guest_pthread_start still passes on both.
  • Rebased onto main 66702f1: guest_pthread_join_detach passes again on Windows 11. The Linux runs and guest_pthread_start above are from before the rebase.
  • Not confirmed in the title. The stop above happened once. Since then PPSA34015 stops earlier in every run, at a pixel shader (v_readlane_b32 of lane 63 is outside the 32-lane host subgroup, see EA SPORTS FC 27 (PPSA34015) #6344), so I could not run it to the point of this race again. A run with this change and a log on the refused paths reached that earlier stop without the join error, which does not prove much either way.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA34015 EA SPORTS FC 27 (PPSA34015) #6344
  • GPUs tested: n/a
  • AI-assisted: yes. Written with AI assistance and reviewed by me.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 3986 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 4972 core/libs/prx/libkernel/CMakeLists.txt
PR 5021 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 6044 core/libs/prx/libkernel/CMakeLists.txt
PR 6149 docs/dev/debt/libkernel.md
PR 6226 core/libs/prx/libkernel/CMakeLists.txt
PR 6446 docs/dev/debt/libkernel.md
PR 6510 docs/dev/debt/libkernel.md
PR 6519 docs/dev/debt/libkernel.md
PR 6534 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 6879 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 6908 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 6936 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 6942 core/libs/prx/libkernel/CMakeLists.txt
PR 6977 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7039 docs/dev/debt/libkernel.md
PR 7049 docs/dev/debt/libkernel.md
PR 7075 docs/dev/debt/libkernel.md
PR 7088 docs/dev/debt/libkernel.md
PR 7105 docs/dev/debt/libkernel.md
PR 7121 docs/dev/debt/libkernel.md
PR 7129 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7149 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7192 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7201 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 7332 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7450 core/libs/prx/libkernel/CMakeLists.txt
PR 7509 docs/dev/debt/libkernel.md
PR 7565 core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7653 docs/dev/debt/libkernel.md

@klosixgaming-arch

Copy link
Copy Markdown
Contributor Author

No dependency: this PR builds on neither of them.

They edit the same lines in scePthreadJoin and scePthreadDetach, so whichever merges second needs a small rebase. If #4481 lands first, its _joined early return can sit before the _join_mtx block here unchanged. The remaining PRs in the list are the AOT stack and CMake-only overlaps.

@blawesom

Copy link
Copy Markdown
Contributor

Rebase handoff, with one contract repair — rebased onto current main (authorship unchanged). The conflicts were main's newer atomic thread state (std::atomic<bool> _joined, _handleStorage) meeting this PR's mutex-guarded _joining flag: the merged version keeps main's atomics plus your _joining flag under _join_mtx, and the detach check is the union (detached, joining, or already joined).

The repair: your join completion set _detached = true, which made a second scePthreadJoin return EINVAL where main's guest_pthread_second_join requires ESRCH. The join no longer marks the thread detached — the detach-after-join refusal your change is after is already covered by the _joined atomic in the detach path, and that test passes.

Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, AMD RX 9070 XT / RADV):

To pick it up:

git fetch https://github.com/blawesom/AnyPS5 rebase/6487
git checkout <your branch>
git merge --ff-only FETCH_HEAD

Head is 785ae9120.

@klosixgaming-arch
klosixgaming-arch force-pushed the fix/pthread-detach-joiner branch from b4ae2ec to 2b4f79e Compare October 17, 2026 16:08
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 17, 2026
@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 18, 2026
@klosixgaming-arch
klosixgaming-arch force-pushed the fix/pthread-detach-joiner branch from 2b4f79e to 97c57c7 Compare October 19, 2026 00:34
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
PPSA34015 (EA SPORTS FC 27) stopped with "Joining guest thread: Access is
denied": scePthreadJoin waited on a thread that another call released
under it, and then waited on its closed handle. As FreeBSD 11.4's
thr_join.c and thr_detach.c do with the joiner field, scePthreadDetach
now returns EINVAL while a thread waits to join the target, a second
concurrent scePthreadJoin returns EOPNOTSUPP, a cancelled join backs out,
and a completed join leaves the thread detached.
@adriadam10
adriadam10 force-pushed the fix/pthread-detach-joiner branch from 97c57c7 to 30cbb2f Compare October 19, 2026 01:38
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@adriadam10
adriadam10 merged commit 40393b0 into boykopovar:main Oct 19, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build docs libkernel unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants